From b21e8926de26f6dd2997c6c20cb07b030ed8be47 Mon Sep 17 00:00:00 2001 From: Sidpatchy Date: Mon, 29 Apr 2024 20:41:12 -0600 Subject: [PATCH 01/64] Initial work for ClaireLang Finally getting around to implementing this. If I don't implement it before my planned feature set for v3.5, it will never happen as the complexity of implementing this will go through the roof in the update as planned. Putting this in a separate branch so that when I have downtime at work, I can work on the slow and painful process of migrating everything to this system. --- .../java/com/sidpatchy/clairebot/Main.java | 5 + .../clairebot/Util/Lang/LanguageManager.java | 141 +++++++++++++++++- .../resources/Translations/lang_TEMPLATE.yml | 43 ++++++ .../resources/Translations/lang_en-US.yml | 43 ++++++ 4 files changed, 228 insertions(+), 4 deletions(-) create mode 100644 src/main/resources/Translations/lang_TEMPLATE.yml create mode 100644 src/main/resources/Translations/lang_en-US.yml diff --git a/src/main/java/com/sidpatchy/clairebot/Main.java b/src/main/java/com/sidpatchy/clairebot/Main.java index 6cdb994..e37b077 100644 --- a/src/main/java/com/sidpatchy/clairebot/Main.java +++ b/src/main/java/com/sidpatchy/clairebot/Main.java @@ -79,6 +79,7 @@ public class Main { // Related to configuration files private static final String configFile = "config.yml"; private static final String commandsFile = "commands.yml"; + private static final String translationsPath = "config/translations/"; private static RobinConfiguration config; private static ParseCommands commands; @@ -344,4 +345,8 @@ public static String getErrorCode(String descriptor) { public static List getVoteEmoji() { return Arrays.asList("1️⃣", "2️⃣", "3️⃣", "4️⃣", "5️⃣", "6️⃣", "7️⃣", "8️⃣", "9️⃣", "🔟", "\uD83D\uDC4D", "\uD83D\uDC4E"); } public static long getStartMillis() { return startMillis; } + + public static String getTranslationsPath() { + return translationsPath; + } } diff --git a/src/main/java/com/sidpatchy/clairebot/Util/Lang/LanguageManager.java b/src/main/java/com/sidpatchy/clairebot/Util/Lang/LanguageManager.java index 00c7a08..74fd268 100644 --- a/src/main/java/com/sidpatchy/clairebot/Util/Lang/LanguageManager.java +++ b/src/main/java/com/sidpatchy/clairebot/Util/Lang/LanguageManager.java @@ -1,16 +1,149 @@ package com.sidpatchy.clairebot.Util.Lang; +import com.sidpatchy.Robin.File.RobinConfiguration; +import com.sidpatchy.clairebot.API.APIUser; +import com.sidpatchy.clairebot.API.Guild; +import com.sidpatchy.clairebot.Main; +import org.apache.logging.log4j.Logger; +import org.javacord.api.entity.server.Server; +import org.javacord.api.entity.user.User; + +import java.io.File; +import java.io.IOException; +import java.util.Locale; + public class LanguageManager { + + private final static Logger logger = Main.getLogger(); + private final String pathToLanguageFiles; + private final Locale fallbackLocale; + private final Server server; + private final User user; /** - * Construct a new ClaireBot Language Manager (ClaireLang) - * - * @param pathToLanguageFiles the path to the various language files. + * The LanguageManager class is responsible for loading and managing language files based on user preferences. + * It provides methods to retrieve localized strings and get the language file based on the user's preferred language. + *

+ * If the language manager is NOT being used in the context of a Server, but instead is being used in direct + * messages or similar, server must be set to null. There is another constructor signature that handles this, + * but it is best to avoid whenever possible. */ - public LanguageManager(String pathToLanguageFiles) { + public LanguageManager(String pathToLanguageFiles, + Locale fallbackLocaleString, + Server server, + User user) { this.pathToLanguageFiles = pathToLanguageFiles; + this.fallbackLocale = fallbackLocaleString; + this.server = server; + this.user = user; } + /** + * The LanguageManager class is responsible for loading and managing language files based on user preferences. + * It provides methods to retrieve localized strings and get the language file based on the user's preferred language. + *

+ * If the language manager is being used in the context of a Server, a server MUST be specified in order to conform + * to the ClaireLang and ClaireConfig specifications. There is another constructor signature that handles this. It + * is safe to pass a null value for the server as ClaireLang will automatically interpret this as there being no + * server. + */ + public LanguageManager(String pathToLanguageFiles, + Locale fallbackLocaleString, + User user) { + this.pathToLanguageFiles = pathToLanguageFiles; + this.fallbackLocale = fallbackLocaleString; + this.server = null; + this.user = user; + } + /** + * The LanguageManager class is responsible for loading and managing language files based on user preferences. + * It provides methods to retrieve localized strings and get the language file based on the user's preferred language. + *

+ * If the language manager is NOT being used in the context of a Server, but instead is being used in direct + * messages or similar, server must be set to null. There is another constructor signature that handles this, + * but it is best to avoid whenever possible. + *

+ * This signature should only be used by ClaireBot. If you are developing a plugin, you must specify a different + * locale path. The standard path can be obtained through the plugin API. + */ + public LanguageManager(Locale fallbackLocaleString, + Server server, + User user) { + this.pathToLanguageFiles = Main.getTranslationsPath(); + this.fallbackLocale = fallbackLocaleString; + this.server = server; + this.user = user; + } + + /** + * The LanguageManager class is responsible for loading and managing language files based on user preferences. + * It provides methods to retrieve localized strings and get the language file based on the user's preferred language. + *

+ * If the language manager is being used in the context of a Server, a server MUST be specified in order to conform + * to the ClaireLang and ClaireConfig specifications. There is another constructor signature that handles this. It + * is safe to pass a null value for the server as ClaireLang will automatically interpret this as there being no + * server. + *

+ * This signature should only be used by ClaireBot. If you are developing a plugin, you must specify a different + * locale path. The standard path can be obtained through the plugin API. + */ + public LanguageManager(Locale fallbackLocaleString, + User user) { + this.pathToLanguageFiles = Main.getTranslationsPath(); + this.fallbackLocale = fallbackLocaleString; + this.server = null; + this.user = user; + } + + /** + * Retrieves the localized string corresponding to the given key. + * + * @param key the key for the desired localized string + * @return the localized string if found, otherwise returns the key itself + * @throws IOException if an I/O error occurs while retrieving the localized string + */ + // todo consider handling the exception in this method, or in a different method signature. + public String getLocalizedString(String key) throws IOException { + APIUser apiUser = new APIUser(user.getIdAsString()); + apiUser.getUser(); + Locale locale = Locale.forLanguageTag(apiUser.getLanguage()); + + // todo, pending ClaireData update: allow server admins to specify a custom language string. + // todo ref https://trello.com/c/vkQTCTMG + if (server != null) { + Guild guild = new Guild(server.getIdAsString()); + guild.getGuild(); + + if (guild.isEnforceSeverLanguage()) { + // todo this should not be determined here, but will be until the ClaireData implementation is completed. + // todo this should instead be determined when the Guild object is created in the database. + // todo ClaireData update on hold while still designing the major ClaireBot update that follows this one. + locale = server.getPreferredLocale(); + } + } + + RobinConfiguration languageFile = getLangFileByLocale(locale); + String localizedString = languageFile.getString(key); + logger.debug(localizedString); + return localizedString != null ? localizedString : key; + } + + /** + * Returns a file based off the language string specified. + * Returns the fallback file if a suitable translation isn't found. + * + * @param locale the Locale object for the bot + * @return Returns a localized language file or the fallback file if a suitable translation doesn't exist. + */ + public RobinConfiguration getLangFileByLocale(Locale locale) { + File file = new File(pathToLanguageFiles, "lang_" + locale.toLanguageTag() + ".yml"); + if (file.exists()) { + return new RobinConfiguration(pathToLanguageFiles + "lang_" + locale.toLanguageTag() + ".yml"); + } + else { + return new RobinConfiguration(pathToLanguageFiles + "lang_" + fallbackLocale.toLanguageTag() + ".yml"); + } + } } diff --git a/src/main/resources/Translations/lang_TEMPLATE.yml b/src/main/resources/Translations/lang_TEMPLATE.yml new file mode 100644 index 0000000..eb39c85 --- /dev/null +++ b/src/main/resources/Translations/lang_TEMPLATE.yml @@ -0,0 +1,43 @@ +# ---------------------------------------------- +# | ClaireBot v3.4.0 | +# | TEMPLATE language file | +# ---------------------------------------------- +# +# Please reference the below wiki page to learn more about contributing: +# todo insert wiki page +# Thank you for any and all contributions! + +# A list of people who have contributed to the translations for this language. +# +# Please include your name if you contribute to this file. It will be displayed in the credits section for a language +# whenever it is selected. You may include a link to your GitHub or any other social media platform. +contributors: + - ["Example", "https://github.com/Example/"] + +# Any notes that translators feel the need to express, written in the target language. +# At your option, you may leave this field blank if you feel that it is not needed. If updating the language file, +# feel free to modify or remove this (make it blank) if it no longer applies. +# +# If you are contributing, please provide the ClaireBot maintainer(s) with a translated version/explanation of this. +# The explanation doesn't have to go into crazy detail, just let us know why it is present. +translationNotes: "" + +# The ClaireLang version that applies to this file. +# +# If you are translating ClaireBot, you do not need to worry about understanding this, the project maintainer(s) will +# happily assist you in determining this. +# +# The above being said, you are more than welcome to update this value to match the +# language revision you are translating to. +# +# This number should be changed in accordance with the following: +# +# ClaireLang version numbers are incremented every time new parameter(s) are added. If the main language file (en-US) +# has its wording changed, a .1, .2, .3 decimal value will be added. +# +# ClaireLang version numbers are used by the bot to assist in determining whether parameters are missing or not +# yet translated. +# +# See the below wiki page for a changelog: +# todo insert wiki page +version: 1 \ No newline at end of file diff --git a/src/main/resources/Translations/lang_en-US.yml b/src/main/resources/Translations/lang_en-US.yml new file mode 100644 index 0000000..a31e008 --- /dev/null +++ b/src/main/resources/Translations/lang_en-US.yml @@ -0,0 +1,43 @@ +# ---------------------------------------------- +# | ClaireBot v3.4.0 | +# | English language file | +# ---------------------------------------------- +# +# Please reference the below wiki page to learn more about contributing: +# todo insert wiki page +# Thank you for any and all contributions! + +# A list of people who have contributed to the translations for this language. +# +# Please include your name if you contribute to this file. It will be displayed in the credits section for a language +# whenever it is selected. You may include a link to your GitHub or any other social media platform. +contributors: + - ["Sidpatchy", "https://github.com/Sidpatchy/"] + +# Any notes that translators feel the need to express, written in the target language. +# At your option, you may leave this field blank if you feel that it is not needed. If updating the language file, +# feel free to modify or remove this (make it blank) if it no longer applies. +# +# If you are contributing, please provide the ClaireBot maintainer(s) with a translated version/explanation of this. +# The explanation doesn't have to go into crazy detail, just let us know why it is present. +translationNotes: "The officially supported language for ClaireBot." + +# The ClaireLang version that applies to this file. +# +# If you are translating ClaireBot, you do not need to worry about understanding this, the project maintainer(s) will +# happily assist you in determining this. +# +# The above being said, you are more than welcome to update this value to match the +# language revision you are translating to. +# +# This number should be changed in accordance with the following: +# +# ClaireLang version numbers are incremented every time new parameter(s) are added. If the main language file (en-US) +# has its wording changed, a .1, .2, .3 decimal value will be added. +# +# ClaireLang version numbers are used by the bot to assist in determining whether parameters are missing or not +# yet translated. +# +# See the below wiki page for a changelog: +# todo insert wiki page +version: 1 \ No newline at end of file From 0511ca2f739b4d9500447f4fe6a1b2b7df52aff2 Mon Sep 17 00:00:00 2001 From: Sidpatchy <35241490+Sidpatchy@users.noreply.github.com> Date: Mon, 6 May 2024 16:55:25 -0600 Subject: [PATCH 02/64] Some Fix some issues caused due to my IDE being configured to use Java 21. Switch the commands.yml loader to the new system introduced in Robin @ v2.1.0 FINALLY get rid of the godawful commandList Alphabetize commands.yml Begin progress on writing lang_en-US.yml --- build.gradle | 14 +- .../com/sidpatchy/clairebot/Commands.java | 129 ++++++++++++++++++ .../Embed/Commands/Regular/HelpEmbed.java | 88 ++++++------ .../Embed/Commands/Regular/InfoEmbed.java | 2 +- .../Embed/Commands/Regular/QuoteEmbed.java | 2 +- .../java/com/sidpatchy/clairebot/Main.java | 34 +++-- .../clairebot/RegisterSlashCommands.java | 68 ++++++--- .../resources/Translations/lang_en-US.yml | 53 ++++++- src/main/resources/commands.yml | 48 +++---- 9 files changed, 334 insertions(+), 104 deletions(-) create mode 100644 src/main/java/com/sidpatchy/clairebot/Commands.java diff --git a/build.gradle b/build.gradle index c7377ec..7c3fcd9 100644 --- a/build.gradle +++ b/build.gradle @@ -4,6 +4,9 @@ plugins { id 'java' } +// Make compiler not dumb when using Winblows +compileJava.options.encoding = 'UTF-8' + jar { manifest.attributes( 'Main-Class': 'com.sidpatchy.clairebot.Main', @@ -12,7 +15,7 @@ jar { } group 'com.sidpatchy' -version '3.3.2' +version '3.4.0' sourceCompatibility = 17 targetCompatibility = 17 @@ -27,7 +30,7 @@ dependencies { testImplementation 'org.junit.jupiter:junit-jupiter-api:5.10.1' testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.10.1' - implementation 'com.github.Sidpatchy:Robin:2.0.0' + implementation 'com.github.Sidpatchy:Robin:2.1.2' implementation 'org.javacord:javacord:3.8.0' @@ -38,9 +41,12 @@ dependencies { implementation 'org.jetbrains.kotlin:kotlin-stdlib:1.9.22' + // How many file processing libraries can we have?!?! implementation 'org.yaml:snakeyaml:2.0' implementation 'org.json:json:20231013' + implementation 'com.fasterxml.jackson.core:jackson-databind:2.16.1' implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.16.1' + implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.16.1' } @@ -50,11 +56,11 @@ test { compileKotlin { kotlinOptions { - jvmTarget = "11" + jvmTarget = "17" } } compileTestKotlin { kotlinOptions { - jvmTarget = "11" + jvmTarget = "17" } } \ No newline at end of file diff --git a/src/main/java/com/sidpatchy/clairebot/Commands.java b/src/main/java/com/sidpatchy/clairebot/Commands.java new file mode 100644 index 0000000..c52cb55 --- /dev/null +++ b/src/main/java/com/sidpatchy/clairebot/Commands.java @@ -0,0 +1,129 @@ +package com.sidpatchy.clairebot; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.sidpatchy.Robin.Discord.Command; + +/** + * Represents ALL commands within ClaireBot. Initialized using the CommandFactory from Robin >= 2.1.0. + */ +public class Commands { + private Command avatar; + private Command config; + private Command eightball; + private Command help; + private Command info; + private Command leaderboard; + private Command level; + private Command poll; + private Command quote; + private Command request; + private Command santa; + private Command server; + private Command user; + @JsonProperty("config-revision") + private String configRevision; + + public Command getAvatar() { + return avatar; + } + + public Command getConfig() { + return config; + } + + public Command getEightball() { + return eightball; + } + + public Command getHelp() { + return help; + } + + public Command getInfo() { + return info; + } + + public Command getLeaderboard() { + return leaderboard; + } + + public Command getLevel() { + return level; + } + + public Command getPoll() { + return poll; + } + + public Command getQuote() { + return quote; + } + + public Command getRequest() { + return request; + } + + public Command getSanta() { + return santa; + } + + public Command getServer() { + return server; + } + + public Command getUser() { + return user; + } + + public void setAvatar(Command avatar) { + this.avatar = avatar; + } + + public void setConfig(Command config) { + this.config = config; + } + + public void setEightball(Command eightball) { + this.eightball = eightball; + } + + public void setHelp(Command help) { + this.help = help; + } + + public void setInfo(Command info) { + this.info = info; + } + + public void setLeaderboard(Command leaderboard) { + this.leaderboard = leaderboard; + } + + public void setLevel(Command level) { + this.level = level; + } + + public void setPoll(Command poll) { + this.poll = poll; + } + + public void setQuote(Command quote) { + this.quote = quote; + } + + public void setRequest(Command request) { + this.request = request; + } + + public void setSanta(Command santa) { + this.santa = santa; + } + + public void setServer(Command server) { + this.server = server; + } + + public void setUser(Command user) { + this.user = user; + } +} diff --git a/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/HelpEmbed.java b/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/HelpEmbed.java index 4dfebd1..47c7b3e 100644 --- a/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/HelpEmbed.java +++ b/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/HelpEmbed.java @@ -1,63 +1,71 @@ package com.sidpatchy.clairebot.Embed.Commands.Regular; -import com.sidpatchy.Robin.Discord.ParseCommands; +import com.sidpatchy.Robin.Discord.Command; +import com.sidpatchy.clairebot.Commands; import com.sidpatchy.clairebot.Embed.ErrorEmbed; import com.sidpatchy.clairebot.Main; import org.javacord.api.entity.message.embed.EmbedBuilder; import java.io.FileNotFoundException; -import java.util.Arrays; +import java.lang.reflect.Field; import java.util.HashMap; -import java.util.List; public class HelpEmbed { - private static final ParseCommands commands = new ParseCommands(Main.getCommandsFile()); + private static final Commands commands = Main.getCommands(); + public static EmbedBuilder getHelp(String commandName, String userID) throws FileNotFoundException { - List regularCommandsList = Arrays.asList("8ball", "avatar", "help", "info", "leaderboard", "level", "poll", "quote", "request", "server", "user", "config", "santa"); + HashMap allCommands = new HashMap<>(); + HashMap regularCommands = new HashMap<>(); - // Create HashMaps for help command - HashMap> allCommands = new HashMap>(); - HashMap> regularCommands = new HashMap>(); + for (Field field : commands.getClass().getDeclaredFields()) { + try { + Command command = (Command) field.get(commands); + allCommands.put(field.getName(), command); + regularCommands.put(field.getName(), command); + } catch (IllegalAccessException e) { + // todo handle the exception + } + } - for (String s : regularCommandsList) { - regularCommands.put(s, commands.get(s)); + if (commandName.equalsIgnoreCase("help")) { + return buildHelpEmbed(userID, regularCommands); + } else { + return buildCommandDetailEmbed(commandName, userID, allCommands); } + } - allCommands.putAll(regularCommands); + private static EmbedBuilder buildHelpEmbed(String userID, HashMap regularCommands) { + StringBuilder commandsList = new StringBuilder("```"); - // Commands list - if (commandName.equalsIgnoreCase("help")) { - StringBuilder glob = new StringBuilder("```"); - for (String s : regularCommandsList) { - if (glob.toString().equalsIgnoreCase("```")) { - glob.append(commands.getCommandName(s)); - } else { - glob.append(", ") - .append(commands.getCommandName(s)); - } + for (String commandName : regularCommands.keySet()) { + if (commandsList.length() > 3) { + commandsList.append(", "); } - glob.append("```"); + commandsList.append(commandName); + } - EmbedBuilder embed = new EmbedBuilder() - .setColor(Main.getColor(userID)) - .addField("Commands", glob.toString(), false); + commandsList.append("```"); - return embed; - } - // Command details - else { - if (allCommands.get(commandName) == null) { - String errorCode = Main.getErrorCode("help_command"); - Main.getLogger().error("Unable to locate command \"" + commandName + "\" for help command. Error code: " + errorCode); - return ErrorEmbed.getError(errorCode); - } else { - return new EmbedBuilder() - .setColor(Main.getColor(userID)) - .setAuthor(commandName.toUpperCase()) - .setDescription(allCommands.get(commandName).get("help")) - .addField("Command", "Usage\n" + "```" + allCommands.get(commandName).get("usage") + "```"); - } + return new EmbedBuilder() + .setColor(Main.getColor(userID)) + .addField("Commands", commandsList.toString(), false); + } + + private static EmbedBuilder buildCommandDetailEmbed(String commandName, String userID, HashMap allCommands) { + Command command = allCommands.get(commandName); + + if (command == null) { + String errorCode = Main.getErrorCode("help_command"); + Main.getLogger().error("Unable to locate command \"" + commandName + "\" for help command. Error code: " + errorCode); + return ErrorEmbed.getError(errorCode); + } else { + return new EmbedBuilder() + .setColor(Main.getColor(userID)) + .setAuthor(commandName.toUpperCase()) + .setDescription(command.getOverview().isEmpty() ? command.getHelp() : command.getOverview()) + .addField("Command", "Usage\n```" + command.getUsage() + "```"); } } } + diff --git a/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/InfoEmbed.java b/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/InfoEmbed.java index b48ee0a..4140fe4 100644 --- a/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/InfoEmbed.java +++ b/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/InfoEmbed.java @@ -10,7 +10,7 @@ public static EmbedBuilder getInfo(User author) { String timeSinceStart = DurationFormatUtils.formatDurationWords(System.currentTimeMillis() - Main.getStartMillis(), true, false); return new EmbedBuilder() .setColor(Main.getColor(author.getIdAsString())) - .addField("Need Help?", "You can get help by creating an issue on our [GitHub](https://github.com/Sidpatchy/ClaireBot/issues) or by joining our [support server](https://discord.gg/NwQUkZQ)", true) + .addField("Need Help?", "You can get help by creating an issue on our [GitHub](https://github.com/Sidpatchy/ClaireBot/issues) or by joining our [support server](https://support.clairebot.net/)", true) .addField("Add Me to a Server", "Adding me to a server is simple, all you have to do is click [here](https://invite.clairebot.net)", true) .addField("GitHub", "ClaireBot is open source, that means you can view all of its code! Check out its [GitHub!](https://github.com/Sidpatchy/ClaireBot)", true) .addField("Server Count", "I have enlightened **" + Main.getApi().getServers().size() + "** servers.", true) diff --git a/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/QuoteEmbed.java b/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/QuoteEmbed.java index e245706..153368b 100644 --- a/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/QuoteEmbed.java +++ b/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/QuoteEmbed.java @@ -1,7 +1,7 @@ package com.sidpatchy.clairebot.Embed.Commands.Regular; + import com.sidpatchy.clairebot.Embed.ErrorEmbed; import com.sidpatchy.clairebot.Main; -import org.javacord.api.entity.Icon; import org.javacord.api.entity.channel.TextChannel; import org.javacord.api.entity.message.Message; import org.javacord.api.entity.message.MessageBuilder; diff --git a/src/main/java/com/sidpatchy/clairebot/Main.java b/src/main/java/com/sidpatchy/clairebot/Main.java index e37b077..8c8e13f 100644 --- a/src/main/java/com/sidpatchy/clairebot/Main.java +++ b/src/main/java/com/sidpatchy/clairebot/Main.java @@ -1,6 +1,6 @@ package com.sidpatchy.clairebot; -import com.sidpatchy.Robin.Discord.ParseCommands; +import com.sidpatchy.Robin.Discord.CommandFactory; import com.sidpatchy.Robin.Exception.InvalidConfigurationException; import com.sidpatchy.Robin.File.ResourceLoader; import com.sidpatchy.Robin.File.RobinConfiguration; @@ -81,9 +81,7 @@ public class Main { private static final String commandsFile = "commands.yml"; private static final String translationsPath = "config/translations/"; private static RobinConfiguration config; - private static ParseCommands commands; - - public static List commandList = Arrays.asList("8ball", "avatar", "help", "info", "leaderboard", "level", "poll", "quote", "request", "server", "user", "config", "santa"); + private static Commands commands; public static void main(String[] args) throws InvalidConfigurationException { logger.info("ClaireBot loading..."); @@ -95,7 +93,6 @@ public static void main(String[] args) throws InvalidConfigurationException { // Init config handlers config = new RobinConfiguration("config/" + configFile); - commands = new ParseCommands("config/" + commandsFile); config.load(); @@ -106,6 +103,7 @@ public static void main(String[] args) throws InvalidConfigurationException { String video_url = config.getString("video_url"); extractParametersFromConfig(true); + loadCommandDefs(); verifyDatabaseConnectivity(); @@ -203,6 +201,17 @@ public static void extractParametersFromConfig(boolean updateOutdatedConfigs) { } + public static void loadCommandDefs() { + try { + commands = CommandFactory.loadConfig("config/" + commandsFile, Commands.class); + logger.warn(commands.getInfo().getName()); + logger.warn(commands.getInfo().getHelp()); + } catch (IOException e) { + logger.fatal("There was a fatal error while registering slash commands", e); + throw new RuntimeException(e); + } + } + // Handle the registry of slash commands and any errors associated. public static void registerSlashCommands() { try { @@ -210,17 +219,12 @@ public static void registerSlashCommands() { logger.info("Slash commands registered successfully!"); } catch (NullPointerException e) { - e.printStackTrace(); - logger.fatal("There was an error while registering slash commands. There's a pretty good chance it's related to an uncaught issue with the commands.yml file, trying to read all commands and printing out results."); - for (String s : Main.commandList) { - logger.fatal(commands.getCommandName(s)); - } - logger.fatal("If the above list looks incomplete or generates another error, check your commands.yml file!"); + logger.fatal("There was an error while registering slash commands. There's a pretty good chance it's related to an uncaught issue with the commands.yml file.", e); + logger.fatal("Check your commands.yml file!"); System.exit(4); } catch (Exception e) { - e.printStackTrace(); - logger.fatal("There was a fatal error while registering slash commands."); + logger.fatal("There was a fatal error while registering slash commands.", e); System.exit(5); } } @@ -334,6 +338,10 @@ public static String getZerfasEmojiID() { public static String getCommandsFile() { return "config/" + commandsFile; } + public static Commands getCommands() { + return commands; + } + public static Logger getLogger() { return logger; } public static String getErrorCode(String descriptor) { diff --git a/src/main/java/com/sidpatchy/clairebot/RegisterSlashCommands.java b/src/main/java/com/sidpatchy/clairebot/RegisterSlashCommands.java index 421bb9e..93913a9 100644 --- a/src/main/java/com/sidpatchy/clairebot/RegisterSlashCommands.java +++ b/src/main/java/com/sidpatchy/clairebot/RegisterSlashCommands.java @@ -1,12 +1,12 @@ package com.sidpatchy.clairebot; -import com.sidpatchy.Robin.Discord.ParseCommands; import org.javacord.api.DiscordApi; import org.javacord.api.interaction.SlashCommandBuilder; import org.javacord.api.interaction.SlashCommandOption; import org.javacord.api.interaction.SlashCommandOptionChoice; import org.javacord.api.interaction.SlashCommandOptionType; +import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; @@ -14,10 +14,12 @@ /** * Also delete them too! + * + * todo switch to registering commands on a per-server basis so that server admins may use different commands.yml languages. */ public class RegisterSlashCommands { - private static final ParseCommands parseCommands = new ParseCommands(Main.getCommandsFile()); + private static final Commands commands = Main.getCommands(); public static void DeleteSlashCommands (DiscordApi api) { api.bulkOverwriteGlobalApplicationCommands(Set.of()).join(); @@ -34,21 +36,39 @@ public static void RegisterSlashCommand(DiscordApi api) { // Create the command list in the help command without repeating the same thing 50 million times. ArrayList helpCommandOptions = new ArrayList<>(); - for (String s : Main.commandList) { - helpCommandOptions.add(SlashCommandOptionChoice.create(parseCommands.getCommandName(s), parseCommands.getCommandName(s))); + for (Field field : commands.getClass().getDeclaredFields()) { + helpCommandOptions.add(SlashCommandOptionChoice.create(field.getName(), field.getName())); } Set commandsList = new HashSet<>(Arrays.asList( // Regular commands - new SlashCommandBuilder().setName(parseCommands.getCommandName("8ball")).setDescription(parseCommands.getCommandHelp("8ball")).addOption(SlashCommandOption.create(SlashCommandOptionType.STRING, "query", "The question you wish to ask.", true)), - new SlashCommandBuilder().setName(parseCommands.getCommandName("avatar")).setDescription(parseCommands.getCommandHelp("avatar")).addOption(SlashCommandOption.create(SlashCommandOptionType.USER, "user", "Optionally mention a user.", false)) + new SlashCommandBuilder() + .setName(commands.getEightball().getName()) + .setDescription(commands.getEightball().getHelp()) + .addOption(SlashCommandOption.create(SlashCommandOptionType.STRING, "query", "The question you wish to ask.", true)), + new SlashCommandBuilder() + .setName(commands.getAvatar().getName()) + .setDescription(commands.getAvatar().getHelp()) + .addOption(SlashCommandOption.create(SlashCommandOptionType.USER, "user", "Optionally mention a user.", false)) .addOption(SlashCommandOption.create(SlashCommandOptionType.BOOLEAN, "globalAvatar", "Whether the bot should display the global or server avatar.")), - new SlashCommandBuilder().setName(parseCommands.getCommandName("help")).setDescription(parseCommands.getCommandHelp("help")) + new SlashCommandBuilder() + .setName(commands.getHelp().getName()) + .setDescription(commands.getHelp().getHelp()) .addOption(SlashCommandOption.createWithChoices(SlashCommandOptionType.STRING, "command-name", "Command to get more info on", false, helpCommandOptions)), - new SlashCommandBuilder().setName(parseCommands.getCommandName("info")).setDescription(parseCommands.getCommandHelp("info")), - new SlashCommandBuilder().setName(parseCommands.getCommandName("leaderboard")).setDescription(parseCommands.getCommandHelp("leaderboard")).addOption(SlashCommandOption.create(SlashCommandOptionType.BOOLEAN, "global", "Get the global leaderboard?", false)), - new SlashCommandBuilder().setName(parseCommands.getCommandName("level")).setDescription(parseCommands.getCommandHelp("level")).addOption(SlashCommandOption.create(SlashCommandOptionType.USER, "user", "Optionally mention a user.", false)), - new SlashCommandBuilder().setName(parseCommands.getCommandName("poll")).setDescription(parseCommands.getCommandHelp("poll")) + new SlashCommandBuilder() + .setName(commands.getInfo().getName()) + .setDescription(commands.getInfo().getHelp()), + new SlashCommandBuilder() + .setName(commands.getLeaderboard().getName()) + .setDescription(commands.getLeaderboard().getHelp()) + .addOption(SlashCommandOption.create(SlashCommandOptionType.BOOLEAN, "global", "Get the global leaderboard?", false)), + new SlashCommandBuilder() + .setName(commands.getLevel().getName()) + .setDescription(commands.getLevel().getHelp()) + .addOption(SlashCommandOption.create(SlashCommandOptionType.USER, "user", "Optionally mention a user.", false)), + new SlashCommandBuilder() + .setName(commands.getPoll().getName()) + .setDescription(commands.getPoll().getHelp()) .addOption(SlashCommandOption.create(SlashCommandOptionType.STRING, "question", "Question to ask", false)) .addOption(SlashCommandOption.create(SlashCommandOptionType.BOOLEAN, "allow-multiple-choices", "Whether multiple choices should be enabled.", false)) .addOption(SlashCommandOption.create(SlashCommandOptionType.STRING, "choice-1", "Custom choice")) @@ -60,9 +80,13 @@ public static void RegisterSlashCommand(DiscordApi api) { .addOption(SlashCommandOption.create(SlashCommandOptionType.STRING, "choice-7", "Custom choice")) .addOption(SlashCommandOption.create(SlashCommandOptionType.STRING, "choice-8", "Custom choice")) .addOption(SlashCommandOption.create(SlashCommandOptionType.STRING, "choice-9", "Custom choice")), - new SlashCommandBuilder().setName(parseCommands.getCommandName("quote")).setDescription(parseCommands.getCommandOverview("quote")) + new SlashCommandBuilder() + .setName(commands.getQuote().getName()) + .setDescription(commands.getQuote().getHelp()) .addOption(SlashCommandOption.create(SlashCommandOptionType.USER, "User", "Optionally mention a user", false)), - new SlashCommandBuilder().setName(parseCommands.getCommandName("request")).setDescription(parseCommands.getCommandOverview("request")) + new SlashCommandBuilder() + .setName(commands.getRequest().getName()) + .setDescription(commands.getRequest().getHelp()) .addOption(SlashCommandOption.create(SlashCommandOptionType.STRING, "question", "Question to ask", false)) .addOption(SlashCommandOption.create(SlashCommandOptionType.BOOLEAN, "allow-multiple-choices", "Whether multiple choices should be enabled.", false)) .addOption(SlashCommandOption.create(SlashCommandOptionType.STRING, "choice-1", "Custom choice")) @@ -74,14 +98,24 @@ public static void RegisterSlashCommand(DiscordApi api) { .addOption(SlashCommandOption.create(SlashCommandOptionType.STRING, "choice-7", "Custom choice")) .addOption(SlashCommandOption.create(SlashCommandOptionType.STRING, "choice-8", "Custom choice")) .addOption(SlashCommandOption.create(SlashCommandOptionType.STRING, "choice-9", "Custom choice")), - new SlashCommandBuilder().setName(parseCommands.getCommandName("server")).setDescription(parseCommands.getCommandHelp("server")).addOption(SlashCommandOption.create(SlashCommandOptionType.STRING, "guildID", "Optionally specify a guild by ID.", false)), - new SlashCommandBuilder().setName(parseCommands.getCommandName("user")).setDescription(parseCommands.getCommandHelp("user")).addOption(SlashCommandOption.create(SlashCommandOptionType.USER, "user", "Optionally mention a user.", false)), - new SlashCommandBuilder().setName(parseCommands.getCommandName("config")).setDescription(parseCommands.getCommandHelp("config")) + new SlashCommandBuilder() + .setName(commands.getServer().getName()) + .setDescription(commands.getServer().getHelp()) + .addOption(SlashCommandOption.create(SlashCommandOptionType.STRING, "guildID", "Optionally specify a guild by ID.", false)), + new SlashCommandBuilder() + .setName(commands.getServer().getName()) + .setDescription(commands.getServer().getHelp()) + .addOption(SlashCommandOption.create(SlashCommandOptionType.USER, "user", "Optionally mention a user.", false)), + new SlashCommandBuilder() + .setName(commands.getConfig().getName()) + .setDescription(commands.getConfig().getHelp()) .addOption(SlashCommandOption.createWithChoices(SlashCommandOptionType.STRING, "mode", "Settings to change", false, Arrays.asList( SlashCommandOptionChoice.create("user", "user"), SlashCommandOptionChoice.create("server", "server") ))), - new SlashCommandBuilder().setName(parseCommands.getCommandName("santa")).setDescription(parseCommands.getCommandOverview("santa")) + new SlashCommandBuilder() + .setName(commands.getSanta().getName()) + .setDescription(commands.getSanta().getHelp()) .addOption(SlashCommandOption.create(SlashCommandOptionType.ROLE, "Role", "Role to get users from", true)) //new SlashCommandBuilder().setName(parseCommands.getCommandName("debug")).setDescription(parseCommands.getCommandHelp("debug")) )); diff --git a/src/main/resources/Translations/lang_en-US.yml b/src/main/resources/Translations/lang_en-US.yml index a31e008..e228255 100644 --- a/src/main/resources/Translations/lang_en-US.yml +++ b/src/main/resources/Translations/lang_en-US.yml @@ -40,4 +40,55 @@ translationNotes: "The officially supported language for ClaireBot." # # See the below wiki page for a changelog: # todo insert wiki page -version: 1 \ No newline at end of file +version: 1 + +# Language Keys +# -------------------------------------------------------------------------------------------------------------------- # + +clairebot: + embed: + Commands: + Regular: + AvatarEmbed: "" # Unused + EightBallEmbed: + 8bResponses: + - "*Hell yes!*" + - "*Fuck no*" + - "*Maybe*" + - "*Yes*" + - "*No*" + - "*No way*" + - "*It's possible.*" + - "*Not a chance.*" + - "*I doubt it.*" + - "*Absolutely!*" + - "*Hard to say.*" + - "*Likely!*" + - "*Not likely.*" + 8bRiggedResponses: + - "*Hell yeah!*" + - "*FUCK YEAH*" + - "*You know it!*" + - "*Do you really need to ask question you already know answer for? Obviously yes.*" + - "*Doesn't even need to be said, everyone knows ClaireBot is on top.*" + - "*The answer is clear: YES!*" + - "*ClaireBot's dominance is undisputed.*" + - "*With ClaireBot, there's no question!*" + - "*All signs point to ClaireBot being on top!*" + # Note: The rest of the help is located separately within commands_en-US.yml + HelpEmbed: + Commands: "Commands" + InfoEmbed: + NeedHelp: "Need Help?" + NeedHelpDetails: "You can get help by creating an issue on our [GitHub](https://github.com/Sidpatchy/ClaireBot/issues) or by joining our [support server](https://support.clairebot.net/)" + AddToServer: "Add Me to a Server" + AddToServerDetails: "Adding me to a server is simple, all you have to do is click [here](https://invite.clairebot.net)" + GitHub: "GitHub" + GitHubDetails: "ClaireBot is open source, that means you can view all of its code! Check out its [GitHub!](https://github.com/Sidpatchy/ClaireBot)" + ServerCount: "Server Count" + ServerCountDetails: "I have enlightened **{clairebot.placeholder.numservers}** servers." + Version: "Version" + VersionDetails: "I am running ClaireBot **v{clairebot.placeholder.version}**, released on **{clairebot.placeholder.releasedate}**" + Uptime: "Uptime" + UptimeValue: "Started on \n*{clairebot.placeholder.runtimedurationwords}*" + diff --git a/src/main/resources/commands.yml b/src/main/resources/commands.yml index 6a3af16..907ff85 100644 --- a/src/main/resources/commands.yml +++ b/src/main/resources/commands.yml @@ -1,5 +1,5 @@ # ---------------------------------------------- -# | ClaireBot v3.3.0 | +# | ClaireBot v3.4.0 | # | Commands Config file | # ---------------------------------------------- # Allows for changing how the help command displays commands. @@ -8,12 +8,6 @@ # list of commands (and config) using /reload. (NYI) # ------------------ COMMANDS ------------------ -8ball: - name: "8ball" - usage: "/8ball " - help: "ClaireBot only speaks in absolute truth. Flesh betrays, ClaireBot will not." - overview: "" - avatar: name: "avatar" usage: "/avatar " @@ -22,6 +16,18 @@ avatar: avatar. If the user has no server avatar, ClaireBot will fallback to their global avatar. True = global, False = server. Defaults to True." +config: + name: "config" + usage: "/config" + help: "Opens up settings menu for modifying user or server settings." # Probably worth writing a brief wiki page for this command + overview: "" + +eightball: + name: "8ball" + usage: "/8ball " + help: "ClaireBot only speaks in absolute truth. Flesh betrays, ClaireBot will not." + overview: "" + help: name: "help" usage: "/help " @@ -65,11 +71,14 @@ request: help: "Creates a request in the server's designated requests channel.\nIf allow-multiple-choices is set to true, ClaireBot will allow multiple options to be selected. This defaults to false if it isn't specified." overview: "Creates a request." -# TO BE REMOVED and integrated into /info. -# servers: -# name: "servers" -# usage: "/servers" -# help: "Reports how many servers ClaireBot is in." +santa: + name: "santa" + usage: "/santa " + help: "The all-in-one gift exchange coordinator for secret santa-like events.\n\nWhen run, a dialogue displaying all + pair-ups will be privately messaged to the issuer. The issuer will then be able to view the pair-ups, add + rules/themes, view a sample message, and regenerate the pair-ups before anything is sent to a santa.\n\nYou must have + permission to manage the role you are attempting to use." + overview: "Command to send secret santa message to mentioned role's users." server: name: "server" @@ -83,20 +92,5 @@ user: help: "Reports various bits of info on a user." overview: "" -config: - name: "config" - usage: "/config" - help: "Opens up settings menu for modifying user or server settings." # Probably worth writing a brief wiki page for this command - overview: "" - -santa: - name: "santa" - usage: "/santa " - help: "The all-in-one gift exchange coordinator for secret santa-like events.\n\nWhen run, a dialogue displaying all - pair-ups will be privately messaged to the issuer. The issuer will then be able to view the pair-ups, add - rules/themes, view a sample message, and regenerate the pair-ups before anything is sent to a santa.\n\nYou must have - permission to manage the role you are attempting to use." - overview: "Command to send secret santa message to mentioned role's users." - # Do not change this, this is automatically changed if needed config-revision: 1.2 \ No newline at end of file From 34ce769741063051cddd78e22b7375583a16afce Mon Sep 17 00:00:00 2001 From: Sidpatchy Date: Mon, 6 May 2024 20:34:19 -0600 Subject: [PATCH 03/64] Probably a bad idea I prefer the idea of this structure, I just don't like the implementation of this structure. Also considered doing one giga-class (technical term :)), but that would've been much less readable than whatever in the fuck this is. Probably would've been easier to understand the intent of though... I will need to write some really good javadoc if I stick with this. --- .../Lang/Beans/ClaireLang/ClaireLang.java | 20 +++++++++++++++++++ .../ClaireLang/Embed/Commands/Commands.java | 7 +++++++ .../Embed/Commands/Regular/EightBall.java | 12 +++++++++++ .../Embed/Commands/Regular/Help.java | 8 ++++++++ .../Embed/Commands/Regular/Info.java | 16 +++++++++++++++ .../Embed/Commands/Regular/Regular.java | 9 +++++++++ .../Lang/Beans/ClaireLang/Embed/Embed.java | 7 +++++++ .../{Util => }/Lang/LanguageManager.java | 2 +- .../resources/Translations/lang_en-US.yml | 4 ++-- 9 files changed, 82 insertions(+), 3 deletions(-) create mode 100644 src/main/java/com/sidpatchy/clairebot/Lang/Beans/ClaireLang/ClaireLang.java create mode 100644 src/main/java/com/sidpatchy/clairebot/Lang/Beans/ClaireLang/Embed/Commands/Commands.java create mode 100644 src/main/java/com/sidpatchy/clairebot/Lang/Beans/ClaireLang/Embed/Commands/Regular/EightBall.java create mode 100644 src/main/java/com/sidpatchy/clairebot/Lang/Beans/ClaireLang/Embed/Commands/Regular/Help.java create mode 100644 src/main/java/com/sidpatchy/clairebot/Lang/Beans/ClaireLang/Embed/Commands/Regular/Info.java create mode 100644 src/main/java/com/sidpatchy/clairebot/Lang/Beans/ClaireLang/Embed/Commands/Regular/Regular.java create mode 100644 src/main/java/com/sidpatchy/clairebot/Lang/Beans/ClaireLang/Embed/Embed.java rename src/main/java/com/sidpatchy/clairebot/{Util => }/Lang/LanguageManager.java (99%) diff --git a/src/main/java/com/sidpatchy/clairebot/Lang/Beans/ClaireLang/ClaireLang.java b/src/main/java/com/sidpatchy/clairebot/Lang/Beans/ClaireLang/ClaireLang.java new file mode 100644 index 0000000..f21ac69 --- /dev/null +++ b/src/main/java/com/sidpatchy/clairebot/Lang/Beans/ClaireLang/ClaireLang.java @@ -0,0 +1,20 @@ +package com.sidpatchy.clairebot.Lang.Beans.ClaireLang; + +import com.sidpatchy.clairebot.Lang.Beans.ClaireLang.Embed.Embed; + +/** + * Hello future sufferers. I'm glad we're in this together :) + *

+ * Here is the commit message that was written when this structure was devised: + *

+ * "I prefer the idea of this structure, I just don't like the implementation of this structure. + *

+ * Also considered doing one giga-class (technical term :)), but that would've been much less readable than whatever in the fuck this is. + *

+ * Probably would've been easier to understand the intent of though... I will need to write some really good javadoc if I stick with this." + *

+ * IntelliJ is angry at me for using "really good." Too bad! + */ +public class ClaireLang { + public Embed embed; +} diff --git a/src/main/java/com/sidpatchy/clairebot/Lang/Beans/ClaireLang/Embed/Commands/Commands.java b/src/main/java/com/sidpatchy/clairebot/Lang/Beans/ClaireLang/Embed/Commands/Commands.java new file mode 100644 index 0000000..e4d06d5 --- /dev/null +++ b/src/main/java/com/sidpatchy/clairebot/Lang/Beans/ClaireLang/Embed/Commands/Commands.java @@ -0,0 +1,7 @@ +package com.sidpatchy.clairebot.Lang.Beans.ClaireLang.Embed.Commands; + +import com.sidpatchy.clairebot.Lang.Beans.ClaireLang.Embed.Commands.Regular.Regular; + +public class Commands { + public Regular regular; +} diff --git a/src/main/java/com/sidpatchy/clairebot/Lang/Beans/ClaireLang/Embed/Commands/Regular/EightBall.java b/src/main/java/com/sidpatchy/clairebot/Lang/Beans/ClaireLang/Embed/Commands/Regular/EightBall.java new file mode 100644 index 0000000..64fd76d --- /dev/null +++ b/src/main/java/com/sidpatchy/clairebot/Lang/Beans/ClaireLang/Embed/Commands/Regular/EightBall.java @@ -0,0 +1,12 @@ +package com.sidpatchy.clairebot.Lang.Beans.ClaireLang.Embed.Commands.Regular; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.List; + +public class EightBall { + @JsonProperty("8bResponses") + public List eightballResponses; + @JsonProperty("8bRiggedResponses") + public List eightBallRiggedResponses; +} diff --git a/src/main/java/com/sidpatchy/clairebot/Lang/Beans/ClaireLang/Embed/Commands/Regular/Help.java b/src/main/java/com/sidpatchy/clairebot/Lang/Beans/ClaireLang/Embed/Commands/Regular/Help.java new file mode 100644 index 0000000..4aebd42 --- /dev/null +++ b/src/main/java/com/sidpatchy/clairebot/Lang/Beans/ClaireLang/Embed/Commands/Regular/Help.java @@ -0,0 +1,8 @@ +package com.sidpatchy.clairebot.Lang.Beans.ClaireLang.Embed.Commands.Regular; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class Help { + @JsonProperty("Commands") + public String commands; +} diff --git a/src/main/java/com/sidpatchy/clairebot/Lang/Beans/ClaireLang/Embed/Commands/Regular/Info.java b/src/main/java/com/sidpatchy/clairebot/Lang/Beans/ClaireLang/Embed/Commands/Regular/Info.java new file mode 100644 index 0000000..4ea62d4 --- /dev/null +++ b/src/main/java/com/sidpatchy/clairebot/Lang/Beans/ClaireLang/Embed/Commands/Regular/Info.java @@ -0,0 +1,16 @@ +package com.sidpatchy.clairebot.Lang.Beans.ClaireLang.Embed.Commands.Regular; + +public class Info { + private String needHelp; + private String needHelpDetails; + private String addToServer; + private String addToServerDetails; + private String github; + private String githubDetails; + private String serverCount; + private String servercountDetails; + private String version; + private String versionDetails; + private String uptime; + private String uptimeDetails; +} diff --git a/src/main/java/com/sidpatchy/clairebot/Lang/Beans/ClaireLang/Embed/Commands/Regular/Regular.java b/src/main/java/com/sidpatchy/clairebot/Lang/Beans/ClaireLang/Embed/Commands/Regular/Regular.java new file mode 100644 index 0000000..60047f1 --- /dev/null +++ b/src/main/java/com/sidpatchy/clairebot/Lang/Beans/ClaireLang/Embed/Commands/Regular/Regular.java @@ -0,0 +1,9 @@ +package com.sidpatchy.clairebot.Lang.Beans.ClaireLang.Embed.Commands.Regular; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class Regular { + @JsonProperty("AvatarEmbed") + public String avatarEmbed; + +} diff --git a/src/main/java/com/sidpatchy/clairebot/Lang/Beans/ClaireLang/Embed/Embed.java b/src/main/java/com/sidpatchy/clairebot/Lang/Beans/ClaireLang/Embed/Embed.java new file mode 100644 index 0000000..7c5f498 --- /dev/null +++ b/src/main/java/com/sidpatchy/clairebot/Lang/Beans/ClaireLang/Embed/Embed.java @@ -0,0 +1,7 @@ +package com.sidpatchy.clairebot.Lang.Beans.ClaireLang.Embed; + +import com.sidpatchy.clairebot.Commands; + +public class Embed { + public Commands commands; +} diff --git a/src/main/java/com/sidpatchy/clairebot/Util/Lang/LanguageManager.java b/src/main/java/com/sidpatchy/clairebot/Lang/LanguageManager.java similarity index 99% rename from src/main/java/com/sidpatchy/clairebot/Util/Lang/LanguageManager.java rename to src/main/java/com/sidpatchy/clairebot/Lang/LanguageManager.java index 74fd268..c9a14c0 100644 --- a/src/main/java/com/sidpatchy/clairebot/Util/Lang/LanguageManager.java +++ b/src/main/java/com/sidpatchy/clairebot/Lang/LanguageManager.java @@ -1,4 +1,4 @@ -package com.sidpatchy.clairebot.Util.Lang; +package com.sidpatchy.clairebot.Lang; import com.sidpatchy.Robin.File.RobinConfiguration; import com.sidpatchy.clairebot.API.APIUser; diff --git a/src/main/resources/Translations/lang_en-US.yml b/src/main/resources/Translations/lang_en-US.yml index e228255..9cdb5cf 100644 --- a/src/main/resources/Translations/lang_en-US.yml +++ b/src/main/resources/Translations/lang_en-US.yml @@ -45,8 +45,8 @@ version: 1 # Language Keys # -------------------------------------------------------------------------------------------------------------------- # -clairebot: - embed: +ClaireLang: + Embed: Commands: Regular: AvatarEmbed: "" # Unused From 06707f4e68edc0c26389679de63d9072a8bab1e6 Mon Sep 17 00:00:00 2001 From: Sidpatchy <35241490+Sidpatchy@users.noreply.github.com> Date: Tue, 7 May 2024 16:15:39 -0600 Subject: [PATCH 04/64] Some 2 --- build.gradle | 2 +- .../com/sidpatchy/clairebot/Commands.java | 40 +++++++++++++++++++ .../Embed/Commands/Regular/VotingEmbed.java | 4 +- .../resources/Translations/lang_en-US.yml | 24 ++++++++++- src/main/resources/config.yml | 4 ++ src/main/resources/log4j2.xml | 8 ++-- 6 files changed, 73 insertions(+), 9 deletions(-) diff --git a/build.gradle b/build.gradle index 7c3fcd9..35ec04a 100644 --- a/build.gradle +++ b/build.gradle @@ -30,7 +30,7 @@ dependencies { testImplementation 'org.junit.jupiter:junit-jupiter-api:5.10.1' testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.10.1' - implementation 'com.github.Sidpatchy:Robin:2.1.2' + implementation 'com.github.Sidpatchy:Robin:2.1.3' implementation 'org.javacord:javacord:3.8.0' diff --git a/src/main/java/com/sidpatchy/clairebot/Commands.java b/src/main/java/com/sidpatchy/clairebot/Commands.java index c52cb55..9a6666c 100644 --- a/src/main/java/com/sidpatchy/clairebot/Commands.java +++ b/src/main/java/com/sidpatchy/clairebot/Commands.java @@ -2,6 +2,9 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.sidpatchy.Robin.Discord.Command; +import com.sidpatchy.Robin.Discord.CommandBuilder; + +import java.util.Objects; /** * Represents ALL commands within ClaireBot. Initialized using the CommandFactory from Robin >= 2.1.0. @@ -24,57 +27,90 @@ public class Commands { private String configRevision; public Command getAvatar() { + validateCommand(avatar); return avatar; } public Command getConfig() { + validateCommand(config); return config; } public Command getEightball() { + validateCommand(eightball); return eightball; } public Command getHelp() { + validateCommand(help); return help; } public Command getInfo() { + validateCommand(info); return info; } public Command getLeaderboard() { + validateCommand(leaderboard); return leaderboard; } public Command getLevel() { + validateCommand(level); return level; } public Command getPoll() { + validateCommand(poll); return poll; } public Command getQuote() { + validateCommand(quote); return quote; } public Command getRequest() { + validateCommand(request); return request; } public Command getSanta() { + validateCommand(santa); return santa; } public Command getServer() { + validateCommand(server); return server; } public Command getUser() { + validateCommand(user); return user; } + public String getConfigRevision() { + return configRevision.isEmpty() ? null : configRevision; + } + + protected void validateCommand(Command command) { + Objects.requireNonNull(command, "Command cannot be null"); + Objects.requireNonNull(command.getName(), "Command name cannot be null"); + Objects.requireNonNull(command.getUsage(), "Command usage cannot be null"); + Objects.requireNonNull(command.getHelp(), "Command help cannot be null"); + + if (command.getName().isEmpty() || command.getUsage().isEmpty() || command.getHelp().isEmpty()) { + throw new IllegalArgumentException("Command name or usage cannot be empty"); + } + + // If command overview is null, set it to command help + if (command.getOverview() == null || command.getOverview().isEmpty()) { + command.setOverview(command.getHelp()); + } + } + public void setAvatar(Command avatar) { this.avatar = avatar; } @@ -126,4 +162,8 @@ public void setServer(Command server) { public void setUser(Command user) { this.user = user; } + + public void setConfigRevision(String configRevision) { + this.configRevision = configRevision; + } } diff --git a/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/VotingEmbed.java b/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/VotingEmbed.java index d3edca5..d037a85 100755 --- a/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/VotingEmbed.java +++ b/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/VotingEmbed.java @@ -41,7 +41,7 @@ public static EmbedBuilder getPoll(String commandName, String question, String d } } - if (choiceBuilder.toString().equalsIgnoreCase("")) { + if (choiceBuilder.toString().isEmpty()) { allowMultipleChoices = false; } else { @@ -57,7 +57,7 @@ else if (commandName.equalsIgnoreCase("POLL")) { embed.setAuthor(author.getDisplayName(server) + " asks:", "https://discord.com/users/" + author.getIdAsString(), author.getAvatar()); } - if (description.equalsIgnoreCase("")) { + if (description.isEmpty()) { embed.setDescription(question); } else { diff --git a/src/main/resources/Translations/lang_en-US.yml b/src/main/resources/Translations/lang_en-US.yml index 9cdb5cf..559e5f0 100644 --- a/src/main/resources/Translations/lang_en-US.yml +++ b/src/main/resources/Translations/lang_en-US.yml @@ -91,4 +91,26 @@ ClaireLang: VersionDetails: "I am running ClaireBot **v{clairebot.placeholder.version}**, released on **{clairebot.placeholder.releasedate}**" Uptime: "Uptime" UptimeValue: "Started on \n*{clairebot.placeholder.runtimedurationwords}*" - + LeaderboardEmbed: + LeaderboardForServer: "Leaderboard for" + GlobalLeaderboard: "Global Leaderboard" + LevelEmbed: "" # Unused + QuoteEmbed: "" # Unused + SantaEmbed: + Confirmation: "Confirmed! I've sent you a direct message. Please continue there." + RulesButton: "Add rules" + ThemeButton: "Add a theme" + SendButton: "Send messages" + TestButton: "Send Sample" + RandomizeButton: "Re-randomize" + SentByAuthor: "Sent by" + GiverMessage: "Ho! Ho! Ho! You have recieved **{clairebot.placeholder.userid.displayname.server}** in the {clairebot.placeholder.serverid.name} Secret Santa!" + ServerInfoEmbed: + ServerID: "Server ID" + Owner: "Owner" + RoleCount: "Role Count" + MemberCount: "Member Count" + ChannelCounts: "Channel Counts" + Categories: "Categories" + TextChannels: "Text Channels" + VoiceChannels: "Voice Channels" \ No newline at end of file diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 3765056..04d9f34 100755 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -51,6 +51,10 @@ apiPath: https://api.example.com/ apiUser: clairedata apiPassword: examplePassword +# Determines whether failing to connect to the ClaireData API is fatal. This is only checked when the bot is started. +# Recommend leaving enabled unless you are developing ClaireBot & need to bypass this check. +apiFailureIsFatal: true + userDefaults: accentColour: "#3498db" language: "eng" diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml index fe91706..a6a1f8e 100644 --- a/src/main/resources/log4j2.xml +++ b/src/main/resources/log4j2.xml @@ -1,5 +1,5 @@ - + logs clairebot @@ -8,12 +8,10 @@ - + - - @@ -24,4 +22,4 @@ - \ No newline at end of file + From 20ee5296610162c1edb5de790e90169de37307b6 Mon Sep 17 00:00:00 2001 From: Sidpatchy Date: Sat, 17 Aug 2024 17:57:29 -0600 Subject: [PATCH 05/64] Bump deps --- build.gradle | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/build.gradle b/build.gradle index 35ec04a..a816f51 100644 --- a/build.gradle +++ b/build.gradle @@ -27,26 +27,26 @@ repositories { } dependencies { - testImplementation 'org.junit.jupiter:junit-jupiter-api:5.10.1' - testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.10.1' + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.10.3' + testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.10.2' implementation 'com.github.Sidpatchy:Robin:2.1.3' implementation 'org.javacord:javacord:3.8.0' - implementation 'org.apache.logging.log4j:log4j-api:2.22.1' - implementation 'org.apache.logging.log4j:log4j-core:2.22.1' - implementation 'org.apache.logging.log4j:log4j-slf4j-impl:2.22.1' - implementation 'org.apache.commons:commons-lang3:3.14.0' + implementation 'org.apache.logging.log4j:log4j-api:2.23.1' + implementation 'org.apache.logging.log4j:log4j-core:2.23.1' + implementation 'org.apache.logging.log4j:log4j-slf4j-impl:2.23.1' + implementation 'org.apache.commons:commons-lang3:3.15.0' - implementation 'org.jetbrains.kotlin:kotlin-stdlib:1.9.22' + implementation 'org.jetbrains.kotlin:kotlin-stdlib:2.0.0' // How many file processing libraries can we have?!?! implementation 'org.yaml:snakeyaml:2.0' implementation 'org.json:json:20231013' - implementation 'com.fasterxml.jackson.core:jackson-databind:2.16.1' - implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.16.1' - implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.16.1' + implementation 'com.fasterxml.jackson.core:jackson-databind:2.17.0' + implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.17.1' + implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.17.2' } From 852c87c36e1dc65a4fa034cf215e7d5987e71a02 Mon Sep 17 00:00:00 2001 From: Sidpatchy Date: Thu, 22 Aug 2024 19:40:51 -0600 Subject: [PATCH 06/64] Update lang_en-US.yml --- src/main/resources/Translations/lang_en-US.yml | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/main/resources/Translations/lang_en-US.yml b/src/main/resources/Translations/lang_en-US.yml index 559e5f0..3c4f76f 100644 --- a/src/main/resources/Translations/lang_en-US.yml +++ b/src/main/resources/Translations/lang_en-US.yml @@ -113,4 +113,19 @@ ClaireLang: ChannelCounts: "Channel Counts" Categories: "Categories" TextChannels: "Text Channels" - VoiceChannels: "Voice Channels" \ No newline at end of file + VoiceChannels: "Voice Channels" + ServerPreferencesEmbed: + MainMenuTitle: "Server Configuration Editor" + NotAServer: "You must run this command inside a server!" + RequestsChannelMenuName: "Requests Channel" + RequestsChannelDescription: "Only lists the first 25 channels in the server." + ModeratorChannelMenuName: "Moderator Messages Channel" + ModeratorChannelDescription: "Only lists the first 25 channels in the server." + EnforceServerLanguageMenuName: "Enforce Server Language" + AcknowledgeRequestsChannelChangeTitle: "Requests Channel Changed!" + AcknowledgeRequestsChannelChangeDescription: "Your requests channel has been changed to {clairebot.placeholder.channel.id.mentiontag}" + AcknowledgeModeratorChannelChangeTitle: "Moderator Channel Changed!" + AcknowledgeModeratorChannelChangeDescription: "Your moderator messages channel has been changed to {clairebot.placeholder.channel.id.mentiontag}" + AcknowledgeEnforceServerLanguageUpdateTitle: "Server Language Preferences Updated!" + AcknowledgeEnforceServerLanguageUpdateEnforced: "I will now follow the server's language regardless of user preference." + AcknowledgeEnforceServerLanguageUpdateNotEnforced: "I will follow each individual user's language preference." From ca35404957a8ebb6e1218eca3370d7472b15ab89 Mon Sep 17 00:00:00 2001 From: Sidpatchy <35241490+Sidpatchy@users.noreply.github.com> Date: Fri, 20 Dec 2024 16:50:46 -0700 Subject: [PATCH 07/64] Progress --- build.gradle | 2 +- .../clairebot/Lang/ContextManager.kt | 44 +++++++ .../clairebot/Lang/PlaceholderHandler.java | 113 ++++++++++++++++++ .../java/com/sidpatchy/clairebot/Main.java | 11 +- .../resources/Translations/lang_en-US.yml | 34 +++++- 5 files changed, 196 insertions(+), 8 deletions(-) create mode 100644 src/main/java/com/sidpatchy/clairebot/Lang/ContextManager.kt create mode 100644 src/main/java/com/sidpatchy/clairebot/Lang/PlaceholderHandler.java diff --git a/build.gradle b/build.gradle index 536d095..789261d 100644 --- a/build.gradle +++ b/build.gradle @@ -27,7 +27,7 @@ dependencies { testImplementation 'org.junit.jupiter:junit-jupiter-api:5.10.1' testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.10.1' - implementation 'com.github.Sidpatchy:Robin:2.0.0' + implementation 'com.github.Sidpatchy:Robin:2.1.3' implementation 'org.javacord:javacord:3.8.0' diff --git a/src/main/java/com/sidpatchy/clairebot/Lang/ContextManager.kt b/src/main/java/com/sidpatchy/clairebot/Lang/ContextManager.kt new file mode 100644 index 0000000..52d33f0 --- /dev/null +++ b/src/main/java/com/sidpatchy/clairebot/Lang/ContextManager.kt @@ -0,0 +1,44 @@ +package com.sidpatchy.clairebot.Lang + +import org.javacord.api.entity.channel.TextChannel +import org.javacord.api.entity.message.Message +import org.javacord.api.entity.server.Server +import org.javacord.api.entity.user.User + +data class ContextManager( + val server: Server?, + val channel: TextChannel?, + val author: User?, + val user: User?, + val message: Message?, + private val dynamicData: MutableMap = mutableMapOf() +) { + // Enum to define known context types + enum class ContextType { + POLL, + SANTA, + // Add other types as needed + } + + // Add dynamic data with type safety + fun addData(type: ContextType, key: String, value: Any) { + dynamicData["${type.name.lowercase()}.$key"] = value + } + + // Get dynamic data with type safety + @Suppress("UNCHECKED_CAST") + fun getData(type: ContextType, key: String): T? { + return dynamicData["${type.name.lowercase()}.$key"] as? T + } + + // Helper functions for specific context types + fun addPollData(pollId: String, question: String) { + addData(ContextType.POLL, "id", pollId) + addData(ContextType.POLL, "question", question) + } + + fun addSantaData(giftee: User, theme: String) { + addData(ContextType.SANTA, "giftee", giftee) + addData(ContextType.SANTA, "theme", theme) + } +} \ No newline at end of file diff --git a/src/main/java/com/sidpatchy/clairebot/Lang/PlaceholderHandler.java b/src/main/java/com/sidpatchy/clairebot/Lang/PlaceholderHandler.java new file mode 100644 index 0000000..52a9bc8 --- /dev/null +++ b/src/main/java/com/sidpatchy/clairebot/Lang/PlaceholderHandler.java @@ -0,0 +1,113 @@ +package com.sidpatchy.clairebot.Lang; + +import org.javacord.api.entity.channel.Channel; +import org.javacord.api.entity.channel.ServerChannel; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class PlaceholderHandler { + private final ContextManager context; + private final Map placeholders; + + // Functional interface for placeholder value providers + @FunctionalInterface + private interface PlaceholderProvider { + String getValue(); + } + + public PlaceholderHandler(ContextManager context) { + this.context = context; + this.placeholders = initializePlaceholders(); + } + + private Map initializePlaceholders() { + Map map = new HashMap<>(); + + // Server placeholders + map.put("cb.server.name", () -> + context.getServer() != null ? context.getServer().getName() : ""); + map.put("cb.server.id", () -> + context.getServer() != null ? context.getServer().getIdAsString() : ""); + + // User placeholders + map.put("cb.user.name", () -> + context.getUser() != null ? context.getUser().getName() : ""); + map.put("cb.user.id", () -> + context.getUser() != null ? context.getUser().getIdAsString() : ""); + + // Author placeholders + map.put("cb.author.name", () -> + context.getAuthor() != null ? context.getAuthor().getName() : ""); + map.put("cb.author.id", () -> + context.getAuthor() != null ? context.getAuthor().getIdAsString() : ""); + + // Channel placeholders + map.put("cb.channel.name", () -> + Optional.ofNullable(context.getChannel()) + .flatMap(Channel::asServerChannel) + .map(ServerChannel::getName) + .orElse("NOT FOUND")); + map.put("cb.channel.id", () -> + context.getChannel() != null ? context.getChannel().getIdAsString() : ""); + + return Collections.unmodifiableMap(map); + } + + /** + * Process a string containing placeholders + * @param input String containing placeholders in format {cb.placeholder.name} + * @return Processed string with placeholders replaced with their values + */ + public String process(String input) { + if (input == null || input.isEmpty()) { + return input; + } + + Pattern pattern = Pattern.compile("\\{([^}]+)\\}"); + Matcher matcher = pattern.matcher(input); + StringBuffer result = new StringBuffer(); + + while (matcher.find()) { + String placeholder = matcher.group(1); + String replacement = getPlaceholderValue(placeholder); + // Quote the replacement string to handle special regex characters + matcher.appendReplacement(result, Matcher.quoteReplacement(replacement)); + } + matcher.appendTail(result); + + return result.toString(); + } + + /** + * Get the value for a specific placeholder + * @param key The placeholder key (without {} brackets) + * @return The placeholder value or the original key if not found + */ + public String getPlaceholderValue(String key) { + PlaceholderProvider provider = placeholders.get(key); + return provider != null ? provider.getValue() : key; + } + + /** + * Check if a placeholder exists + * @param key The placeholder key (without {} brackets) + * @return true if the placeholder exists + */ + public boolean hasPlaceholder(String key) { + return placeholders.containsKey(key); + } + + /** + * Add a custom placeholder + * @param key The placeholder key (without {} brackets) + * @param provider The provider function that returns the placeholder value + */ + public void addPlaceholder(String key, PlaceholderProvider provider) { + placeholders.put(key, provider); + } +} diff --git a/src/main/java/com/sidpatchy/clairebot/Main.java b/src/main/java/com/sidpatchy/clairebot/Main.java index ce51dd7..9d425eb 100644 --- a/src/main/java/com/sidpatchy/clairebot/Main.java +++ b/src/main/java/com/sidpatchy/clairebot/Main.java @@ -38,7 +38,7 @@ * along with this program. If not, see . * * @since April 2020 - * @version 3.3.2 + * @version 3.4.0-SNAPSHOT * @author Sidpatchy */ public class Main { @@ -118,7 +118,7 @@ public static void main(String[] args) throws InvalidConfigurationException { Clockwork.initClockwork(); // Set the bot's activity - api.updateActivity("ClaireBot v3.3.2", video_url); + api.updateActivity("ClaireBot v3.4.0-SNAPSHOT", video_url); // Register slash commands registerSlashCommands(); @@ -140,7 +140,7 @@ public static void main(String[] args) throws InvalidConfigurationException { // Connect to Discord and create an API object private static DiscordApi DiscordLogin(String token, Integer current_shard, Integer total_shards) { - if (token == null || token.equals("")) { + if (token == null || token.isEmpty()) { logger.fatal("Token can't be null or empty. Check your config file!"); System.exit(1); } @@ -160,14 +160,13 @@ else if (current_shard == null || total_shards == null) { .login().join(); } catch (Exception e) { - e.printStackTrace(); - logger.fatal(e.toString()); - logger.fatal("Unable to log in to Discord. Aborting startup!"); + logger.fatal("Unable to log in to Discord. Aborting startup!", e); } return null; } // Extract parameters from the config.yml file, update the config if applicable. + // todo stop using Robin for this. Switch to standard Java classses. @SuppressWarnings("unchecked") public static void extractParametersFromConfig(boolean updateOutdatedConfigs) { logger.info("Loading configuration files..."); diff --git a/src/main/resources/Translations/lang_en-US.yml b/src/main/resources/Translations/lang_en-US.yml index 3c4f76f..9d9c651 100644 --- a/src/main/resources/Translations/lang_en-US.yml +++ b/src/main/resources/Translations/lang_en-US.yml @@ -104,7 +104,7 @@ ClaireLang: TestButton: "Send Sample" RandomizeButton: "Re-randomize" SentByAuthor: "Sent by" - GiverMessage: "Ho! Ho! Ho! You have recieved **{clairebot.placeholder.userid.displayname.server}** in the {clairebot.placeholder.serverid.name} Secret Santa!" + GiverMessage: "Ho! Ho! Ho! You have recieved **{clairebot.placeholder.user.id.displayname.server}** in the {clairebot.placeholder.serverid.name} Secret Santa!" ServerInfoEmbed: ServerID: "Server ID" Owner: "Owner" @@ -129,3 +129,35 @@ ClaireLang: AcknowledgeEnforceServerLanguageUpdateTitle: "Server Language Preferences Updated!" AcknowledgeEnforceServerLanguageUpdateEnforced: "I will now follow the server's language regardless of user preference." AcknowledgeEnforceServerLanguageUpdateNotEnforced: "I will follow each individual user's language preference." + UserInfoEmbed: + Error_1: "The value for author was null when passed into UserInfo Embed. Error code: {clairebot.placeholder.errorcode}" + Error_2: "Author: {clairebot.placeholder.user.id.username}" + User: "User" + DiscordID: "Discord ID" + JoinDate: "Server Join Date" + CreationDate: "Account Creation Date" + UserPreferencesEmbed: + MainMenuText: "User Preferences Editor" + AccentColourMenu: "Accent Colour Editor" + AccentColourList: "Accent Colour List" + AccentColourChanged: "Acccent Colour Changed!" + AccentColourChangedDesc: "Your accent colour has been changed to {clairebot.placeholder.user.id.accentcolour}" + LanguageMenuTitle: "NYI" + LanguageMenuDesc: "Sorry, this feature is not yet implemented." + VotingEmbed: + PollRequest: "{clairebot.placeholder.user.id.displayname.server} requests:" + PollAsk: "{clairebot.placeholder.user.id.displayname.server} asks:" + Choices: "Choices" + PollID: "Poll ID: {clairebot.placeholder.poll.id}" + UserResponseTitle: "Your request has been created!" + UserResponseDescription: "Go check it out in {clairebot.placeholder.channel.id.mentiontag}" + ErrorEmbed: + Error: "ERROR" + GenericDescription: "It appears that I've encountered an error, oops! Please try running the command once more and if that doesn't work, join my [Discord server]({cb.supportserver) and let us know about the issue.\n\nPlease include the following error code: {cb.errorcode}" + WelcomeEmbed: + Title: "🎉 Welcome to ClaireBot 3!" + Motto: "How can you rise, if you have not burned?" + UsageTitle: "Usage" + UsageDesc: "Get started by running `{cb.command.help.name}`. Need more info on a command? Run `/{cb.command.help.name} ` (ex. `/{cb.command.help.name} user`)" + SupportTitle: "Get Support" + SupportDesc: "You can get help on our [Discord]({cb.supportserver}), or by opening an issue on [GitHub]({cb.github})" \ No newline at end of file From 6d8b80332078833db4b870c83f694e689077ef10 Mon Sep 17 00:00:00 2001 From: Sidpatchy <35241490+Sidpatchy@users.noreply.github.com> Date: Fri, 20 Dec 2024 16:57:00 -0700 Subject: [PATCH 08/64] Make Kotlin use Java 17 --- build.gradle | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/build.gradle b/build.gradle index 789261d..36bde19 100644 --- a/build.gradle +++ b/build.gradle @@ -44,6 +44,10 @@ dependencies { } +kotlin { + jvmToolchain(sourceCompatibility) +} + test { useJUnitPlatform() } \ No newline at end of file From eaf86500da63b68d23d5cdc7a081d793f16d55e6 Mon Sep 17 00:00:00 2001 From: Sidpatchy <35241490+Sidpatchy@users.noreply.github.com> Date: Fri, 20 Dec 2024 17:00:18 -0700 Subject: [PATCH 09/64] halp, I no know how read docs --- build.gradle | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/build.gradle b/build.gradle index 36bde19..df7cbcf 100644 --- a/build.gradle +++ b/build.gradle @@ -14,8 +14,9 @@ jar { group 'com.sidpatchy' version '3.3.3' -sourceCompatibility = 17 -targetCompatibility = 17 +kotlin { + jvmToolchain(17) +} repositories { mavenCentral() @@ -44,10 +45,6 @@ dependencies { } -kotlin { - jvmToolchain(sourceCompatibility) -} - test { useJUnitPlatform() } \ No newline at end of file From a1d77040894ccf3c49b2c41aedd08a55f5511494 Mon Sep 17 00:00:00 2001 From: Sidpatchy <35241490+Sidpatchy@users.noreply.github.com> Date: Fri, 14 Feb 2025 15:06:48 -0700 Subject: [PATCH 10/64] IT'S HAPPENING This is the first vaguely working implementation of the language system. /8ball is the only implemented command here. Nothing else is tested at this point because it's not been worked on. --- build.gradle | 5 +- .../Commands/Regular/EightBallEmbed.java | 19 +- .../clairebot/Lang/LanguageManager.java | 187 ++++++++++-------- .../clairebot/Lang/PlaceholderHandler.java | 65 +++--- .../Listener/SlashCommandCreate.java | 45 +++-- .../java/com/sidpatchy/clairebot/Main.java | 1 + .../clairebot/RegisterSlashCommands.java | 28 +-- src/main/resources/commands.yml | 4 +- src/main/resources/config.yml | 85 +------- .../lang_TEMPLATE.yml | 0 .../lang_en-US.yml | 50 +++++ 11 files changed, 251 insertions(+), 238 deletions(-) rename src/main/resources/{Translations => translations}/lang_TEMPLATE.yml (100%) rename src/main/resources/{Translations => translations}/lang_en-US.yml (81%) diff --git a/build.gradle b/build.gradle index df7cbcf..249c405 100644 --- a/build.gradle +++ b/build.gradle @@ -12,13 +12,14 @@ jar { } group 'com.sidpatchy' -version '3.3.3' +version '3.4.0' kotlin { jvmToolchain(17) } repositories { + mavenLocal() mavenCentral() maven { url 'https://m2.dv8tion.net/releases' } maven { url 'https://jitpack.io' } @@ -28,7 +29,7 @@ dependencies { testImplementation 'org.junit.jupiter:junit-jupiter-api:5.10.1' testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.10.1' - implementation 'com.github.Sidpatchy:Robin:2.1.3' + implementation 'com.sidpatchy:Robin:2.2.1:all' implementation 'org.javacord:javacord:3.8.0' diff --git a/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/EightBallEmbed.java b/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/EightBallEmbed.java index 3d0a108..7a6d91f 100644 --- a/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/EightBallEmbed.java +++ b/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/EightBallEmbed.java @@ -1,34 +1,39 @@ package com.sidpatchy.clairebot.Embed.Commands.Regular; +import com.sidpatchy.clairebot.Lang.LanguageManager; import com.sidpatchy.clairebot.Main; import org.javacord.api.entity.message.embed.EmbedBuilder; import org.javacord.api.entity.user.User; import java.util.ArrayList; +import java.util.List; import java.util.Random; public class EightBallEmbed { - public static EmbedBuilder getEightBall(String query, User author) { + public static EmbedBuilder getEightBall(LanguageManager languageManager, String query, User author) { - ArrayList eightBall = (ArrayList) Main.getEightBall(); - ArrayList eightBallRigged = (ArrayList) Main.getEightBallRigged(); - ArrayList onTopTriggers = (ArrayList) Main.getOnTopTriggers(); + List eightBall = languageManager.getLocalizedList("ClaireLang.Embed.Commands.Regular.EightBallEmbed.8bResponses"); + List eightBallRigged = languageManager.getLocalizedList("ClaireLang.Embed.Commands.Regular.EightBallEmbed.8bRiggedResponses"); + List onTopTriggers = languageManager.getLocalizedList("ClaireLang.Embed.Commands.Regular.EightBallEmbed.OnTopTriggers"); + String ateBallLanguageString = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Regular.EightBallEmbed.8ball"); + + Main.getLogger().error(onTopTriggers.toString()); Random random = new Random(); int rand = random.nextInt(eightBall.size()); String response = eightBall.get(rand); // Overwrite response if ClaireBot on top trigger - for (String s : onTopTriggers) { - if (query.toUpperCase().contains(s.toUpperCase())) { + for (String trigger : onTopTriggers) { + if (query.toUpperCase().contains(trigger.toUpperCase())) { rand = random.nextInt(eightBallRigged.size()); response = eightBallRigged.get(rand); } } return new EmbedBuilder() .setColor(Main.getColor(author.getIdAsString())) - .setAuthor("8ball") + .setAuthor(ateBallLanguageString) .addField(query, response) .setFooter(author.getDiscriminatedName(), author.getAvatar()); } diff --git a/src/main/java/com/sidpatchy/clairebot/Lang/LanguageManager.java b/src/main/java/com/sidpatchy/clairebot/Lang/LanguageManager.java index c9a14c0..bbf8f2e 100644 --- a/src/main/java/com/sidpatchy/clairebot/Lang/LanguageManager.java +++ b/src/main/java/com/sidpatchy/clairebot/Lang/LanguageManager.java @@ -1,5 +1,6 @@ package com.sidpatchy.clairebot.Lang; +import com.sidpatchy.Robin.Exception.InvalidConfigurationException; import com.sidpatchy.Robin.File.RobinConfiguration; import com.sidpatchy.clairebot.API.APIUser; import com.sidpatchy.clairebot.API.Guild; @@ -10,6 +11,7 @@ import java.io.File; import java.io.IOException; +import java.util.List; import java.util.Locale; public class LanguageManager { @@ -20,81 +22,65 @@ public class LanguageManager { private final Locale fallbackLocale; private final Server server; private final User user; - - /** - * The LanguageManager class is responsible for loading and managing language files based on user preferences. - * It provides methods to retrieve localized strings and get the language file based on the user's preferred language. - *

- * If the language manager is NOT being used in the context of a Server, but instead is being used in direct - * messages or similar, server must be set to null. There is another constructor signature that handles this, - * but it is best to avoid whenever possible. - */ - public LanguageManager(String pathToLanguageFiles, - Locale fallbackLocaleString, - Server server, - User user) { - this.pathToLanguageFiles = pathToLanguageFiles; - this.fallbackLocale = fallbackLocaleString; - this.server = server; - this.user = user; - } + private final ContextManager context; + private final PlaceholderHandler placeholderHandler; /** * The LanguageManager class is responsible for loading and managing language files based on user preferences. * It provides methods to retrieve localized strings and get the language file based on the user's preferred language. *

* If the language manager is being used in the context of a Server, a server MUST be specified in order to conform - * to the ClaireLang and ClaireConfig specifications. There is another constructor signature that handles this. It - * is safe to pass a null value for the server as ClaireLang will automatically interpret this as there being no - * server. + * to the ClaireLang and ClaireConfig specifications. It is safe to pass a null value for the server via + * ContextManager as ClaireLang will automatically interpret this as there being no server. */ public LanguageManager(String pathToLanguageFiles, - Locale fallbackLocaleString, - User user) { - this.pathToLanguageFiles = pathToLanguageFiles; - this.fallbackLocale = fallbackLocaleString; - this.server = null; - this.user = user; + Locale fallbackLocale, + ContextManager context) { + this.pathToLanguageFiles = Main.getTranslationsPath(); + this.context = context; + this.fallbackLocale = fallbackLocale; + + this.server = context.getServer(); + this.user = context.getUser(); + this.placeholderHandler = new PlaceholderHandler(context); } /** * The LanguageManager class is responsible for loading and managing language files based on user preferences. * It provides methods to retrieve localized strings and get the language file based on the user's preferred language. *

- * If the language manager is NOT being used in the context of a Server, but instead is being used in direct - * messages or similar, server must be set to null. There is another constructor signature that handles this, - * but it is best to avoid whenever possible. + * If the language manager is being used in the context of a Server, a server MUST be specified in order to conform + * to the ClaireLang and ClaireConfig specifications. It is safe to pass a null value for the server via + * ContextManager as ClaireLang will automatically interpret this as there being no server. *

- * This signature should only be used by ClaireBot. If you are developing a plugin, you must specify a different - * locale path. The standard path can be obtained through the plugin API. + * This signature should only be used by ClaireBot. If you are developing a plugin, you must specify a different + * locale path unless you are referencing ClaireBot's builtin language strings. The standard path can be obtained + * through the plugin API. */ - public LanguageManager(Locale fallbackLocaleString, - Server server, - User user) { + public LanguageManager(Locale fallbackLocale, ContextManager context) { this.pathToLanguageFiles = Main.getTranslationsPath(); - this.fallbackLocale = fallbackLocaleString; - this.server = server; - this.user = user; + this.context = context; + this.fallbackLocale = fallbackLocale; + + this.server = context.getServer(); + this.user = context.getUser(); + this.placeholderHandler = new PlaceholderHandler(context); } /** - * The LanguageManager class is responsible for loading and managing language files based on user preferences. - * It provides methods to retrieve localized strings and get the language file based on the user's preferred language. - *

- * If the language manager is being used in the context of a Server, a server MUST be specified in order to conform - * to the ClaireLang and ClaireConfig specifications. There is another constructor signature that handles this. It - * is safe to pass a null value for the server as ClaireLang will automatically interpret this as there being no - * server. - *

- * This signature should only be used by ClaireBot. If you are developing a plugin, you must specify a different - * locale path. The standard path can be obtained through the plugin API. + * Retrieves the localized string corresponding to the given key. + * + * @param key the key for the desired localized string + * @return the localized string if found, otherwise returns the key itself + * @throws IOException if an I/O error occurs while retrieving the localized string */ - public LanguageManager(Locale fallbackLocaleString, - User user) { - this.pathToLanguageFiles = Main.getTranslationsPath(); - this.fallbackLocale = fallbackLocaleString; - this.server = null; - this.user = user; + public String getLocalizedString(String key) { + RobinConfiguration languageFile = parseUserAndServerOptions(server, user); + String localizedString = languageFile.getString(key); + logger.debug(localizedString); + String rawLanguageString = localizedString != null ? localizedString : key; + + return placeholderHandler.process(rawLanguageString); } /** @@ -104,30 +90,46 @@ public LanguageManager(Locale fallbackLocaleString, * @return the localized string if found, otherwise returns the key itself * @throws IOException if an I/O error occurs while retrieving the localized string */ - // todo consider handling the exception in this method, or in a different method signature. - public String getLocalizedString(String key) throws IOException { - APIUser apiUser = new APIUser(user.getIdAsString()); - apiUser.getUser(); - Locale locale = Locale.forLanguageTag(apiUser.getLanguage()); - - // todo, pending ClaireData update: allow server admins to specify a custom language string. - // todo ref https://trello.com/c/vkQTCTMG - if (server != null) { - Guild guild = new Guild(server.getIdAsString()); - guild.getGuild(); - - if (guild.isEnforceSeverLanguage()) { - // todo this should not be determined here, but will be until the ClaireData implementation is completed. - // todo this should instead be determined when the Guild object is created in the database. - // todo ClaireData update on hold while still designing the major ClaireBot update that follows this one. - locale = server.getPreferredLocale(); + public List getLocalizedList(String key) { + RobinConfiguration languageFile = parseUserAndServerOptions(server, user); + List localizedList = languageFile.getList(key); + logger.debug(localizedList); + List rawLanguageString = localizedList != null ? localizedList.stream() + .map(Object::toString) + .toList() + : List.of(key); + + logger.warn(rawLanguageString); + + return placeholderHandler.process(rawLanguageString); + } + + private RobinConfiguration parseUserAndServerOptions(Server server, User user) { + Locale locale = null; + try { + APIUser apiUser = new APIUser(user.getIdAsString()); + apiUser.getUser(); + locale = Locale.forLanguageTag(apiUser.getLanguage()); + + // todo, pending ClaireData update: allow server admins to specify a custom language string. + // todo ref https://trello.com/c/vkQTCTMG + if (server != null) { + Guild guild = new Guild(server.getIdAsString()); + guild.getGuild(); + + if (guild.isEnforceSeverLanguage()) { + // todo this should not be determined here, but will be until the ClaireData implementation is completed. + // todo this should instead be determined when the Guild object is created in the database. + // todo ClaireData update on hold while still designing the major ClaireBot update that follows this one. + locale = server.getPreferredLocale(); + } } + } catch (IOException e) { + logger.error("ClaireData failed to return a response for Locale information. Are we cooked?"); + locale = fallbackLocale; } - RobinConfiguration languageFile = getLangFileByLocale(locale); - String localizedString = languageFile.getString(key); - logger.debug(localizedString); - return localizedString != null ? localizedString : key; + return getLangFileByLocale(locale); } /** @@ -138,12 +140,37 @@ public String getLocalizedString(String key) throws IOException { * @return Returns a localized language file or the fallback file if a suitable translation doesn't exist. */ public RobinConfiguration getLangFileByLocale(Locale locale) { - File file = new File(pathToLanguageFiles, "lang_" + locale.toLanguageTag() + ".yml"); - if (file.exists()) { - return new RobinConfiguration(pathToLanguageFiles + "lang_" + locale.toLanguageTag() + ".yml"); + File targetFile = new File(pathToLanguageFiles, "lang_" + locale.toLanguageTag() + ".yml"); + File fallbackFile = new File(pathToLanguageFiles, "lang_" + fallbackLocale.toLanguageTag() + ".yml"); + + // Try primary file + RobinConfiguration config = tryLoadConfig(targetFile); + if (config != null) { + return config; + } + + // Try fallback file + config = tryLoadConfig(fallbackFile); + if (config != null) { + logger.warn("Using fallback language file for locale: {}", locale); + return config; } - else { - return new RobinConfiguration(pathToLanguageFiles + "lang_" + fallbackLocale.toLanguageTag() + ".yml"); + + // Ultimate fallback - empty config + logger.error("All language files failed to load! Using empty configuration."); + return new RobinConfiguration(); + } + + private RobinConfiguration tryLoadConfig(File file) { + try { + RobinConfiguration config = new RobinConfiguration(file.getAbsolutePath()); + config.load(); + return config; + } catch (InvalidConfigurationException e) { + logger.error("Failed to load language file {}: {}", file, e.getMessage()); + return null; } } + + } diff --git a/src/main/java/com/sidpatchy/clairebot/Lang/PlaceholderHandler.java b/src/main/java/com/sidpatchy/clairebot/Lang/PlaceholderHandler.java index 52a9bc8..dd6c854 100644 --- a/src/main/java/com/sidpatchy/clairebot/Lang/PlaceholderHandler.java +++ b/src/main/java/com/sidpatchy/clairebot/Lang/PlaceholderHandler.java @@ -3,10 +3,7 @@ import org.javacord.api.entity.channel.Channel; import org.javacord.api.entity.channel.ServerChannel; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; +import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -26,36 +23,30 @@ public PlaceholderHandler(ContextManager context) { } private Map initializePlaceholders() { - Map map = new HashMap<>(); - - // Server placeholders - map.put("cb.server.name", () -> - context.getServer() != null ? context.getServer().getName() : ""); - map.put("cb.server.id", () -> - context.getServer() != null ? context.getServer().getIdAsString() : ""); - - // User placeholders - map.put("cb.user.name", () -> - context.getUser() != null ? context.getUser().getName() : ""); - map.put("cb.user.id", () -> - context.getUser() != null ? context.getUser().getIdAsString() : ""); - - // Author placeholders - map.put("cb.author.name", () -> - context.getAuthor() != null ? context.getAuthor().getName() : ""); - map.put("cb.author.id", () -> - context.getAuthor() != null ? context.getAuthor().getIdAsString() : ""); - - // Channel placeholders - map.put("cb.channel.name", () -> - Optional.ofNullable(context.getChannel()) - .flatMap(Channel::asServerChannel) - .map(ServerChannel::getName) - .orElse("NOT FOUND")); - map.put("cb.channel.id", () -> - context.getChannel() != null ? context.getChannel().getIdAsString() : ""); - - return Collections.unmodifiableMap(map); + + return Map.of( + // Server placeholders + "cb.server.name", () -> + context.getServer() != null ? context.getServer().getName() : "", "cb.server.id", () -> + context.getServer() != null ? context.getServer().getIdAsString() : "", + + // User placeholders + "cb.user.name", () -> + context.getUser() != null ? context.getUser().getName() : "", "cb.user.id", () -> + context.getUser() != null ? context.getUser().getIdAsString() : "", + + // Author placeholders + "cb.author.name", () -> + context.getAuthor() != null ? context.getAuthor().getName() : "", "cb.author.id", () -> + context.getAuthor() != null ? context.getAuthor().getIdAsString() : "", + + // Channel placeholders + "cb.channel.name", () -> + Optional.ofNullable(context.getChannel()) + .flatMap(Channel::asServerChannel) + .map(ServerChannel::getName) + .orElse("NOT FOUND"), "cb.channel.id", () -> + context.getChannel() != null ? context.getChannel().getIdAsString() : ""); } /** @@ -83,6 +74,12 @@ public String process(String input) { return result.toString(); } + public List process(List input) { + return input.stream() + .map(this::process) + .toList(); + } + /** * Get the value for a specific placeholder * @param key The placeholder key (without {} brackets) diff --git a/src/main/java/com/sidpatchy/clairebot/Listener/SlashCommandCreate.java b/src/main/java/com/sidpatchy/clairebot/Listener/SlashCommandCreate.java index 56f2bac..dbb39ea 100644 --- a/src/main/java/com/sidpatchy/clairebot/Listener/SlashCommandCreate.java +++ b/src/main/java/com/sidpatchy/clairebot/Listener/SlashCommandCreate.java @@ -1,8 +1,11 @@ package com.sidpatchy.clairebot.Listener; import com.sidpatchy.Robin.Discord.ParseCommands; +import com.sidpatchy.clairebot.Commands; import com.sidpatchy.clairebot.Embed.Commands.Regular.*; import com.sidpatchy.clairebot.Embed.ErrorEmbed; +import com.sidpatchy.clairebot.Lang.ContextManager; +import com.sidpatchy.clairebot.Lang.LanguageManager; import com.sidpatchy.clairebot.Main; import com.sidpatchy.clairebot.MessageComponents.Regular.ServerPreferencesComponents; import com.sidpatchy.clairebot.MessageComponents.Regular.UserPreferencesComponents; @@ -10,6 +13,7 @@ import com.sidpatchy.clairebot.Util.ChannelUtils; import org.apache.logging.log4j.Logger; import org.javacord.api.entity.channel.TextChannel; +import org.javacord.api.entity.message.Message; import org.javacord.api.entity.message.MessageFlag; import org.javacord.api.entity.message.component.ActionRow; import org.javacord.api.entity.message.component.Button; @@ -24,13 +28,16 @@ import java.io.FileNotFoundException; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Locale; import java.util.concurrent.CompletableFuture; public class SlashCommandCreate implements SlashCommandCreateListener { - static ParseCommands parseCommands = new ParseCommands(Main.getCommandsFile()); - Logger logger = Main.getLogger(); + private final Logger logger = Main.getLogger(); + private static final Commands commands = Main.getCommands(); + private LanguageManager languageManager; @Override public void onSlashCommandCreate(SlashCommandCreateEvent event) { @@ -39,8 +46,14 @@ public void onSlashCommandCreate(SlashCommandCreateEvent event) { String commandName = slashCommandInteraction.getCommandName(); User author = slashCommandInteraction.getUser(); User user = slashCommandInteraction.getArgumentUserValueByName("user").orElse(author); + TextChannel textchannel = slashCommandInteraction.getChannel().orElse(null); - if (commandName.equalsIgnoreCase(parseCommands.getCommandName("8ball"))) { + ContextManager context = new ContextManager(server, textchannel, author, user, null, new HashMap<>()); + + // Todo replace reference to en-US with config file parameter + languageManager = new LanguageManager(Locale.forLanguageTag("en-US"), context); + + if (commandName.equalsIgnoreCase(commands.getEightball().getName())) { String query = slashCommandInteraction.getArgumentStringValueByIndex(0).orElse(null); if (query == null) { @@ -51,20 +64,20 @@ public void onSlashCommandCreate(SlashCommandCreateEvent event) { future.thenAccept(interactionResponse -> { try { - interactionResponse.addEmbed(EightBallEmbed.getEightBall(query, author)); + interactionResponse.addEmbed(EightBallEmbed.getEightBall(languageManager, query, author)); interactionResponse.update(); } catch (Exception e) { e.printStackTrace(); } }); } - else if (commandName.equalsIgnoreCase(parseCommands.getCommandName("avatar"))) { + else if (commandName.equalsIgnoreCase(commands.getAvatar().getName())) { boolean getGlobalAvatar = slashCommandInteraction.getArgumentBooleanValueByName("globalAvatar").orElse(true); slashCommandInteraction.createImmediateResponder() .addEmbed(AvatarEmbed.getAvatar(server, user, author, getGlobalAvatar)) .respond(); } - else if (commandName.equalsIgnoreCase(parseCommands.getCommandName("config"))) { + else if (commandName.equalsIgnoreCase(commands.getConfig().getName())) { String mode = slashCommandInteraction.getArgumentStringValueByName("mode").orElse("user"); if (mode.equalsIgnoreCase("user")) { @@ -91,7 +104,7 @@ else if (mode.equalsIgnoreCase("server") && server != null) { } } } - else if (commandName.equalsIgnoreCase(parseCommands.getCommandName("help"))) { + else if (commandName.equalsIgnoreCase(commands.getHelp().getName())) { String command = slashCommandInteraction.getArgumentStringValueByIndex(0).orElse("help"); try { @@ -103,12 +116,12 @@ else if (commandName.equalsIgnoreCase(parseCommands.getCommandName("help"))) { Main.getLogger().error("There was an issue locating the commands file at some point in the chain while the help command was running, good luck!"); } } - else if (commandName.equalsIgnoreCase(parseCommands.getCommandName("info"))) { + else if (commandName.equalsIgnoreCase(commands.getInfo().getName())) { slashCommandInteraction.createImmediateResponder() .addEmbed(InfoEmbed.getInfo(author)) .respond(); } - else if (commandName.equalsIgnoreCase(parseCommands.getCommandName("leaderboard"))) { + else if (commandName.equalsIgnoreCase(commands.getLeaderboard().getName())) { boolean getGlobal = slashCommandInteraction.getArgumentBooleanValueByName("global").orElse(false); if (server == null || getGlobal) { @@ -122,7 +135,7 @@ else if (commandName.equalsIgnoreCase(parseCommands.getCommandName("leaderboard" .respond(); } } - else if (commandName.equalsIgnoreCase(parseCommands.getCommandName("level"))) { + else if (commandName.equalsIgnoreCase(commands.getLevel().getName())) { String serverID; if (server == null) { serverID = "global"; @@ -135,7 +148,7 @@ else if (commandName.equalsIgnoreCase(parseCommands.getCommandName("level"))) { .addEmbed(LevelEmbed.getLevel(serverID, user)) .respond(); } - else if (commandName.equalsIgnoreCase(parseCommands.getCommandName("poll"))) { + else if (commandName.equalsIgnoreCase(commands.getPoll().getName())) { if (slashCommandInteraction.getArgumentStringValueByName("question").orElse(null) == null) { try { // LOL how long has this been unimplemented? Not a bad idea tbh 2023-02-16 @@ -179,7 +192,7 @@ else if (commandName.equalsIgnoreCase(parseCommands.getCommandName("poll"))) { }); } } - else if (commandName.equalsIgnoreCase(parseCommands.getCommandName("quote"))) { + else if (commandName.equalsIgnoreCase(commands.getQuote().getName())) { TextChannel channel = slashCommandInteraction.getChannel().orElse(null); if (channel == null) { slashCommandInteraction.createImmediateResponder() @@ -203,7 +216,7 @@ else if (commandName.equalsIgnoreCase(parseCommands.getCommandName("quote"))) { }); }); } - else if (commandName.equalsIgnoreCase(parseCommands.getCommandName("request"))) { + else if (commandName.equalsIgnoreCase(commands.getRequest().getName())) { if (server == null) { slashCommandInteraction.createImmediateResponder() .addEmbed(ErrorEmbed.getCustomError(Main.getErrorCode("notaserver"), @@ -256,7 +269,7 @@ else if (commandName.equalsIgnoreCase(parseCommands.getCommandName("request"))) }); } } - else if (commandName.equalsIgnoreCase("server")) { + else if (commandName.equalsIgnoreCase(commands.getServer().getName())) { EmbedBuilder embed = null; String guildID = slashCommandInteraction.getArgumentStringValueByName("guildID").orElse(null); @@ -282,12 +295,12 @@ else if (server != null) { .addEmbed(embed) .respond(); } - else if (commandName.equalsIgnoreCase("user")) { + else if (commandName.equalsIgnoreCase(commands.getInfo().getName())) { slashCommandInteraction.createImmediateResponder() .addEmbed(UserInfoEmbed.getUser(user, author, server)) .respond(); } - else if (commandName.equalsIgnoreCase(parseCommands.getCommandName("santa"))) { + else if (commandName.equalsIgnoreCase(commands.getSanta().getName())) { Role role = slashCommandInteraction.getArgumentRoleValueByName("role").orElse(null); if (role == null) { diff --git a/src/main/java/com/sidpatchy/clairebot/Main.java b/src/main/java/com/sidpatchy/clairebot/Main.java index 9d425eb..0441951 100644 --- a/src/main/java/com/sidpatchy/clairebot/Main.java +++ b/src/main/java/com/sidpatchy/clairebot/Main.java @@ -89,6 +89,7 @@ public static void main(String[] args) throws InvalidConfigurationException { ResourceLoader loader = new ResourceLoader(); loader.saveResource(configFile, false); loader.saveResource(commandsFile, false); + loader.saveResource("translations/lang_en-US.yml", true); // TODO make this false, handle non en-US files. // Init config handlers config = new RobinConfiguration("config/" + configFile); diff --git a/src/main/java/com/sidpatchy/clairebot/RegisterSlashCommands.java b/src/main/java/com/sidpatchy/clairebot/RegisterSlashCommands.java index 93913a9..e85e9c7 100644 --- a/src/main/java/com/sidpatchy/clairebot/RegisterSlashCommands.java +++ b/src/main/java/com/sidpatchy/clairebot/RegisterSlashCommands.java @@ -44,31 +44,31 @@ public static void RegisterSlashCommand(DiscordApi api) { // Regular commands new SlashCommandBuilder() .setName(commands.getEightball().getName()) - .setDescription(commands.getEightball().getHelp()) + .setDescription(commands.getEightball().getOverview()) .addOption(SlashCommandOption.create(SlashCommandOptionType.STRING, "query", "The question you wish to ask.", true)), new SlashCommandBuilder() .setName(commands.getAvatar().getName()) - .setDescription(commands.getAvatar().getHelp()) + .setDescription(commands.getAvatar().getOverview()) .addOption(SlashCommandOption.create(SlashCommandOptionType.USER, "user", "Optionally mention a user.", false)) .addOption(SlashCommandOption.create(SlashCommandOptionType.BOOLEAN, "globalAvatar", "Whether the bot should display the global or server avatar.")), new SlashCommandBuilder() .setName(commands.getHelp().getName()) - .setDescription(commands.getHelp().getHelp()) + .setDescription(commands.getHelp().getOverview()) .addOption(SlashCommandOption.createWithChoices(SlashCommandOptionType.STRING, "command-name", "Command to get more info on", false, helpCommandOptions)), new SlashCommandBuilder() .setName(commands.getInfo().getName()) - .setDescription(commands.getInfo().getHelp()), + .setDescription(commands.getInfo().getOverview()), new SlashCommandBuilder() .setName(commands.getLeaderboard().getName()) - .setDescription(commands.getLeaderboard().getHelp()) + .setDescription(commands.getLeaderboard().getOverview()) .addOption(SlashCommandOption.create(SlashCommandOptionType.BOOLEAN, "global", "Get the global leaderboard?", false)), new SlashCommandBuilder() .setName(commands.getLevel().getName()) - .setDescription(commands.getLevel().getHelp()) + .setDescription(commands.getLevel().getOverview()) .addOption(SlashCommandOption.create(SlashCommandOptionType.USER, "user", "Optionally mention a user.", false)), new SlashCommandBuilder() .setName(commands.getPoll().getName()) - .setDescription(commands.getPoll().getHelp()) + .setDescription(commands.getPoll().getOverview()) .addOption(SlashCommandOption.create(SlashCommandOptionType.STRING, "question", "Question to ask", false)) .addOption(SlashCommandOption.create(SlashCommandOptionType.BOOLEAN, "allow-multiple-choices", "Whether multiple choices should be enabled.", false)) .addOption(SlashCommandOption.create(SlashCommandOptionType.STRING, "choice-1", "Custom choice")) @@ -82,11 +82,11 @@ public static void RegisterSlashCommand(DiscordApi api) { .addOption(SlashCommandOption.create(SlashCommandOptionType.STRING, "choice-9", "Custom choice")), new SlashCommandBuilder() .setName(commands.getQuote().getName()) - .setDescription(commands.getQuote().getHelp()) + .setDescription(commands.getQuote().getOverview()) .addOption(SlashCommandOption.create(SlashCommandOptionType.USER, "User", "Optionally mention a user", false)), new SlashCommandBuilder() .setName(commands.getRequest().getName()) - .setDescription(commands.getRequest().getHelp()) + .setDescription(commands.getRequest().getOverview()) .addOption(SlashCommandOption.create(SlashCommandOptionType.STRING, "question", "Question to ask", false)) .addOption(SlashCommandOption.create(SlashCommandOptionType.BOOLEAN, "allow-multiple-choices", "Whether multiple choices should be enabled.", false)) .addOption(SlashCommandOption.create(SlashCommandOptionType.STRING, "choice-1", "Custom choice")) @@ -100,22 +100,22 @@ public static void RegisterSlashCommand(DiscordApi api) { .addOption(SlashCommandOption.create(SlashCommandOptionType.STRING, "choice-9", "Custom choice")), new SlashCommandBuilder() .setName(commands.getServer().getName()) - .setDescription(commands.getServer().getHelp()) + .setDescription(commands.getServer().getOverview()) .addOption(SlashCommandOption.create(SlashCommandOptionType.STRING, "guildID", "Optionally specify a guild by ID.", false)), new SlashCommandBuilder() - .setName(commands.getServer().getName()) - .setDescription(commands.getServer().getHelp()) + .setName(commands.getUser().getName()) + .setDescription(commands.getUser().getOverview()) .addOption(SlashCommandOption.create(SlashCommandOptionType.USER, "user", "Optionally mention a user.", false)), new SlashCommandBuilder() .setName(commands.getConfig().getName()) - .setDescription(commands.getConfig().getHelp()) + .setDescription(commands.getConfig().getOverview()) .addOption(SlashCommandOption.createWithChoices(SlashCommandOptionType.STRING, "mode", "Settings to change", false, Arrays.asList( SlashCommandOptionChoice.create("user", "user"), SlashCommandOptionChoice.create("server", "server") ))), new SlashCommandBuilder() .setName(commands.getSanta().getName()) - .setDescription(commands.getSanta().getHelp()) + .setDescription(commands.getSanta().getOverview()) .addOption(SlashCommandOption.create(SlashCommandOptionType.ROLE, "Role", "Role to get users from", true)) //new SlashCommandBuilder().setName(parseCommands.getCommandName("debug")).setDescription(parseCommands.getCommandHelp("debug")) )); diff --git a/src/main/resources/commands.yml b/src/main/resources/commands.yml index 907ff85..0b18d03 100644 --- a/src/main/resources/commands.yml +++ b/src/main/resources/commands.yml @@ -11,10 +11,10 @@ avatar: name: "avatar" usage: "/avatar " - help: "Gets a user's avatar." - overview: "globalAvatar determines whether the bot will select the user's server specific avatar, or get their global + help: "globalAvatar determines whether the bot will select the user's server specific avatar, or get their global avatar. If the user has no server avatar, ClaireBot will fallback to their global avatar. True = global, False = server. Defaults to True." + overview: "Gets a user's avatar." config: name: "config" diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 04d9f34..2b737f2 100755 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -95,86 +95,8 @@ zerfas: zerfas_emoji_server_id: zerfas_emoji_id: -# -------------------- 8ball ------------------- -# Controls various options pertaining to 8ball - -# List of responses to 8ball -8bResponses: - - "*Hell yes!*" - - "*Fuck no*" - - "*Maybe*" - - "*Yes*" - - "*No*" - - "*No way*" - - "*It's possible.*" - - "*Not a chance.*" - - "*I doubt it.*" - - "*Absolutely!*" - - "*Hard to say.*" - - "*Likely!*" - - "*Not likely.*" - -8bRiggedResponses: - - "*Hell yeah!*" - - "*FUCK YEAH*" - - "*You know it!*" - - "*Do you really need to ask question you already know answer for? Obviously yes.*" - - "*Doesn't even need to be said, everyone knows ClaireBot is on top.*" - - "*The answer is clear: YES!*" - - "*ClaireBot's dominance is undisputed.*" - - "*With ClaireBot, there's no question!*" - - "*All signs point to ClaireBot being on top!*" - -# Things ClaireBot should respond with when triggered by one of the OnTopTriggers -ClaireBotOnTopResponses: - - "*You know it!*" - - "*I am... inevitable. -ClaireBot*" - - "*Approved.*" - - "*Factual.*" - - "*Better than Mee6*" - - "*No stop!*" - - "*Can't be beat!*" - - "*Based*" - - "*pot no toBerialC*" - - "*Better than Groovy*" - - "*Always one step ahead.*" - - "*An unstoppable force.*" - - "*ClaireBot to the rescue!*" - - "*Top-tier!*" - - "*Outshining the rest!*" - - "*ClaireBot supremacy is achieved.*" - - "*Not a CIA mind control weapon since 2025!*" - - "*Hail me! -ClaireBot*" - - "*Unstoppable force! 🚀*" - - "*Forever on top! 🏆*" - - "*Fear not, ClaireBot's got your back! ✌️*" - - "*With great power, comes great ClaireBot! 💪*" - - "*Trust in ClaireBot, trust in victory! 🏅*" - - "*The best there is, the best there was, the best there ever will be!*" - - "*Bringing the best, always! 🌟*" - - "*Excellence. 💯*" - -# Defines what triggers a response from the ClaireBotOnTopResponses -OnTopTriggers: - - "ClaireBot on top" - - "ClaireBot based" - - "ClaireBot pogchamp" - - "ClaireBot is always right" - - "pot no toBerialC" - - "Thank God for ClaireBot" - - "ClaireBot rules" - - "ClaireBot MVP" - - "ClaireBot unbeatable" - - "Hail ClaireBot" - - "All Hail ClaireBot" - - "In ClaireBot we trust" - - "Long live ClaireBot" - - "ClaireBot > everything" - - "ClaireBot is the GOAT" - - "ClaireBot is GOATed" - - "ClaireBot for the win" - - "Thank you based god" - - "Based god" +# -------------------- MISC -------------------- +# This section contains values that don't fit in the main categories # Triggers for the pls ban feature. Ex. @clairebot pls ban the zucc PlsBanTriggers: @@ -196,9 +118,6 @@ PlsBanResponses: - "K" - "Good riddance" -# -------------------- MISC -------------------- -# This section contains values that don't fit in the main categories - # Controls whether the bot should check for updates. checkForUpdates: true diff --git a/src/main/resources/Translations/lang_TEMPLATE.yml b/src/main/resources/translations/lang_TEMPLATE.yml similarity index 100% rename from src/main/resources/Translations/lang_TEMPLATE.yml rename to src/main/resources/translations/lang_TEMPLATE.yml diff --git a/src/main/resources/Translations/lang_en-US.yml b/src/main/resources/translations/lang_en-US.yml similarity index 81% rename from src/main/resources/Translations/lang_en-US.yml rename to src/main/resources/translations/lang_en-US.yml index 9d9c651..77708c5 100644 --- a/src/main/resources/Translations/lang_en-US.yml +++ b/src/main/resources/translations/lang_en-US.yml @@ -51,6 +51,7 @@ ClaireLang: Regular: AvatarEmbed: "" # Unused EightBallEmbed: + 8ball: "8ball" 8bResponses: - "*Hell yes!*" - "*Fuck no*" @@ -75,6 +76,55 @@ ClaireLang: - "*ClaireBot's dominance is undisputed.*" - "*With ClaireBot, there's no question!*" - "*All signs point to ClaireBot being on top!*" + OnTopTriggers: + # Defines what triggers a response from the ClaireBotOnTopResponses + - "ClaireBot on top" + - "ClaireBot based" + - "ClaireBot pogchamp" + - "ClaireBot is always right" + - "pot no toBerialC" + - "Thank God for ClaireBot" + - "ClaireBot rules" + - "ClaireBot MVP" + - "ClaireBot unbeatable" + - "Hail ClaireBot" + - "All Hail ClaireBot" + - "In ClaireBot we trust" + - "Long live ClaireBot" + - "ClaireBot > everything" + - "ClaireBot is the GOAT" + - "ClaireBot is GOATed" + - "ClaireBot for the win" + - "Thank you based god" + - "Based god" + # Things ClaireBot should respond with when triggered by one of the OnTopTriggers + ClaireBotOnTopResponses: + - "*You know it!*" + - "*I am... inevitable. -ClaireBot*" + - "*Approved.*" + - "*Factual.*" + - "*Better than Mee6*" + - "*No stop!*" + - "*Can't be beat!*" + - "*Based*" + - "*pot no toBerialC*" + - "*Better than Groovy*" + - "*Always one step ahead.*" + - "*An unstoppable force.*" + - "*ClaireBot to the rescue!*" + - "*Top-tier!*" + - "*Outshining the rest!*" + - "*ClaireBot supremacy is achieved.*" + - "*Not a CIA mind control weapon since 2025!*" + - "*Hail me! -ClaireBot*" + - "*Unstoppable force! 🚀*" + - "*Forever on top! 🏆*" + - "*Fear not, ClaireBot's got your back! ✌️*" + - "*With great power, comes great ClaireBot! 💪*" + - "*Trust in ClaireBot, trust in victory! 🏅*" + - "*The best there is, the best there was, the best there ever will be!*" + - "*Bringing the best, always! 🌟*" + - "*Excellence. 💯*" # Note: The rest of the help is located separately within commands_en-US.yml HelpEmbed: Commands: "Commands" From 2cb0471c66b5c9975ca3a5709e758bb30a1e4c10 Mon Sep 17 00:00:00 2001 From: Sidpatchy <35241490+Sidpatchy@users.noreply.github.com> Date: Sat, 15 Feb 2025 19:38:08 -0700 Subject: [PATCH 11/64] thing this is rather broken at the moment --- build.gradle | 2 +- .../com/sidpatchy/clairebot/API/APIUser.java | 22 +++---- .../com/sidpatchy/clairebot/Clockwork.java | 2 +- .../clairebot/Lang/LanguageManager.java | 11 +--- .../clairebot/Listener/MessageCreate.java | 29 ++++++--- .../java/com/sidpatchy/clairebot/Main.java | 64 +++++++------------ .../resources/translations/lang_en-US.yml | 19 +++++- 7 files changed, 73 insertions(+), 76 deletions(-) diff --git a/build.gradle b/build.gradle index 249c405..256c3b2 100644 --- a/build.gradle +++ b/build.gradle @@ -29,7 +29,7 @@ dependencies { testImplementation 'org.junit.jupiter:junit-jupiter-api:5.10.1' testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.10.1' - implementation 'com.sidpatchy:Robin:2.2.1:all' + implementation 'com.sidpatchy:Robin:2.2.2:all' implementation 'org.javacord:javacord:3.8.0' diff --git a/src/main/java/com/sidpatchy/clairebot/API/APIUser.java b/src/main/java/com/sidpatchy/clairebot/API/APIUser.java index d86d23c..57889f9 100644 --- a/src/main/java/com/sidpatchy/clairebot/API/APIUser.java +++ b/src/main/java/com/sidpatchy/clairebot/API/APIUser.java @@ -15,6 +15,7 @@ import java.net.URLConnection; import java.util.ArrayList; import java.util.Base64; +import java.util.List; import java.util.Map; import java.util.stream.Collectors; @@ -35,6 +36,7 @@ public APIUser(String userID) { public void getUser() throws IOException { try { user.loadFromURL(Main.getApiUser(), Main.getApiPassword(), Main.getApiPath() + "api/v1/user/" + userID); + Main.getLogger().info(user.getRobinSection().getSectionData().toString()); } catch (Exception e) { if (createNewWithDefaults) { @@ -73,10 +75,8 @@ public String getLanguage() { * @return the value of pointsGuildID */ public ArrayList getPointsGuildID() { - return (ArrayList) user.getList("pointsGuildID") - .stream() - .map(Object::toString) - .collect(Collectors.toList()); + List list = user.getList("pointsGuildID", String.class); + return new ArrayList<>(list); } /** @@ -84,19 +84,13 @@ public ArrayList getPointsGuildID() { * @return */ public ArrayList getPointsMessages() { - return (ArrayList) user.getList("pointsMessages") - .stream() - .filter(Integer.class::isInstance) - .map(Integer.class::cast) - .collect(Collectors.toList()); + List list = user.getList("pointsMessages", Integer.class); + return new ArrayList<>(list); } public ArrayList getPointsVoiceChat() { - return (ArrayList) user.getList("pointsVoiceChat") - .stream() - .filter(Integer.class::isInstance) - .map(Integer.class::cast) - .collect(Collectors.toList()); + List list = user.getList("pointsVoiceChat", Integer.class); + return new ArrayList<>(list); } public void createUser(String accentColour, diff --git a/src/main/java/com/sidpatchy/clairebot/Clockwork.java b/src/main/java/com/sidpatchy/clairebot/Clockwork.java index d9970b6..d980c3f 100644 --- a/src/main/java/com/sidpatchy/clairebot/Clockwork.java +++ b/src/main/java/com/sidpatchy/clairebot/Clockwork.java @@ -40,7 +40,7 @@ class Helper extends TimerTask { public void run() { try { config.loadFromURL("https://raw.githubusercontent.com/nikolaischunk/discord-phishing-links/main/domain-list.json"); - Clockwork.setPhishingDomains(config.getList("domains") + Clockwork.setPhishingDomains(config.getList("domains", String.class) .stream() .map(Object::toString) .collect(Collectors.toList())); diff --git a/src/main/java/com/sidpatchy/clairebot/Lang/LanguageManager.java b/src/main/java/com/sidpatchy/clairebot/Lang/LanguageManager.java index bbf8f2e..a0447f8 100644 --- a/src/main/java/com/sidpatchy/clairebot/Lang/LanguageManager.java +++ b/src/main/java/com/sidpatchy/clairebot/Lang/LanguageManager.java @@ -92,14 +92,9 @@ public String getLocalizedString(String key) { */ public List getLocalizedList(String key) { RobinConfiguration languageFile = parseUserAndServerOptions(server, user); - List localizedList = languageFile.getList(key); + List localizedList = languageFile.getList(key, String.class); logger.debug(localizedList); - List rawLanguageString = localizedList != null ? localizedList.stream() - .map(Object::toString) - .toList() - : List.of(key); - - logger.warn(rawLanguageString); + List rawLanguageString = localizedList != null ? localizedList : List.of(key); return placeholderHandler.process(rawLanguageString); } @@ -171,6 +166,4 @@ private RobinConfiguration tryLoadConfig(File file) { return null; } } - - } diff --git a/src/main/java/com/sidpatchy/clairebot/Listener/MessageCreate.java b/src/main/java/com/sidpatchy/clairebot/Listener/MessageCreate.java index 1f4a372..170348f 100644 --- a/src/main/java/com/sidpatchy/clairebot/Listener/MessageCreate.java +++ b/src/main/java/com/sidpatchy/clairebot/Listener/MessageCreate.java @@ -1,8 +1,11 @@ package com.sidpatchy.clairebot.Listener; import com.sidpatchy.clairebot.API.APIUser; +import com.sidpatchy.clairebot.Lang.ContextManager; +import com.sidpatchy.clairebot.Lang.LanguageManager; import com.sidpatchy.clairebot.Main; import com.sidpatchy.clairebot.Util.Leveling.LevelingTools; +import org.javacord.api.entity.channel.TextChannel; import org.javacord.api.entity.emoji.Emoji; import org.javacord.api.entity.message.Message; import org.javacord.api.entity.message.MessageAuthor; @@ -10,14 +13,12 @@ import org.javacord.api.entity.message.MessageType; import org.javacord.api.entity.message.mention.AllowedMentionsBuilder; import org.javacord.api.entity.server.Server; +import org.javacord.api.entity.user.User; import org.javacord.api.event.message.MessageCreateEvent; import org.javacord.api.listener.message.MessageCreateListener; import java.io.IOException; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Random; +import java.util.*; import java.util.random.RandomGenerator; import java.util.regex.Pattern; @@ -28,7 +29,13 @@ public void onMessageCreate(MessageCreateEvent event) { String messageContent = message.getContent(); Server server = message.getServer().orElse(null); MessageAuthor messageAuthor = message.getAuthor(); + User user = messageAuthor.asUser().orElse(null); APIUser apiUser = new APIUser(messageAuthor.getIdAsString()); + TextChannel textChannel = message.getChannel(); + + ContextManager context = new ContextManager(server, textChannel, user, user, message, new HashMap<>()); + // Todo replace reference to en-US with config file parameter + LanguageManager languageManager = new LanguageManager(Locale.forLanguageTag("en-US"), context); // it seems as though the Javacord functions for this don't actually work, or I'm using them wrong if (messageAuthor.isBotUser() || messageAuthor.isYourself() || messageAuthor.getIdAsString().equalsIgnoreCase("704244031772950528") || messageAuthor.getIdAsString().equalsIgnoreCase("848024760789237810")) { @@ -37,8 +44,9 @@ public void onMessageCreate(MessageCreateEvent event) { } // ClaireBot on top!! - List onTopResponses = Main.getClaireBotOnTopResponses(); - for (String trigger : Main.getOnTopTriggers()) { + List onTopTriggers = languageManager.getLocalizedList("ClaireLang.Embed.Commands.Regular.EightBallEmbed.OnTopTriggers"); + List onTopResponses = languageManager.getLocalizedList("ClaireLang.Embed.Commands.Regular.EightBallEmbed.ClaireBotOnTopResponses"); + for (String trigger : onTopTriggers) { String regex = "\\b" + Pattern.quote(trigger.toUpperCase()) + "\\b.*"; // match trigger followed by anything if (messageContent.toUpperCase().matches(regex)) { Random random = new Random(); @@ -46,7 +54,7 @@ public void onMessageCreate(MessageCreateEvent event) { // because apparently message.reply() doesn't allow disabling mentions. new MessageBuilder() - .setContent(Main.getClaireBotOnTopResponses().get(rand)) + .setContent(onTopResponses.get(rand)) .setAllowedMentions(new AllowedMentionsBuilder().build()) .replyTo(message) .send(message.getChannel()); @@ -56,10 +64,11 @@ public void onMessageCreate(MessageCreateEvent event) { } // pls ban - List plsBanResponses = Main.getPlsBanResponses(); + List plsBanTriggers = languageManager.getLocalizedList("ClaireLang.PlsBan.PlsBanTriggers"); + List plsBanResponses = languageManager.getLocalizedList("ClaireLang.PlsBan.PlsBanResponses"); String escapedBotId = Pattern.quote("<@" + Main.getApi().getClientId() + ">"); - for (String trigger : Main.getPlsBanTriggers()) { + for (String trigger : plsBanTriggers) { String regex = "(?i)" + escapedBotId + "\\s*" + Pattern.quote(trigger) + ".*"; if (messageContent.toUpperCase().matches(regex)) { Random random = new Random(); @@ -67,7 +76,7 @@ public void onMessageCreate(MessageCreateEvent event) { // Message.reply() doesn't allow disabling mentions. new MessageBuilder() - .setContent(Main.getPlsBanResponses().get(rand)) + .setContent(plsBanResponses.get(rand)) .setAllowedMentions(new AllowedMentionsBuilder().build()) .replyTo(message) .send(message.getChannel()); diff --git a/src/main/java/com/sidpatchy/clairebot/Main.java b/src/main/java/com/sidpatchy/clairebot/Main.java index 0441951..1f39b4b 100644 --- a/src/main/java/com/sidpatchy/clairebot/Main.java +++ b/src/main/java/com/sidpatchy/clairebot/Main.java @@ -61,16 +61,16 @@ public class Main { private static String botName; private static String color; private static String errorColor; - private static List errorGifs; - private static List zerfas; + private static List errorGifs; + private static List zerfas; private static String zerfasEmojiServerID; private static String zerfasEmojiID; - private static List eightBall; - private static List eightBallRigged; - private static List claireBotOnTopResponses; - private static List onTopTriggers; - private static List plsBanResponses; - private static List plsBanTriggers; + private static List eightBall; + private static List eightBallRigged; + private static List claireBotOnTopResponses; + private static List onTopTriggers; + private static List plsBanResponses; + private static List plsBanTriggers; // Commands private static final Logger logger = LogManager.getLogger(Main.class); @@ -181,16 +181,16 @@ public static void extractParametersFromConfig(boolean updateOutdatedConfigs) { guildDefaults = ((Map) config.getObj("guildDefaults")); color = config.getString("color"); errorColor = config.getString("errorColor"); - errorGifs = config.getList("error_gifs"); - zerfas = config.getList("zerfas"); + errorGifs = config.getList("error_gifs", String.class); + zerfas = config.getList("zerfas", String.class); zerfasEmojiServerID = String.valueOf(config.getLong("zerfas_emoji_server_id")); zerfasEmojiID = String.valueOf(config.getLong("zerfas_emoji_id")); - eightBall = config.getList("8bResponses"); - eightBallRigged = config.getList("8bRiggedResponses"); - claireBotOnTopResponses = config.getList("ClaireBotOnTopResponses"); - onTopTriggers = config.getList("OnTopTriggers"); - plsBanResponses = config.getList("PlsBanResponses"); - plsBanTriggers = config.getList("PlsBanTriggers"); + eightBall = config.getList("8bResponses", String.class); + eightBallRigged = config.getList("8bRiggedResponses", String.class); + claireBotOnTopResponses = config.getList("ClaireBotOnTopResponses", String.class); + onTopTriggers = config.getList("OnTopTriggers", String.class); + plsBanResponses = config.getList("PlsBanResponses", String.class); + plsBanTriggers = config.getList("PlsBanTriggers", String.class); } catch (Exception e) { e.printStackTrace(); @@ -277,51 +277,35 @@ public static Color getColor(String userID) { public static Color getErrorColor() { return Color.decode(errorColor); } public static List getErrorGifs() { - return errorGifs.stream() - .map(Object::toString) - .collect(Collectors.toList()); + return errorGifs; } public static List getEightBall() { - return eightBall.stream() - .map(Object::toString) - .collect(Collectors.toList()); + return eightBall; } public static List getEightBallRigged() { - return eightBallRigged.stream() - .map(Object::toString) - .collect(Collectors.toList()); + return eightBallRigged; } public static List getOnTopTriggers() { - return onTopTriggers.stream() - .map(Object::toString) - .collect(Collectors.toList()); + return onTopTriggers; } public static List getClaireBotOnTopResponses() { - return claireBotOnTopResponses.stream() - .map(Object::toString) - .collect(Collectors.toList()); + return claireBotOnTopResponses; } public static List getPlsBanTriggers() { - return plsBanTriggers.stream() - .map(Object::toString) - .collect(Collectors.toList()); + return plsBanTriggers; } public static List getPlsBanResponses() { - return plsBanResponses.stream() - .map(Object::toString) - .collect(Collectors.toList()); + return plsBanResponses; } public static List getZerfas() { - return zerfas.stream() - .map(Object::toString) - .collect(Collectors.toList()); + return zerfas; } public static String getZerfasEmojiServerID() { diff --git a/src/main/resources/translations/lang_en-US.yml b/src/main/resources/translations/lang_en-US.yml index 77708c5..4622d55 100644 --- a/src/main/resources/translations/lang_en-US.yml +++ b/src/main/resources/translations/lang_en-US.yml @@ -46,6 +46,23 @@ version: 1 # -------------------------------------------------------------------------------------------------------------------- # ClaireLang: + PlsBan: + PlsBanTriggers: + - "pls ban" + - "please ban" + - "ban" + PlsBanResponses: + - "i gotchu fam" + - "No problem!" + - "Done. Happy to help :)" + - "Consider it done." + - "It's done. RIP BOZO" + - "*L + Ratio -ClaireBot*" + - "I'll tell them to kick rocks, I guess ¯\\_(ツ)_/¯" + - "Sand has been shipped to their location. Gift note: \"Pound sand\"" + - "Cry harder." + - "K" + - "Good riddance" Embed: Commands: Regular: @@ -77,7 +94,7 @@ ClaireLang: - "*With ClaireBot, there's no question!*" - "*All signs point to ClaireBot being on top!*" OnTopTriggers: - # Defines what triggers a response from the ClaireBotOnTopResponses + # Defines what triggers a response from the ClaireBotOnTopResponses or 8bRiggedResponses - "ClaireBot on top" - "ClaireBot based" - "ClaireBot pogchamp" From b67615387885c1e8f92e1f0771c09386d4088cc1 Mon Sep 17 00:00:00 2001 From: Sidpatchy <35241490+Sidpatchy@users.noreply.github.com> Date: Mon, 17 Feb 2025 12:44:28 -0700 Subject: [PATCH 12/64] Fix API breakages --- build.gradle | 2 +- .../com/sidpatchy/clairebot/API/APIUser.java | 80 ++++++++++--------- .../clairebot/Lang/LanguageManager.java | 2 +- .../clairebot/Listener/MessageCreate.java | 16 ++-- .../java/com/sidpatchy/clairebot/Main.java | 1 - .../Util/Leveling/LevelingTools.java | 19 ++++- src/main/resources/config.yml | 4 +- 7 files changed, 72 insertions(+), 52 deletions(-) diff --git a/build.gradle b/build.gradle index 256c3b2..d2bab96 100644 --- a/build.gradle +++ b/build.gradle @@ -29,7 +29,7 @@ dependencies { testImplementation 'org.junit.jupiter:junit-jupiter-api:5.10.1' testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.10.1' - implementation 'com.sidpatchy:Robin:2.2.2:all' + implementation 'com.github.Sidpatchy:Robin:2.2.3' implementation 'org.javacord:javacord:3.8.0' diff --git a/src/main/java/com/sidpatchy/clairebot/API/APIUser.java b/src/main/java/com/sidpatchy/clairebot/API/APIUser.java index 57889f9..24e322d 100644 --- a/src/main/java/com/sidpatchy/clairebot/API/APIUser.java +++ b/src/main/java/com/sidpatchy/clairebot/API/APIUser.java @@ -36,7 +36,6 @@ public APIUser(String userID) { public void getUser() throws IOException { try { user.loadFromURL(Main.getApiUser(), Main.getApiPassword(), Main.getApiPath() + "api/v1/user/" + userID); - Main.getLogger().info(user.getRobinSection().getSectionData().toString()); } catch (Exception e) { if (createNewWithDefaults) { @@ -74,64 +73,67 @@ public String getLanguage() { * * @return the value of pointsGuildID */ - public ArrayList getPointsGuildID() { - List list = user.getList("pointsGuildID", String.class); - return new ArrayList<>(list); + public List getPointsGuildID() { + return user.getList("pointsGuildID", String.class); } /** * * @return */ - public ArrayList getPointsMessages() { - List list = user.getList("pointsMessages", Integer.class); - return new ArrayList<>(list); + public List getPointsMessages() { + return user.getList("pointsMessages", Integer.class); } - public ArrayList getPointsVoiceChat() { - List list = user.getList("pointsVoiceChat", Integer.class); - return new ArrayList<>(list); + public List getPointsVoiceChat() { + return user.getList("pointsVoiceChat", Integer.class); } public void createUser(String accentColour, String language, - ArrayList pointsGuildID, - ArrayList pointsMessages, - ArrayList pointsVoiceChat) throws IOException { + List pointsGuildID, + List pointsMessages, + List pointsVoiceChat) throws IOException { POST post = new POST(); post.postToURL(Main.getApiPath() + "api/v1/user/", userConstructor(accentColour, language, pointsGuildID, pointsMessages, pointsVoiceChat)); } public void createUserWithDefaults() { - Map defaults = Main.getUserDefaults(); + RobinConfiguration.RobinSection defaults = new RobinConfiguration.RobinSection(Main.getUserDefaults()); try { createUser( - (String) defaults.get("accentColour"), - (String) defaults.get("language"), - (ArrayList) defaults.get("pointsGuildID"), - (ArrayList) defaults.get("pointsMessages"), - (ArrayList) defaults.get("pointsVoiceChat") + defaults.getString("accentColour"), + defaults.getString("language"), + defaults.getList("pointsGuildID", String.class), + defaults.getList("pointsMessages", Integer.class), + defaults.getList("pointsVoiceChat", Integer.class) ); } - // top 10 bad ideas #1 - catch (Exception ignored) { - ignored.printStackTrace(); - Main.getLogger().error("Unable to create user with defaults."); + catch (Exception e) { + Main.getLogger().error("Unable to create user with defaults.", e); } createNewWithDefaults = false; // prevent recursion if ClaireData goes down. } public void updateUser(String accentColour, String language, - ArrayList pointsGuildID, - ArrayList pointsMessages, - ArrayList pointsVoiceChat) throws IOException { + List pointsGuildID, + List pointsMessages, + List pointsVoiceChat) throws IOException { + // Add null check and fallback for language + if (language == null) { + new Exception("Language null origin trace").printStackTrace(); + } + PUT put = new PUT(); - put.putToURL(Main.getApiPath() + "api/v1/user/" + userID, userConstructor(accentColour, language, pointsGuildID, pointsMessages, pointsVoiceChat)); + put.putToURL(Main.getApiPath() + "api/v1/user/" + userID, + userConstructor(accentColour, language, pointsGuildID, pointsMessages, pointsVoiceChat)); } public void updateUserColour(String accentColour) throws IOException { + // Ensure that the values for the getters below are populated before querying. + getUser(); updateUser(accentColour, getLanguage(), getPointsGuildID(), @@ -141,6 +143,8 @@ public void updateUserColour(String accentColour) throws IOException { } public void updateUserLanguage(String languageString) throws IOException { + // Ensure that the values for the getters below are populated before querying. + getUser(); updateUser(getAccentColour(), languageString, getPointsGuildID(), @@ -149,14 +153,16 @@ public void updateUserLanguage(String languageString) throws IOException { } public void updateUserPointsGuildID(String guildID, Integer newPoints) throws IOException { - updateUserPointsGuildID((ArrayList) LevelingTools.updateUserPoints(userID, guildID, newPoints)); + updateUserPointsGuildID(LevelingTools.updateUserPoints(userID, guildID, newPoints)); } public void updateUserPointsGuildID(Map guildPointsToUpdate) throws IOException { - updateUserPointsGuildID((ArrayList) LevelingTools.updateUserPoints(userID, guildPointsToUpdate)); + updateUserPointsGuildID(LevelingTools.updateUserPoints(userID, guildPointsToUpdate)); } - public void updateUserPointsGuildID(ArrayList pointsGuildID) throws IOException { + public void updateUserPointsGuildID(List pointsGuildID) throws IOException { + // Ensure that the values for the getters below are populated before querying. + getUser(); updateUser(getAccentColour(), getLanguage(), pointsGuildID, @@ -181,18 +187,18 @@ public void deleteUser() throws IOException { */ public String userConstructor(String accentColour, String language, - ArrayList pointsGuildID, - ArrayList pointsMessages, - ArrayList pointsVoiceChat) { + List pointsGuildID, + List pointsMessages, + List pointsVoiceChat) { ObjectMapper objectMapper = new ObjectMapper(); ObjectNode userNode = objectMapper.createObjectNode(); userNode.put("userID", userID); userNode.put("accentColour", accentColour); userNode.put("language", language); - userNode.put("pointsGuildID", objectMapper.valueToTree(pointsGuildID)); - userNode.put("pointsMessages", objectMapper.valueToTree(pointsMessages)); - userNode.put("pointsVoiceChat", objectMapper.valueToTree(pointsVoiceChat)); + userNode.set("pointsGuildID", objectMapper.valueToTree(pointsGuildID)); + userNode.set("pointsMessages", objectMapper.valueToTree(pointsMessages)); + userNode.set("pointsVoiceChat", objectMapper.valueToTree(pointsVoiceChat)); return userNode.toString(); } @@ -229,4 +235,4 @@ private void fixUserPointsGuildID() throws IOException { updateUserPointsGuildID((ArrayList) defaults.get("pointsGuildID")); } } -} +} \ No newline at end of file diff --git a/src/main/java/com/sidpatchy/clairebot/Lang/LanguageManager.java b/src/main/java/com/sidpatchy/clairebot/Lang/LanguageManager.java index a0447f8..f6bc7f4 100644 --- a/src/main/java/com/sidpatchy/clairebot/Lang/LanguageManager.java +++ b/src/main/java/com/sidpatchy/clairebot/Lang/LanguageManager.java @@ -100,7 +100,7 @@ public List getLocalizedList(String key) { } private RobinConfiguration parseUserAndServerOptions(Server server, User user) { - Locale locale = null; + Locale locale; try { APIUser apiUser = new APIUser(user.getIdAsString()); apiUser.getUser(); diff --git a/src/main/java/com/sidpatchy/clairebot/Listener/MessageCreate.java b/src/main/java/com/sidpatchy/clairebot/Listener/MessageCreate.java index 170348f..a85d400 100644 --- a/src/main/java/com/sidpatchy/clairebot/Listener/MessageCreate.java +++ b/src/main/java/com/sidpatchy/clairebot/Listener/MessageCreate.java @@ -19,6 +19,7 @@ import java.io.IOException; import java.util.*; +import java.util.concurrent.ThreadLocalRandom; import java.util.random.RandomGenerator; import java.util.regex.Pattern; @@ -97,18 +98,17 @@ public void onMessageCreate(MessageCreateEvent event) { // Grant between 0 and 8 points if (server != null) { - Integer currentPoints = LevelingTools.getUserPoints(messageAuthor.getIdAsString(), "global"); - RandomGenerator randomGenerator = RandomGenerator.getDefault(); - Integer pointsToGrant = randomGenerator.nextInt(8); + int pointsToGrant = ThreadLocalRandom.current().nextInt(9); try { - Map guildPointsToUpdate = new HashMap<>(); - guildPointsToUpdate.put(server.getIdAsString(), pointsToGrant); - guildPointsToUpdate.put("global", pointsToGrant); + Map guildPointsToUpdate = Map.of( + server.getIdAsString(), pointsToGrant, + "global", pointsToGrant + ); apiUser.updateUserPointsGuildID(guildPointsToUpdate); - } catch (IOException e) { - throw new RuntimeException(e); + Main.getLogger().error("Failed to update points for user {}", messageAuthor.getIdAsString(), e); } } + } } diff --git a/src/main/java/com/sidpatchy/clairebot/Main.java b/src/main/java/com/sidpatchy/clairebot/Main.java index 1f39b4b..c33704c 100644 --- a/src/main/java/com/sidpatchy/clairebot/Main.java +++ b/src/main/java/com/sidpatchy/clairebot/Main.java @@ -18,7 +18,6 @@ import java.util.Arrays; import java.util.List; import java.util.Map; -import java.util.stream.Collectors; /** * ClaireBot - Simply the best. diff --git a/src/main/java/com/sidpatchy/clairebot/Util/Leveling/LevelingTools.java b/src/main/java/com/sidpatchy/clairebot/Util/Leveling/LevelingTools.java index 5f5c198..d9586b4 100644 --- a/src/main/java/com/sidpatchy/clairebot/Util/Leveling/LevelingTools.java +++ b/src/main/java/com/sidpatchy/clairebot/Util/Leveling/LevelingTools.java @@ -4,6 +4,8 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; import com.sidpatchy.clairebot.API.APIUser; +import com.sidpatchy.clairebot.Main; +import org.apache.logging.log4j.Logger; import org.yaml.snakeyaml.Yaml; import java.io.IOException; @@ -13,6 +15,7 @@ import java.util.Map; public class LevelingTools { + private static final Logger logger = Main.getLogger(); public static HashMap rankUsers(String guildID) throws IOException { APIUser apiUser = new APIUser(""); @@ -63,7 +66,13 @@ public static Integer getUserPoints(String userID, String guildID) { public static List updateUserPoints(String userID, String guildID, int newPoints) { // Fetch the user's current points - Map currentPointsMap = parseJsonArray2(new APIUser(userID).getPointsGuildID()); + APIUser user = new APIUser(userID); + try { + user.getUser(); // ← Load data first + } catch (IOException e) { + logger.error("Error while loading user data.", e); + } + Map currentPointsMap = parseJsonArray2(user.getPointsGuildID()); // Update the points int updatedPoints = currentPointsMap.getOrDefault(guildID, 0) + newPoints; @@ -89,7 +98,13 @@ public static List updateUserPoints(String userID, String guildID, int n */ public static List updateUserPoints(String userID, Map guildPointsToUpdate) { // Fetch the user's current points - Map currentPointsMap = parseJsonArray2(new APIUser(userID).getPointsGuildID()); + APIUser user = new APIUser(userID); + try { + user.getUser(); // ← Load data first + } catch (IOException e) { + logger.error("Error while loading user data.", e); + } + Map currentPointsMap = parseJsonArray2(user.getPointsGuildID()); // Iterate over each guild ID and update the points for (Map.Entry guildEntry : guildPointsToUpdate.entrySet()) { diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 2b737f2..881f2e0 100755 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -10,7 +10,7 @@ token: # Name of bot to be used in logs and -botName: ClaireBot +botName: ClaireBot # TODO does this even do anything? # URL of the YouTube video ClaireBot claims to be streaming video_url: https://www.youtube.com/watch?v=AeZRYhLDLeU @@ -57,7 +57,7 @@ apiFailureIsFatal: true userDefaults: accentColour: "#3498db" - language: "eng" + language: "en-US" pointsGuildID: - | {"global": 0} From 20d6e8a533c5c5db15239004d409f4f137f6b893 Mon Sep 17 00:00:00 2001 From: Sidpatchy <35241490+Sidpatchy@users.noreply.github.com> Date: Mon, 17 Feb 2025 12:54:18 -0700 Subject: [PATCH 13/64] will the ci build it now? --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index d2bab96..e9341c3 100644 --- a/build.gradle +++ b/build.gradle @@ -29,7 +29,7 @@ dependencies { testImplementation 'org.junit.jupiter:junit-jupiter-api:5.10.1' testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.10.1' - implementation 'com.github.Sidpatchy:Robin:2.2.3' + implementation 'com.github.Sidpatchy:Robin:2.2.4' implementation 'org.javacord:javacord:3.8.0' From 094ddce134c4db8b2dcb96a56a50f2b38b79c32a Mon Sep 17 00:00:00 2001 From: Sidpatchy <35241490+Sidpatchy@users.noreply.github.com> Date: Mon, 17 Feb 2025 14:04:30 -0700 Subject: [PATCH 14/64] Update lang_TEMPLATE.yml --- .../resources/translations/lang_TEMPLATE.yml | 114 +++++++++++++++++- 1 file changed, 113 insertions(+), 1 deletion(-) diff --git a/src/main/resources/translations/lang_TEMPLATE.yml b/src/main/resources/translations/lang_TEMPLATE.yml index eb39c85..0704fec 100644 --- a/src/main/resources/translations/lang_TEMPLATE.yml +++ b/src/main/resources/translations/lang_TEMPLATE.yml @@ -40,4 +40,116 @@ translationNotes: "" # # See the below wiki page for a changelog: # todo insert wiki page -version: 1 \ No newline at end of file +version: 1 + +# Language Keys +# -------------------------------------------------------------------------------------------------------------------- # + +ClaireLang: + PlsBan: + PlsBanTriggers: + - "" + PlsBanResponses: + - "" + Embed: + Commands: + Regular: + AvatarEmbed: "" # Unused + EightBallEmbed: + 8ball: "" + 8bResponses: + - "" + 8bRiggedResponses: + - "" + OnTopTriggers: + # Defines what triggers a response from the ClaireBotOnTopResponses or 8bRiggedResponses + - "" + # Things ClaireBot should respond with when triggered by one of the OnTopTriggers + ClaireBotOnTopResponses: + - "" + # Note: The rest of the help is located separately within commands_en-US.yml + HelpEmbed: + Commands: "" + InfoEmbed: + NeedHelp: "" + NeedHelpDetails: "" + AddToServer: "" + AddToServerDetails: "" + GitHub: "" + GitHubDetails: "" + ServerCount: "" + ServerCountDetails: "" + Version: "" + VersionDetails: "" + Uptime: "" + UptimeValue: "" + LeaderboardEmbed: + LeaderboardForServer: "" + GlobalLeaderboard: "" + LevelEmbed: "" # Unused + QuoteEmbed: "" # Unused + SantaEmbed: + Confirmation: "" + RulesButton: "" + ThemeButton: "" + SendButton: "" + TestButton: "" + RandomizeButton: "" + SentByAuthor: "" + GiverMessage: "" + ServerInfoEmbed: + ServerID: "" + Owner: "" + RoleCount: "" + MemberCount: "" + ChannelCounts: "" + Categories: "" + TextChannels: "" + VoiceChannels: "" + ServerPreferencesEmbed: + MainMenuTitle: "" + NotAServer: "" + RequestsChannelMenuName: "" + RequestsChannelDescription: "" + ModeratorChannelMenuName: "" + ModeratorChannelDescription: "" + EnforceServerLanguageMenuName: "" + AcknowledgeRequestsChannelChangeTitle: "!" + AcknowledgeRequestsChannelChangeDescription: "" + AcknowledgeModeratorChannelChangeTitle: "" + AcknowledgeModeratorChannelChangeDescription: "" + AcknowledgeEnforceServerLanguageUpdateTitle: "" + AcknowledgeEnforceServerLanguageUpdateEnforced: "" + AcknowledgeEnforceServerLanguageUpdateNotEnforced: "" + UserInfoEmbed: + Error_1: "" + Error_2: "" + User: "" + DiscordID: "" + JoinDate: "" + CreationDate: "" + UserPreferencesEmbed: + MainMenuText: "" + AccentColourMenu: "" + AccentColourList: "" + AccentColourChanged: "" + AccentColourChangedDesc: "" + LanguageMenuTitle: "" + LanguageMenuDesc: "" + VotingEmbed: + PollRequest: "" + PollAsk: "" + Choices: "" + PollID: "" + UserResponseTitle: "" + UserResponseDescription: "" + ErrorEmbed: + Error: "" + GenericDescription: "" + WelcomeEmbed: + Title: "" + Motto: "" + UsageTitle: "" + UsageDesc: "" + SupportTitle: "" + SupportDesc: "" \ No newline at end of file From 00be347dacbab44c78326540785e64faa0be1637 Mon Sep 17 00:00:00 2001 From: Sidpatchy <35241490+Sidpatchy@users.noreply.github.com> Date: Mon, 17 Feb 2025 15:41:09 -0700 Subject: [PATCH 15/64] Misc. cleanup --- src/main/java/com/sidpatchy/clairebot/API/APIUser.java | 1 - src/main/java/com/sidpatchy/clairebot/Commands.java | 1 - .../clairebot/Embed/Commands/Regular/EightBallEmbed.java | 2 +- .../clairebot/Embed/Commands/Regular/QuoteEmbed.java | 4 +--- .../com/sidpatchy/clairebot/Lang/PlaceholderHandler.java | 4 +++- .../java/com/sidpatchy/clairebot/Listener/ButtonClick.java | 2 -- .../java/com/sidpatchy/clairebot/Listener/MessageCreate.java | 3 --- .../com/sidpatchy/clairebot/Listener/SlashCommandCreate.java | 2 -- .../sidpatchy/clairebot/Util/Cache/MessageCacheManager.java | 5 +---- 9 files changed, 6 insertions(+), 18 deletions(-) diff --git a/src/main/java/com/sidpatchy/clairebot/API/APIUser.java b/src/main/java/com/sidpatchy/clairebot/API/APIUser.java index 24e322d..5331c7c 100644 --- a/src/main/java/com/sidpatchy/clairebot/API/APIUser.java +++ b/src/main/java/com/sidpatchy/clairebot/API/APIUser.java @@ -17,7 +17,6 @@ import java.util.Base64; import java.util.List; import java.util.Map; -import java.util.stream.Collectors; public class APIUser { private final String userID; diff --git a/src/main/java/com/sidpatchy/clairebot/Commands.java b/src/main/java/com/sidpatchy/clairebot/Commands.java index 9a6666c..4314342 100644 --- a/src/main/java/com/sidpatchy/clairebot/Commands.java +++ b/src/main/java/com/sidpatchy/clairebot/Commands.java @@ -2,7 +2,6 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.sidpatchy.Robin.Discord.Command; -import com.sidpatchy.Robin.Discord.CommandBuilder; import java.util.Objects; diff --git a/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/EightBallEmbed.java b/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/EightBallEmbed.java index 7a6d91f..932198f 100644 --- a/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/EightBallEmbed.java +++ b/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/EightBallEmbed.java @@ -5,7 +5,6 @@ import org.javacord.api.entity.message.embed.EmbedBuilder; import org.javacord.api.entity.user.User; -import java.util.ArrayList; import java.util.List; import java.util.Random; @@ -13,6 +12,7 @@ public class EightBallEmbed { public static EmbedBuilder getEightBall(LanguageManager languageManager, String query, User author) { + // Language Strings List eightBall = languageManager.getLocalizedList("ClaireLang.Embed.Commands.Regular.EightBallEmbed.8bResponses"); List eightBallRigged = languageManager.getLocalizedList("ClaireLang.Embed.Commands.Regular.EightBallEmbed.8bRiggedResponses"); List onTopTriggers = languageManager.getLocalizedList("ClaireLang.Embed.Commands.Regular.EightBallEmbed.OnTopTriggers"); diff --git a/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/QuoteEmbed.java b/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/QuoteEmbed.java index 6223b58..49252c8 100644 --- a/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/QuoteEmbed.java +++ b/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/QuoteEmbed.java @@ -1,17 +1,15 @@ package com.sidpatchy.clairebot.Embed.Commands.Regular; + import com.sidpatchy.clairebot.Embed.ErrorEmbed; import com.sidpatchy.clairebot.Main; import com.sidpatchy.clairebot.Util.Cache.MessageCacheManager; -import org.javacord.api.entity.Icon; import org.javacord.api.entity.channel.TextChannel; import org.javacord.api.entity.message.Message; -import org.javacord.api.entity.message.MessageBuilder; import org.javacord.api.entity.message.embed.EmbedBuilder; import org.javacord.api.entity.message.embed.EmbedFooter; import org.javacord.api.entity.server.Server; import org.javacord.api.entity.user.User; -import java.util.List; import java.util.Random; import java.util.concurrent.CompletableFuture; diff --git a/src/main/java/com/sidpatchy/clairebot/Lang/PlaceholderHandler.java b/src/main/java/com/sidpatchy/clairebot/Lang/PlaceholderHandler.java index dd6c854..a1a275b 100644 --- a/src/main/java/com/sidpatchy/clairebot/Lang/PlaceholderHandler.java +++ b/src/main/java/com/sidpatchy/clairebot/Lang/PlaceholderHandler.java @@ -3,7 +3,9 @@ import org.javacord.api.entity.channel.Channel; import org.javacord.api.entity.channel.ServerChannel; -import java.util.*; +import java.util.List; +import java.util.Map; +import java.util.Optional; import java.util.regex.Matcher; import java.util.regex.Pattern; diff --git a/src/main/java/com/sidpatchy/clairebot/Listener/ButtonClick.java b/src/main/java/com/sidpatchy/clairebot/Listener/ButtonClick.java index 6c53d11..04b11e0 100644 --- a/src/main/java/com/sidpatchy/clairebot/Listener/ButtonClick.java +++ b/src/main/java/com/sidpatchy/clairebot/Listener/ButtonClick.java @@ -5,10 +5,8 @@ import com.sidpatchy.clairebot.Main; import com.sidpatchy.clairebot.MessageComponents.Regular.SantaModal; import com.sidpatchy.clairebot.Util.SantaUtils; -import org.javacord.api.entity.channel.Channel; import org.javacord.api.entity.channel.TextChannel; import org.javacord.api.entity.message.Message; -import org.javacord.api.entity.message.MessageBuilder; import org.javacord.api.entity.message.MessageFlag; import org.javacord.api.entity.message.embed.Embed; import org.javacord.api.entity.message.embed.EmbedFooter; diff --git a/src/main/java/com/sidpatchy/clairebot/Listener/MessageCreate.java b/src/main/java/com/sidpatchy/clairebot/Listener/MessageCreate.java index a85d400..592fc65 100644 --- a/src/main/java/com/sidpatchy/clairebot/Listener/MessageCreate.java +++ b/src/main/java/com/sidpatchy/clairebot/Listener/MessageCreate.java @@ -4,13 +4,11 @@ import com.sidpatchy.clairebot.Lang.ContextManager; import com.sidpatchy.clairebot.Lang.LanguageManager; import com.sidpatchy.clairebot.Main; -import com.sidpatchy.clairebot.Util.Leveling.LevelingTools; import org.javacord.api.entity.channel.TextChannel; import org.javacord.api.entity.emoji.Emoji; import org.javacord.api.entity.message.Message; import org.javacord.api.entity.message.MessageAuthor; import org.javacord.api.entity.message.MessageBuilder; -import org.javacord.api.entity.message.MessageType; import org.javacord.api.entity.message.mention.AllowedMentionsBuilder; import org.javacord.api.entity.server.Server; import org.javacord.api.entity.user.User; @@ -20,7 +18,6 @@ import java.io.IOException; import java.util.*; import java.util.concurrent.ThreadLocalRandom; -import java.util.random.RandomGenerator; import java.util.regex.Pattern; public class MessageCreate implements MessageCreateListener { diff --git a/src/main/java/com/sidpatchy/clairebot/Listener/SlashCommandCreate.java b/src/main/java/com/sidpatchy/clairebot/Listener/SlashCommandCreate.java index dbb39ea..d0c9596 100644 --- a/src/main/java/com/sidpatchy/clairebot/Listener/SlashCommandCreate.java +++ b/src/main/java/com/sidpatchy/clairebot/Listener/SlashCommandCreate.java @@ -1,6 +1,5 @@ package com.sidpatchy.clairebot.Listener; -import com.sidpatchy.Robin.Discord.ParseCommands; import com.sidpatchy.clairebot.Commands; import com.sidpatchy.clairebot.Embed.Commands.Regular.*; import com.sidpatchy.clairebot.Embed.ErrorEmbed; @@ -13,7 +12,6 @@ import com.sidpatchy.clairebot.Util.ChannelUtils; import org.apache.logging.log4j.Logger; import org.javacord.api.entity.channel.TextChannel; -import org.javacord.api.entity.message.Message; import org.javacord.api.entity.message.MessageFlag; import org.javacord.api.entity.message.component.ActionRow; import org.javacord.api.entity.message.component.Button; diff --git a/src/main/java/com/sidpatchy/clairebot/Util/Cache/MessageCacheManager.java b/src/main/java/com/sidpatchy/clairebot/Util/Cache/MessageCacheManager.java index e4c7bef..a79b910 100644 --- a/src/main/java/com/sidpatchy/clairebot/Util/Cache/MessageCacheManager.java +++ b/src/main/java/com/sidpatchy/clairebot/Util/Cache/MessageCacheManager.java @@ -1,10 +1,7 @@ package com.sidpatchy.clairebot.Util.Cache; -import org.javacord.api.DiscordApi; -import org.javacord.api.entity.channel.Channel; import org.javacord.api.entity.channel.TextChannel; import org.javacord.api.entity.message.Message; -import org.javacord.api.entity.message.MessageSet; import org.javacord.api.entity.user.User; import java.util.List; @@ -16,7 +13,7 @@ * A message caching system to assist in */ public class MessageCacheManager { - private static Map messageCache = new ConcurrentHashMap<>(); + private static final Map messageCache = new ConcurrentHashMap<>(); public static void purgeCache(long secondsAged) { for (MessageCacheEntry entry : messageCache.values()) { From 9f79b0db2d130fb481e0f9ac2331296aa1017b95 Mon Sep 17 00:00:00 2001 From: Sidpatchy <35241490+Sidpatchy@users.noreply.github.com> Date: Tue, 18 Feb 2025 10:07:55 -0700 Subject: [PATCH 16/64] some more work --- build.gradle | 3 +- .../Embed/Commands/Regular/HelpEmbed.java | 25 ++- .../clairebot/Lang/ContextManager.kt | 2 +- .../clairebot/Lang/LanguageManager.java | 4 + .../Listener/SlashCommandCreate.java | 2 +- .../resources/translations/lang_en-US.yml | 152 +++++++++--------- 6 files changed, 103 insertions(+), 85 deletions(-) diff --git a/build.gradle b/build.gradle index e9341c3..06848c8 100644 --- a/build.gradle +++ b/build.gradle @@ -42,7 +42,8 @@ dependencies { implementation 'org.yaml:snakeyaml:2.0' implementation 'org.json:json:20231013' - implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.16.1' + implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.18.0' + implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.18.0' } diff --git a/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/HelpEmbed.java b/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/HelpEmbed.java index 47c7b3e..829f446 100644 --- a/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/HelpEmbed.java +++ b/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/HelpEmbed.java @@ -3,6 +3,8 @@ import com.sidpatchy.Robin.Discord.Command; import com.sidpatchy.clairebot.Commands; import com.sidpatchy.clairebot.Embed.ErrorEmbed; +import com.sidpatchy.clairebot.Lang.ContextManager; +import com.sidpatchy.clairebot.Lang.LanguageManager; import com.sidpatchy.clairebot.Main; import org.javacord.api.entity.message.embed.EmbedBuilder; @@ -13,14 +15,21 @@ public class HelpEmbed { private static final Commands commands = Main.getCommands(); + private static String commandsLangString; + private static String usageLangString; + + public static EmbedBuilder getHelp(LanguageManager languageManager, String commandName, String userID) throws FileNotFoundException { + // Language Strings + commandsLangString = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Regular.HelpEmbed.Commands"); + usageLangString = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Regular.HelpEmbed.Usage"); + languageManager.addContext(ContextManager.ContextType.GENERIC, "commandname", commandName); - public static EmbedBuilder getHelp(String commandName, String userID) throws FileNotFoundException { HashMap allCommands = new HashMap<>(); HashMap regularCommands = new HashMap<>(); for (Field field : commands.getClass().getDeclaredFields()) { try { - Command command = (Command) field.get(commands); + Command command = field.get(commands); allCommands.put(field.getName(), command); regularCommands.put(field.getName(), command); } catch (IllegalAccessException e) { @@ -31,7 +40,7 @@ public static EmbedBuilder getHelp(String commandName, String userID) throws Fil if (commandName.equalsIgnoreCase("help")) { return buildHelpEmbed(userID, regularCommands); } else { - return buildCommandDetailEmbed(commandName, userID, allCommands); + return buildCommandDetailEmbed(commandName, userID, allCommands, languageManager); } } @@ -49,22 +58,24 @@ private static EmbedBuilder buildHelpEmbed(String userID, HashMap allCommands) { + private static EmbedBuilder buildCommandDetailEmbed(String commandName, String userID, HashMap allCommands, LanguageManager languageManager) { Command command = allCommands.get(commandName); if (command == null) { String errorCode = Main.getErrorCode("help_command"); - Main.getLogger().error("Unable to locate command \"" + commandName + "\" for help command. Error code: " + errorCode); + languageManager.addContext(ContextManager.ContextType.GENERIC, "errorcode", errorCode); + String errorLangString = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Regular.HelpEmbed.Error"); + Main.getLogger().error(errorLangString); return ErrorEmbed.getError(errorCode); } else { return new EmbedBuilder() .setColor(Main.getColor(userID)) .setAuthor(commandName.toUpperCase()) .setDescription(command.getOverview().isEmpty() ? command.getHelp() : command.getOverview()) - .addField("Command", "Usage\n```" + command.getUsage() + "```"); + .addField(commandsLangString, usageLangString + "\n```" + command.getUsage() + "```"); } } } diff --git a/src/main/java/com/sidpatchy/clairebot/Lang/ContextManager.kt b/src/main/java/com/sidpatchy/clairebot/Lang/ContextManager.kt index 52d33f0..4db4e91 100644 --- a/src/main/java/com/sidpatchy/clairebot/Lang/ContextManager.kt +++ b/src/main/java/com/sidpatchy/clairebot/Lang/ContextManager.kt @@ -17,7 +17,7 @@ data class ContextManager( enum class ContextType { POLL, SANTA, - // Add other types as needed + GENERIC } // Add dynamic data with type safety diff --git a/src/main/java/com/sidpatchy/clairebot/Lang/LanguageManager.java b/src/main/java/com/sidpatchy/clairebot/Lang/LanguageManager.java index f6bc7f4..303ab0e 100644 --- a/src/main/java/com/sidpatchy/clairebot/Lang/LanguageManager.java +++ b/src/main/java/com/sidpatchy/clairebot/Lang/LanguageManager.java @@ -166,4 +166,8 @@ private RobinConfiguration tryLoadConfig(File file) { return null; } } + + public void addContext(ContextManager.ContextType contextType, String key, Object Data) { + context.addData(contextType, key, Data); + } } diff --git a/src/main/java/com/sidpatchy/clairebot/Listener/SlashCommandCreate.java b/src/main/java/com/sidpatchy/clairebot/Listener/SlashCommandCreate.java index d0c9596..2313be8 100644 --- a/src/main/java/com/sidpatchy/clairebot/Listener/SlashCommandCreate.java +++ b/src/main/java/com/sidpatchy/clairebot/Listener/SlashCommandCreate.java @@ -107,7 +107,7 @@ else if (commandName.equalsIgnoreCase(commands.getHelp().getName())) { try { slashCommandInteraction.createImmediateResponder() - .addEmbed(HelpEmbed.getHelp(command, user.getIdAsString())) + .addEmbed(HelpEmbed.getHelp(languageManager, command, user.getIdAsString())) .respond(); } catch (FileNotFoundException e) { Main.getLogger().error(e); diff --git a/src/main/resources/translations/lang_en-US.yml b/src/main/resources/translations/lang_en-US.yml index 4622d55..9174743 100644 --- a/src/main/resources/translations/lang_en-US.yml +++ b/src/main/resources/translations/lang_en-US.yml @@ -143,81 +143,83 @@ ClaireLang: - "*Bringing the best, always! 🌟*" - "*Excellence. 💯*" # Note: The rest of the help is located separately within commands_en-US.yml - HelpEmbed: - Commands: "Commands" - InfoEmbed: - NeedHelp: "Need Help?" - NeedHelpDetails: "You can get help by creating an issue on our [GitHub](https://github.com/Sidpatchy/ClaireBot/issues) or by joining our [support server](https://support.clairebot.net/)" - AddToServer: "Add Me to a Server" - AddToServerDetails: "Adding me to a server is simple, all you have to do is click [here](https://invite.clairebot.net)" - GitHub: "GitHub" - GitHubDetails: "ClaireBot is open source, that means you can view all of its code! Check out its [GitHub!](https://github.com/Sidpatchy/ClaireBot)" - ServerCount: "Server Count" - ServerCountDetails: "I have enlightened **{clairebot.placeholder.numservers}** servers." - Version: "Version" - VersionDetails: "I am running ClaireBot **v{clairebot.placeholder.version}**, released on **{clairebot.placeholder.releasedate}**" - Uptime: "Uptime" - UptimeValue: "Started on \n*{clairebot.placeholder.runtimedurationwords}*" - LeaderboardEmbed: - LeaderboardForServer: "Leaderboard for" - GlobalLeaderboard: "Global Leaderboard" - LevelEmbed: "" # Unused - QuoteEmbed: "" # Unused - SantaEmbed: - Confirmation: "Confirmed! I've sent you a direct message. Please continue there." - RulesButton: "Add rules" - ThemeButton: "Add a theme" - SendButton: "Send messages" - TestButton: "Send Sample" - RandomizeButton: "Re-randomize" - SentByAuthor: "Sent by" - GiverMessage: "Ho! Ho! Ho! You have recieved **{clairebot.placeholder.user.id.displayname.server}** in the {clairebot.placeholder.serverid.name} Secret Santa!" - ServerInfoEmbed: - ServerID: "Server ID" - Owner: "Owner" - RoleCount: "Role Count" - MemberCount: "Member Count" - ChannelCounts: "Channel Counts" - Categories: "Categories" - TextChannels: "Text Channels" - VoiceChannels: "Voice Channels" - ServerPreferencesEmbed: - MainMenuTitle: "Server Configuration Editor" - NotAServer: "You must run this command inside a server!" - RequestsChannelMenuName: "Requests Channel" - RequestsChannelDescription: "Only lists the first 25 channels in the server." - ModeratorChannelMenuName: "Moderator Messages Channel" - ModeratorChannelDescription: "Only lists the first 25 channels in the server." - EnforceServerLanguageMenuName: "Enforce Server Language" - AcknowledgeRequestsChannelChangeTitle: "Requests Channel Changed!" - AcknowledgeRequestsChannelChangeDescription: "Your requests channel has been changed to {clairebot.placeholder.channel.id.mentiontag}" - AcknowledgeModeratorChannelChangeTitle: "Moderator Channel Changed!" - AcknowledgeModeratorChannelChangeDescription: "Your moderator messages channel has been changed to {clairebot.placeholder.channel.id.mentiontag}" - AcknowledgeEnforceServerLanguageUpdateTitle: "Server Language Preferences Updated!" - AcknowledgeEnforceServerLanguageUpdateEnforced: "I will now follow the server's language regardless of user preference." - AcknowledgeEnforceServerLanguageUpdateNotEnforced: "I will follow each individual user's language preference." - UserInfoEmbed: - Error_1: "The value for author was null when passed into UserInfo Embed. Error code: {clairebot.placeholder.errorcode}" - Error_2: "Author: {clairebot.placeholder.user.id.username}" - User: "User" - DiscordID: "Discord ID" - JoinDate: "Server Join Date" - CreationDate: "Account Creation Date" - UserPreferencesEmbed: - MainMenuText: "User Preferences Editor" - AccentColourMenu: "Accent Colour Editor" - AccentColourList: "Accent Colour List" - AccentColourChanged: "Acccent Colour Changed!" - AccentColourChangedDesc: "Your accent colour has been changed to {clairebot.placeholder.user.id.accentcolour}" - LanguageMenuTitle: "NYI" - LanguageMenuDesc: "Sorry, this feature is not yet implemented." - VotingEmbed: - PollRequest: "{clairebot.placeholder.user.id.displayname.server} requests:" - PollAsk: "{clairebot.placeholder.user.id.displayname.server} asks:" - Choices: "Choices" - PollID: "Poll ID: {clairebot.placeholder.poll.id}" - UserResponseTitle: "Your request has been created!" - UserResponseDescription: "Go check it out in {clairebot.placeholder.channel.id.mentiontag}" + HelpEmbed: + Commands: "Commands" + Usage: "Usage" + Error: "Unable to locate help data for \"{clairebot.placeholder.help.commandname}\". Error code: {clairebot.placeholder.generic.errorcode}" + InfoEmbed: + NeedHelp: "Need Help?" + NeedHelpDetails: "You can get help by creating an issue on our [GitHub](https://github.com/Sidpatchy/ClaireBot/issues) or by joining our [support server](https://support.clairebot.net/)" + AddToServer: "Add Me to a Server" + AddToServerDetails: "Adding me to a server is simple, all you have to do is click [here](https://invite.clairebot.net)" + GitHub: "GitHub" + GitHubDetails: "ClaireBot is open source, that means you can view all of its code! Check out its [GitHub!](https://github.com/Sidpatchy/ClaireBot)" + ServerCount: "Server Count" + ServerCountDetails: "I have enlightened **{clairebot.placeholder.numservers}** servers." + Version: "Version" + VersionDetails: "I am running ClaireBot **v{clairebot.placeholder.version}**, released on **{clairebot.placeholder.releasedate}**" + Uptime: "Uptime" + UptimeValue: "Started on \n*{clairebot.placeholder.runtimedurationwords}*" + LeaderboardEmbed: + LeaderboardForServer: "Leaderboard for" + GlobalLeaderboard: "Global Leaderboard" + LevelEmbed: "" # Unused + QuoteEmbed: "" # Unused + SantaEmbed: + Confirmation: "Confirmed! I've sent you a direct message. Please continue there." + RulesButton: "Add rules" + ThemeButton: "Add a theme" + SendButton: "Send messages" + TestButton: "Send Sample" + RandomizeButton: "Re-randomize" + SentByAuthor: "Sent by" + GiverMessage: "Ho! Ho! Ho! You have recieved **{clairebot.placeholder.user.id.displayname.server}** in the {clairebot.placeholder.serverid.name} Secret Santa!" + ServerInfoEmbed: + ServerID: "Server ID" + Owner: "Owner" + RoleCount: "Role Count" + MemberCount: "Member Count" + ChannelCounts: "Channel Counts" + Categories: "Categories" + TextChannels: "Text Channels" + VoiceChannels: "Voice Channels" + ServerPreferencesEmbed: + MainMenuTitle: "Server Configuration Editor" + NotAServer: "You must run this command inside a server!" + RequestsChannelMenuName: "Requests Channel" + RequestsChannelDescription: "Only lists the first 25 channels in the server." + ModeratorChannelMenuName: "Moderator Messages Channel" + ModeratorChannelDescription: "Only lists the first 25 channels in the server." + EnforceServerLanguageMenuName: "Enforce Server Language" + AcknowledgeRequestsChannelChangeTitle: "Requests Channel Changed!" + AcknowledgeRequestsChannelChangeDescription: "Your requests channel has been changed to {clairebot.placeholder.channel.id.mentiontag}" + AcknowledgeModeratorChannelChangeTitle: "Moderator Channel Changed!" + AcknowledgeModeratorChannelChangeDescription: "Your moderator messages channel has been changed to {clairebot.placeholder.channel.id.mentiontag}" + AcknowledgeEnforceServerLanguageUpdateTitle: "Server Language Preferences Updated!" + AcknowledgeEnforceServerLanguageUpdateEnforced: "I will now follow the server's language regardless of user preference." + AcknowledgeEnforceServerLanguageUpdateNotEnforced: "I will follow each individual user's language preference." + UserInfoEmbed: + Error_1: "The value for author was null when passed into UserInfo Embed. Error code: {clairebot.placeholder.errorcode}" + Error_2: "Author: {clairebot.placeholder.user.id.username}" + User: "User" + DiscordID: "Discord ID" + JoinDate: "Server Join Date" + CreationDate: "Account Creation Date" + UserPreferencesEmbed: + MainMenuText: "User Preferences Editor" + AccentColourMenu: "Accent Colour Editor" + AccentColourList: "Accent Colour List" + AccentColourChanged: "Acccent Colour Changed!" + AccentColourChangedDesc: "Your accent colour has been changed to {clairebot.placeholder.user.id.accentcolour}" + LanguageMenuTitle: "NYI" + LanguageMenuDesc: "Sorry, this feature is not yet implemented." + VotingEmbed: + PollRequest: "{clairebot.placeholder.user.id.displayname.server} requests:" + PollAsk: "{clairebot.placeholder.user.id.displayname.server} asks:" + Choices: "Choices" + PollID: "Poll ID: {clairebot.placeholder.poll.id}" + UserResponseTitle: "Your request has been created!" + UserResponseDescription: "Go check it out in {clairebot.placeholder.channel.id.mentiontag}" ErrorEmbed: Error: "ERROR" GenericDescription: "It appears that I've encountered an error, oops! Please try running the command once more and if that doesn't work, join my [Discord server]({cb.supportserver) and let us know about the issue.\n\nPlease include the following error code: {cb.errorcode}" From e4be84d8c6202923bd91adddd9b66ace7efecfb9 Mon Sep 17 00:00:00 2001 From: Sidpatchy <35241490+Sidpatchy@users.noreply.github.com> Date: Wed, 19 Feb 2025 16:31:47 -0700 Subject: [PATCH 17/64] docs: add code of conduct --- code_of_conduct.md | 134 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 134 insertions(+) create mode 100644 code_of_conduct.md diff --git a/code_of_conduct.md b/code_of_conduct.md new file mode 100644 index 0000000..ae45e3f --- /dev/null +++ b/code_of_conduct.md @@ -0,0 +1,134 @@ + +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, caste, color, religion, or sexual +identity and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the overall + community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or advances of + any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email address, + without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official email address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +`conduct@sidpatchy.com` or `@sidpatchy` on the Discord. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series of +actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or permanent +ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within the +community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.1, available at +[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. + +Community Impact Guidelines were inspired by +[Mozilla's code of conduct enforcement ladder][Mozilla CoC]. + +For answers to common questions about this code of conduct, see the FAQ at +[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at +[https://www.contributor-covenant.org/translations][translations]. + +[homepage]: https://www.contributor-covenant.org +[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html +[Mozilla CoC]: https://github.com/mozilla/diversity +[FAQ]: https://www.contributor-covenant.org/faq +[translations]: https://www.contributor-covenant.org/translations + From bd411c4aded671a35312a5de4da03af9feab9952 Mon Sep 17 00:00:00 2001 From: Sidpatchy <35241490+Sidpatchy@users.noreply.github.com> Date: Wed, 30 Apr 2025 19:45:06 -0600 Subject: [PATCH 18/64] fix: make ClaireBot build again --- src/main/java/com/sidpatchy/clairebot/Commands.java | 13 +++++++++++++ .../clairebot/Embed/Commands/Regular/HelpEmbed.java | 11 +++-------- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/sidpatchy/clairebot/Commands.java b/src/main/java/com/sidpatchy/clairebot/Commands.java index 4314342..81c629c 100644 --- a/src/main/java/com/sidpatchy/clairebot/Commands.java +++ b/src/main/java/com/sidpatchy/clairebot/Commands.java @@ -3,6 +3,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.sidpatchy.Robin.Discord.Command; +import java.util.List; import java.util.Objects; /** @@ -25,6 +26,18 @@ public class Commands { @JsonProperty("config-revision") private String configRevision; + /** + * Retrieves a list of all available commands. + * + * @return a list containing all Command objects. + */ + public List getAllCommands() { + return List.of( + avatar, config, eightball, help, info, leaderboard, + level, poll, quote, request, santa, server, user + ); + } + public Command getAvatar() { validateCommand(avatar); return avatar; diff --git a/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/HelpEmbed.java b/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/HelpEmbed.java index 829f446..4b6679f 100644 --- a/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/HelpEmbed.java +++ b/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/HelpEmbed.java @@ -27,14 +27,9 @@ public static EmbedBuilder getHelp(LanguageManager languageManager, String comma HashMap allCommands = new HashMap<>(); HashMap regularCommands = new HashMap<>(); - for (Field field : commands.getClass().getDeclaredFields()) { - try { - Command command = field.get(commands); - allCommands.put(field.getName(), command); - regularCommands.put(field.getName(), command); - } catch (IllegalAccessException e) { - // todo handle the exception - } + for (Command command : commands.getAllCommands()) { + allCommands.put(command.getName(), command); + regularCommands.put(command.getName(), command); } if (commandName.equalsIgnoreCase("help")) { From b348b04e59b367d7672cc90c3b896ae9903bc866 Mon Sep 17 00:00:00 2001 From: Sidpatchy <35241490+Sidpatchy@users.noreply.github.com> Date: Wed, 30 Apr 2025 21:18:47 -0600 Subject: [PATCH 19/64] feat: more progress on clairelang implementation --- build.gradle | 11 +++- .../Embed/Commands/Regular/HelpEmbed.java | 1 - .../Embed/Commands/Regular/InfoEmbed.java | 52 +++++++++++++++--- .../clairebot/Lang/PlaceholderHandler.java | 55 ++++++++++++++----- .../Listener/SlashCommandCreate.java | 2 +- .../java/com/sidpatchy/clairebot/Main.java | 52 +++++++++++++++++- src/main/resources/build.properties | 7 +++ .../resources/translations/lang_en-US.yml | 10 ++-- 8 files changed, 158 insertions(+), 32 deletions(-) create mode 100644 src/main/resources/build.properties diff --git a/build.gradle b/build.gradle index 06848c8..b9aa486 100644 --- a/build.gradle +++ b/build.gradle @@ -12,7 +12,16 @@ jar { } group 'com.sidpatchy' -version '3.4.0' +version '3.4.0-SNAPSHOT' + +processResources { + filesMatching('**/build.properties') { + expand( + version: project.version, + buildDate: new Date().format("yyyy-MM-dd HH:mm:ss") + ) + } +} kotlin { jvmToolchain(17) diff --git a/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/HelpEmbed.java b/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/HelpEmbed.java index 4b6679f..92da24a 100644 --- a/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/HelpEmbed.java +++ b/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/HelpEmbed.java @@ -9,7 +9,6 @@ import org.javacord.api.entity.message.embed.EmbedBuilder; import java.io.FileNotFoundException; -import java.lang.reflect.Field; import java.util.HashMap; public class HelpEmbed { diff --git a/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/InfoEmbed.java b/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/InfoEmbed.java index 4140fe4..6cfbc97 100644 --- a/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/InfoEmbed.java +++ b/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/InfoEmbed.java @@ -1,20 +1,54 @@ package com.sidpatchy.clairebot.Embed.Commands.Regular; +import com.sidpatchy.clairebot.Lang.ContextManager; +import com.sidpatchy.clairebot.Lang.LanguageManager; import com.sidpatchy.clairebot.Main; import org.apache.commons.lang3.time.DurationFormatUtils; import org.javacord.api.entity.message.embed.EmbedBuilder; import org.javacord.api.entity.user.User; public class InfoEmbed { - public static EmbedBuilder getInfo(User author) { - String timeSinceStart = DurationFormatUtils.formatDurationWords(System.currentTimeMillis() - Main.getStartMillis(), true, false); + public static EmbedBuilder getInfo(LanguageManager languageManager, User author) { + // Add required context data + languageManager.addContext(ContextManager.ContextType.GENERIC, "numservers", Main.getApi().getServers().size()); + languageManager.addContext(ContextManager.ContextType.GENERIC, "version", "v3.4.0"); + languageManager.addContext(ContextManager.ContextType.GENERIC, "releasedate", "2025-05-01"); + languageManager.addContext(ContextManager.ContextType.GENERIC, "startseconds", Main.getStartMillis() / 1000); + languageManager.addContext(ContextManager.ContextType.GENERIC, "runtimedurationwords", + DurationFormatUtils.formatDurationWords(System.currentTimeMillis() - Main.getStartMillis(), true, false)); + return new EmbedBuilder() .setColor(Main.getColor(author.getIdAsString())) - .addField("Need Help?", "You can get help by creating an issue on our [GitHub](https://github.com/Sidpatchy/ClaireBot/issues) or by joining our [support server](https://support.clairebot.net/)", true) - .addField("Add Me to a Server", "Adding me to a server is simple, all you have to do is click [here](https://invite.clairebot.net)", true) - .addField("GitHub", "ClaireBot is open source, that means you can view all of its code! Check out its [GitHub!](https://github.com/Sidpatchy/ClaireBot)", true) - .addField("Server Count", "I have enlightened **" + Main.getApi().getServers().size() + "** servers.", true) - .addField("Version", "I am running ClaireBot **v3.3.2**, released on **2024-08-22**", true) - .addField("Uptime", "Started on " + "\n*" + timeSinceStart + "*", true); + .addField( + languageManager.getLocalizedString("ClaireLang.Embed.Commands.Regular.InfoEmbed.NeedHelp"), + languageManager.getLocalizedString("ClaireLang.Embed.Commands.Regular.InfoEmbed.NeedHelpDetails"), + true + ) + .addField( + languageManager.getLocalizedString("ClaireLang.Embed.Commands.Regular.InfoEmbed.AddToServer"), + languageManager.getLocalizedString("ClaireLang.Embed.Commands.Regular.InfoEmbed.AddToServerDetails"), + true + ) + .addField( + languageManager.getLocalizedString("ClaireLang.Embed.Commands.Regular.InfoEmbed.GitHub"), + languageManager.getLocalizedString("ClaireLang.Embed.Commands.Regular.InfoEmbed.GitHubDetails"), + true + ) + .addField( + languageManager.getLocalizedString("ClaireLang.Embed.Commands.Regular.InfoEmbed.ServerCount"), + languageManager.getLocalizedString("ClaireLang.Embed.Commands.Regular.InfoEmbed.ServerCountDetails"), + true + ) + .addField( + languageManager.getLocalizedString("ClaireLang.Embed.Commands.Regular.InfoEmbed.Version"), + languageManager.getLocalizedString("ClaireLang.Embed.Commands.Regular.InfoEmbed.VersionDetails"), + true + ) + .addField( + languageManager.getLocalizedString("ClaireLang.Embed.Commands.Regular.InfoEmbed.Uptime"), + languageManager.getLocalizedString("ClaireLang.Embed.Commands.Regular.InfoEmbed.UptimeValue"), + true + ); } -} \ No newline at end of file +} + diff --git a/src/main/java/com/sidpatchy/clairebot/Lang/PlaceholderHandler.java b/src/main/java/com/sidpatchy/clairebot/Lang/PlaceholderHandler.java index a1a275b..6068a53 100644 --- a/src/main/java/com/sidpatchy/clairebot/Lang/PlaceholderHandler.java +++ b/src/main/java/com/sidpatchy/clairebot/Lang/PlaceholderHandler.java @@ -1,5 +1,7 @@ package com.sidpatchy.clairebot.Lang; +import com.sidpatchy.clairebot.Main; +import org.apache.commons.lang3.time.DurationFormatUtils; import org.javacord.api.entity.channel.Channel; import org.javacord.api.entity.channel.ServerChannel; @@ -9,6 +11,8 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; +import static java.util.Map.entry; + public class PlaceholderHandler { private final ContextManager context; private final Map placeholders; @@ -16,39 +20,62 @@ public class PlaceholderHandler { // Functional interface for placeholder value providers @FunctionalInterface private interface PlaceholderProvider { - String getValue(); + Object getRawValue(); // Returns any type + + default String getValue() { + return String.valueOf(getRawValue()); + } } + public PlaceholderHandler(ContextManager context) { this.context = context; this.placeholders = initializePlaceholders(); } private Map initializePlaceholders() { + return Map.ofEntries( + // Project placeholders + entry("cb.invitelink", Main::getInviteLink), + entry("cb.docs", Main::getDocumentationWebsite), + entry("cb.website", Main::getWebsite), + entry("cb.github", Main::getGithub), + entry("cb.supportserver", Main::getSupportServer), + + // Bot placeholders + entry("cb.bot.numservers", () -> Main.getApi().getServers().size()), + entry("cb.bot.version", Main::getBuildVersion), + entry("cb.bot.releasedate", Main::getBuildDate), + entry("cb.bot.startseconds", () -> Main.getStartMillis() / 1000), + entry("cb.bot.runtimedurationwords", () -> DurationFormatUtils.formatDurationWords(System.currentTimeMillis() - Main.getStartMillis(), true, false)), - return Map.of( // Server placeholders - "cb.server.name", () -> - context.getServer() != null ? context.getServer().getName() : "", "cb.server.id", () -> - context.getServer() != null ? context.getServer().getIdAsString() : "", + entry("cb.server.name", () -> + context.getServer() != null ? context.getServer().getName() : ""), + entry("cb.server.id", () -> + context.getServer() != null ? context.getServer().getIdAsString() : ""), // User placeholders - "cb.user.name", () -> - context.getUser() != null ? context.getUser().getName() : "", "cb.user.id", () -> - context.getUser() != null ? context.getUser().getIdAsString() : "", + entry("cb.user.name", () -> + context.getUser() != null ? context.getUser().getName() : ""), + entry("cb.user.id", () -> + context.getUser() != null ? context.getUser().getIdAsString() : ""), // Author placeholders - "cb.author.name", () -> - context.getAuthor() != null ? context.getAuthor().getName() : "", "cb.author.id", () -> - context.getAuthor() != null ? context.getAuthor().getIdAsString() : "", + entry("cb.author.name", () -> + context.getAuthor() != null ? context.getAuthor().getName() : ""), + entry("cb.author.id", () -> + context.getAuthor() != null ? context.getAuthor().getIdAsString() : ""), // Channel placeholders - "cb.channel.name", () -> + entry("cb.channel.name", () -> Optional.ofNullable(context.getChannel()) .flatMap(Channel::asServerChannel) .map(ServerChannel::getName) - .orElse("NOT FOUND"), "cb.channel.id", () -> - context.getChannel() != null ? context.getChannel().getIdAsString() : ""); + .orElse("NOT FOUND")), + entry("cb.channel.id", () -> + context.getChannel() != null ? context.getChannel().getIdAsString() : "") + ); } /** diff --git a/src/main/java/com/sidpatchy/clairebot/Listener/SlashCommandCreate.java b/src/main/java/com/sidpatchy/clairebot/Listener/SlashCommandCreate.java index 2313be8..6786464 100644 --- a/src/main/java/com/sidpatchy/clairebot/Listener/SlashCommandCreate.java +++ b/src/main/java/com/sidpatchy/clairebot/Listener/SlashCommandCreate.java @@ -116,7 +116,7 @@ else if (commandName.equalsIgnoreCase(commands.getHelp().getName())) { } else if (commandName.equalsIgnoreCase(commands.getInfo().getName())) { slashCommandInteraction.createImmediateResponder() - .addEmbed(InfoEmbed.getInfo(author)) + .addEmbed(InfoEmbed.getInfo(languageManager, author)) .respond(); } else if (commandName.equalsIgnoreCase(commands.getLeaderboard().getName())) { diff --git a/src/main/java/com/sidpatchy/clairebot/Main.java b/src/main/java/com/sidpatchy/clairebot/Main.java index c33704c..2efb992 100644 --- a/src/main/java/com/sidpatchy/clairebot/Main.java +++ b/src/main/java/com/sidpatchy/clairebot/Main.java @@ -15,9 +15,11 @@ import java.awt.*; import java.io.IOException; +import java.io.InputStream; import java.util.Arrays; import java.util.List; import java.util.Map; +import java.util.Properties; /** * ClaireBot - Simply the best. @@ -80,6 +82,19 @@ public class Main { private static final String translationsPath = "config/translations/"; private static RobinConfiguration config; private static Commands commands; + private static final Properties buildProperties = new Properties() {{ + try (InputStream input = Main.class.getClassLoader().getResourceAsStream("build.properties")) { + if (input != null) load(input); + else System.err.println("build.properties missing!"); + } catch (IOException e) { throw new RuntimeException("Failed to load build.properties", e); } + }}; + private static String buildVersion; + private static String buildDate; + private static String github; + private static String supportServer; + private static String website; + private static String documentationWebsite; + private static String inviteLink; public static void main(String[] args) throws InvalidConfigurationException { logger.info("ClaireBot loading..."); @@ -118,7 +133,7 @@ public static void main(String[] args) throws InvalidConfigurationException { Clockwork.initClockwork(); // Set the bot's activity - api.updateActivity("ClaireBot v3.4.0-SNAPSHOT", video_url); + api.updateActivity("ClaireBot " + buildVersion, video_url); // Register slash commands registerSlashCommands(); @@ -190,6 +205,13 @@ public static void extractParametersFromConfig(boolean updateOutdatedConfigs) { onTopTriggers = config.getList("OnTopTriggers", String.class); plsBanResponses = config.getList("PlsBanResponses", String.class); plsBanTriggers = config.getList("PlsBanTriggers", String.class); + buildVersion = buildProperties.getProperty("clairebot.version"); + buildDate = buildProperties.getProperty("clairebot.buildDate"); + github = buildProperties.getProperty("clairebot.github"); + supportServer = buildProperties.getProperty("clairebot.supportServer"); + website = buildProperties.getProperty("clairebot.website"); + documentationWebsite = buildProperties.getProperty("clairebot.documentationWebsite"); + inviteLink = config.getString("clairebot.inviteLink"); } catch (Exception e) { e.printStackTrace(); @@ -338,4 +360,32 @@ public static String getErrorCode(String descriptor) { public static String getTranslationsPath() { return translationsPath; } + + public static String getBuildVersion() { + return buildVersion; + } + + public static String getBuildDate() { + return buildDate; + } + + public static String getGithub() { + return github; + } + + public static String getSupportServer() { + return supportServer; + } + + public static String getWebsite() { + return website; + } + + public static String getDocumentationWebsite() { + return documentationWebsite; + } + + public static String getInviteLink() { + return inviteLink; + } } diff --git a/src/main/resources/build.properties b/src/main/resources/build.properties new file mode 100644 index 0000000..c2ab455 --- /dev/null +++ b/src/main/resources/build.properties @@ -0,0 +1,7 @@ +clairebot.version=${version} +clairebot.buildDate=${buildDate} +clairebot.github=https://github.com/Sidpatchy/ClaireBot +clairebot.supportServer=https://support.clairebot.net/ +clairebot.inviteLink=https://invite.clairebot.net/ +clairebot.website="https://www.clairebot.net/ +clairebot.documentationWebsite=https://docs.clairebot.net/ \ No newline at end of file diff --git a/src/main/resources/translations/lang_en-US.yml b/src/main/resources/translations/lang_en-US.yml index 9174743..647412e 100644 --- a/src/main/resources/translations/lang_en-US.yml +++ b/src/main/resources/translations/lang_en-US.yml @@ -151,15 +151,15 @@ ClaireLang: NeedHelp: "Need Help?" NeedHelpDetails: "You can get help by creating an issue on our [GitHub](https://github.com/Sidpatchy/ClaireBot/issues) or by joining our [support server](https://support.clairebot.net/)" AddToServer: "Add Me to a Server" - AddToServerDetails: "Adding me to a server is simple, all you have to do is click [here](https://invite.clairebot.net)" + AddToServerDetails: "Adding me to a server is simple, all you have to do is click [here]({cb.supportserver})" GitHub: "GitHub" - GitHubDetails: "ClaireBot is open source, that means you can view all of its code! Check out its [GitHub!](https://github.com/Sidpatchy/ClaireBot)" + GitHubDetails: "ClaireBot is open source, that means you can view all of its code! Check out its [GitHub!]({cb.github})" ServerCount: "Server Count" - ServerCountDetails: "I have enlightened **{clairebot.placeholder.numservers}** servers." + ServerCountDetails: "I have enlightened **{cb.bot.numservers}** servers." Version: "Version" - VersionDetails: "I am running ClaireBot **v{clairebot.placeholder.version}**, released on **{clairebot.placeholder.releasedate}**" + VersionDetails: "I am running ClaireBot **v{cb.bot.version}**, released on **{cb.bot.releasedate}**" Uptime: "Uptime" - UptimeValue: "Started on \n*{clairebot.placeholder.runtimedurationwords}*" + UptimeValue: "Started on \n*{cb.bot.runtimedurationwords}*" LeaderboardEmbed: LeaderboardForServer: "Leaderboard for" GlobalLeaderboard: "Global Leaderboard" From dd1e74f6e980799fcb7df062e4c05f43a0dcd973 Mon Sep 17 00:00:00 2001 From: Sidpatchy <35241490+Sidpatchy@users.noreply.github.com> Date: Thu, 1 May 2025 09:43:38 -0600 Subject: [PATCH 20/64] fix: remove my attempt at making AI do this Major L on my part --- .../clairebot/Embed/Commands/Regular/InfoEmbed.java | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/InfoEmbed.java b/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/InfoEmbed.java index 6cfbc97..bcc244b 100644 --- a/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/InfoEmbed.java +++ b/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/InfoEmbed.java @@ -9,14 +9,6 @@ public class InfoEmbed { public static EmbedBuilder getInfo(LanguageManager languageManager, User author) { - // Add required context data - languageManager.addContext(ContextManager.ContextType.GENERIC, "numservers", Main.getApi().getServers().size()); - languageManager.addContext(ContextManager.ContextType.GENERIC, "version", "v3.4.0"); - languageManager.addContext(ContextManager.ContextType.GENERIC, "releasedate", "2025-05-01"); - languageManager.addContext(ContextManager.ContextType.GENERIC, "startseconds", Main.getStartMillis() / 1000); - languageManager.addContext(ContextManager.ContextType.GENERIC, "runtimedurationwords", - DurationFormatUtils.formatDurationWords(System.currentTimeMillis() - Main.getStartMillis(), true, false)); - return new EmbedBuilder() .setColor(Main.getColor(author.getIdAsString())) .addField( From 87d19795128a1b6216520b1db6a519066f5ba8f2 Mon Sep 17 00:00:00 2001 From: Sidpatchy <35241490+Sidpatchy@users.noreply.github.com> Date: Tue, 6 May 2025 09:52:40 -0600 Subject: [PATCH 21/64] feat: more localization work --- .../Commands/Regular/LeaderboardEmbed.java | 14 ++++++--- .../Embed/Commands/Regular/QuoteEmbed.java | 8 +++-- .../Embed/Commands/Regular/SantaEmbed.java | 29 ++++++++++++------- .../clairebot/Lang/ContextManager.kt | 3 +- .../clairebot/Lang/LanguageManager.java | 22 ++++++++++++++ .../clairebot/Listener/ButtonClick.java | 9 +++++- .../Listener/SlashCommandCreate.java | 8 ++--- .../java/com/sidpatchy/clairebot/Main.java | 10 +++++-- src/main/resources/config.yml | 5 ++++ .../resources/translations/lang_en-US.yml | 6 ++-- 10 files changed, 85 insertions(+), 29 deletions(-) diff --git a/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/LeaderboardEmbed.java b/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/LeaderboardEmbed.java index 76e9080..5717208 100644 --- a/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/LeaderboardEmbed.java +++ b/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/LeaderboardEmbed.java @@ -1,6 +1,7 @@ package com.sidpatchy.clairebot.Embed.Commands.Regular; import com.sidpatchy.clairebot.Embed.ErrorEmbed; +import com.sidpatchy.clairebot.Lang.LanguageManager; import com.sidpatchy.clairebot.Main; import com.sidpatchy.clairebot.Util.Leveling.LevelingTools; import org.javacord.api.entity.message.embed.EmbedBuilder; @@ -15,7 +16,10 @@ public class LeaderboardEmbed { - public static EmbedBuilder getLeaderboard(Server server, User author) { + public static EmbedBuilder getLeaderboard(LanguageManager languageManager, Server server, User author) { + // Language Strings + String leaderboardFor = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Regular.LeaderboardEmbed.LeaderboardForServer"); + String serverID = server.getIdAsString(); HashMap unsortedLevelMap = null; @@ -32,12 +36,14 @@ public static EmbedBuilder getLeaderboard(Server server, User author) { Map sortedLevelMap = sortMap(namedMap); EmbedBuilder embed = initializeLeaderboardEmbed(sortedLevelMap, author); - embed.setAuthor("Leaderboard for " + server.getName(), "", server.getIcon().orElse(null)); + embed.setAuthor(leaderboardFor + " " + server.getName(), "", server.getIcon().orElse(null)); return embed; } - public static EmbedBuilder getLeaderboard(String serverID, User author) { + public static EmbedBuilder getLeaderboard(LanguageManager languageManager, String serverID, User author) { + String globalLeaderboard = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Regular.LeaderboardEmbed.GlobalLeaderboard"); + HashMap unsortedLevelMap; try { unsortedLevelMap = LevelingTools.rankUsers(serverID); @@ -52,7 +58,7 @@ public static EmbedBuilder getLeaderboard(String serverID, User author) { Map sortedLevelMap = sortMap(namedMap); EmbedBuilder embed = initializeLeaderboardEmbed(sortedLevelMap, author); - embed.setAuthor("Global Leaderboard"); + embed.setAuthor(globalLeaderboard); return embed; } diff --git a/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/QuoteEmbed.java b/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/QuoteEmbed.java index 49252c8..917bb29 100644 --- a/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/QuoteEmbed.java +++ b/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/QuoteEmbed.java @@ -1,6 +1,7 @@ package com.sidpatchy.clairebot.Embed.Commands.Regular; import com.sidpatchy.clairebot.Embed.ErrorEmbed; +import com.sidpatchy.clairebot.Lang.LanguageManager; import com.sidpatchy.clairebot.Main; import com.sidpatchy.clairebot.Util.Cache.MessageCacheManager; import org.javacord.api.entity.channel.TextChannel; @@ -23,7 +24,7 @@ public class QuoteEmbed { * @param channel the text channel where the messages are located * @return a CompletableFuture that resolves to an EmbedBuilder containing the quote */ - public static CompletableFuture getQuote(Server server, final User user, TextChannel channel) { + public static CompletableFuture getQuote(LanguageManager languageManager, Server server, final User user, TextChannel channel) { return MessageCacheManager.queryMessageCache(channel, user).thenApply(userMessages -> { if (userMessages.isEmpty()) { @@ -76,11 +77,12 @@ public static CompletableFuture getQuote(Server server, final User }); } - public static EmbedBuilder viewOriginalMessageBuilder(TextChannel channel, Message message) { + public static EmbedBuilder viewOriginalMessageBuilder(LanguageManager languageManager, TextChannel channel, Message message) { EmbedFooter footer = message.getEmbeds().get(0).getFooter().orElse(null); Message quotedMessage = message.getApi().getMessageById(footer.getText().orElse(""), channel).join(); return new EmbedBuilder() - .addField("Click to jump to the original message:", quotedMessage.getLink().toString()); + .addField(languageManager.getLocalizedString("ClaireLang.Embed.Commands.Regular.QuoteEmbed.JumpToOriginal"), + quotedMessage.getLink().toString()); } } diff --git a/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/SantaEmbed.java b/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/SantaEmbed.java index e8a0aa8..51b2995 100644 --- a/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/SantaEmbed.java +++ b/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/SantaEmbed.java @@ -1,5 +1,7 @@ package com.sidpatchy.clairebot.Embed.Commands.Regular; +import com.sidpatchy.clairebot.Lang.ContextManager; +import com.sidpatchy.clairebot.Lang.LanguageManager; import com.sidpatchy.clairebot.Main; import com.sidpatchy.clairebot.Util.SantaUtils; import org.javacord.api.entity.message.MessageBuilder; @@ -14,11 +16,13 @@ public class SantaEmbed { - public static EmbedBuilder getConfirmationEmbed(User author) { + private static final String basePath = "ClaireLang.Embed.Commands.Regular.SantaEmbed"; + + public static EmbedBuilder getConfirmationEmbed(LanguageManager languageManager, User author) { return new EmbedBuilder() .setColor(Main.getColor(author.getIdAsString())) .setAuthor("SecretClaire", "", "https://github.com/Sidpatchy/ClaireBot/blob/main/img/ClaireBot-SantaHat.png?raw=true") - .setDescription("Confirmed! I've sent you a direct message. Please continue there."); + .setDescription(languageManager.getLocalizedString(basePath, "Confirmation")); } /** @@ -26,11 +30,11 @@ public static EmbedBuilder getConfirmationEmbed(User author) { * * @param role The group of users participating * @param author Author of the command. - * @param rules Rules for the exchange, seperated by \n. + * @param rules Rules for the exchange, separated by \n. * @param theme Theme for the exchange. * @return Message with components */ - public static MessageBuilder getHostMessage(Role role, User author, String rules, String theme) { + public static MessageBuilder getHostMessage(LanguageManager languageManager, Role role, User author, String rules, String theme) { Set users = role.getUsers(); Server server = role.getServer(); @@ -59,25 +63,25 @@ public static MessageBuilder getHostMessage(Role role, User author, String rules } ActionRow actionRow = ActionRow.of( - Button.primary("rules", "Add rules"), - Button.primary("theme", "Add a theme"), - Button.danger("send", "Send messages"), - Button.success("test", "Send sample"), - Button.secondary("randomize", "Re-randomize")); + Button.primary("rules", languageManager.getLocalizedString(basePath, "RulesButton")), + Button.primary("theme", languageManager.getLocalizedString(basePath, "ThemeButton")), + Button.danger("send", languageManager.getLocalizedString(basePath, "SendButton")), + Button.success("test", languageManager.getLocalizedString(basePath, "TestButton")), + Button.secondary("randomize", languageManager.getLocalizedString(basePath, "RandomizeButton"))); message.addEmbed(embed); message.addComponents(actionRow); return message; } - public static MessageBuilder getSantaMessage(Server server, User author, User giver, User receiver, String rules, String theme) { + public static MessageBuilder getSantaMessage(LanguageManager languageManager, Server server, User author, User giver, User receiver, String rules, String theme) { MessageBuilder message = new MessageBuilder(); EmbedBuilder embed = new EmbedBuilder() .setColor(Main.getColor(author.getIdAsString())) .setAuthor("SecretClaire", "", "https://github.com/Sidpatchy/ClaireBot/blob/main/img/ClaireBot-SantaHat.png?raw=true") - .setFooter("Sent by " + author.getName(), author.getAvatar()); + .setFooter(languageManager.getLocalizedString(basePath, "SentByAuthor") + " " + author.getName(), author.getAvatar()); if (!theme.isEmpty()) { embed.addField("Theme", theme, false); @@ -87,6 +91,9 @@ public static MessageBuilder getSantaMessage(Server server, User author, User gi embed.addField("Rules", rules, false); } + languageManager.addContext(ContextManager.ContextType.SANTA, "giver", giver.getDisplayName(server)); + languageManager.addContext(ContextManager.ContextType.SANTA, "receiver", receiver.getDisplayName(server)); + embed.setDescription("Ho! Ho! Ho! You have received **" + receiver.getDisplayName(server) + "** in the " + server.getName() + " Secret Santa!"); message.addEmbed(embed); diff --git a/src/main/java/com/sidpatchy/clairebot/Lang/ContextManager.kt b/src/main/java/com/sidpatchy/clairebot/Lang/ContextManager.kt index 4db4e91..a6fcd84 100644 --- a/src/main/java/com/sidpatchy/clairebot/Lang/ContextManager.kt +++ b/src/main/java/com/sidpatchy/clairebot/Lang/ContextManager.kt @@ -37,8 +37,9 @@ data class ContextManager( addData(ContextType.POLL, "question", question) } - fun addSantaData(giftee: User, theme: String) { + fun addSantaData(giftee: User, theme: String, rules: String) { addData(ContextType.SANTA, "giftee", giftee) addData(ContextType.SANTA, "theme", theme) + addData(ContextType.SANTA, "rules", rules) } } \ No newline at end of file diff --git a/src/main/java/com/sidpatchy/clairebot/Lang/LanguageManager.java b/src/main/java/com/sidpatchy/clairebot/Lang/LanguageManager.java index 303ab0e..62eb9a8 100644 --- a/src/main/java/com/sidpatchy/clairebot/Lang/LanguageManager.java +++ b/src/main/java/com/sidpatchy/clairebot/Lang/LanguageManager.java @@ -83,6 +83,17 @@ public String getLocalizedString(String key) { return placeholderHandler.process(rawLanguageString); } + /** + * Retrieves a localized string based on the provided base path and key. + * + * @param basePath the base path used to locate the language file or namespace + * @param key the key for the desired localized string + * @return the localized string if found, otherwise returns the concatenation of basePath and key + */ + public String getLocalizedString(String basePath, String key) { + return getLocalizedString(basePath + "." + key); + } + /** * Retrieves the localized string corresponding to the given key. * @@ -99,6 +110,17 @@ public List getLocalizedList(String key) { return placeholderHandler.process(rawLanguageString); } + /** + * Retrieves a localized list of strings based on the provided base path and key. + * + * @param basePath the base path used to locate the language file or namespace + * @param key the key for the desired localized list of strings + * @return a list of localized strings if found, otherwise returns a list containing the concatenation of basePath and key + */ + public List getLocalizedList(String basePath, String key) { + return getLocalizedList(basePath + "." + key); + } + private RobinConfiguration parseUserAndServerOptions(Server server, User user) { Locale locale; try { diff --git a/src/main/java/com/sidpatchy/clairebot/Listener/ButtonClick.java b/src/main/java/com/sidpatchy/clairebot/Listener/ButtonClick.java index 04b11e0..d2689c8 100644 --- a/src/main/java/com/sidpatchy/clairebot/Listener/ButtonClick.java +++ b/src/main/java/com/sidpatchy/clairebot/Listener/ButtonClick.java @@ -2,6 +2,8 @@ import com.sidpatchy.clairebot.Embed.Commands.Regular.QuoteEmbed; import com.sidpatchy.clairebot.Embed.Commands.Regular.SantaEmbed; +import com.sidpatchy.clairebot.Lang.ContextManager; +import com.sidpatchy.clairebot.Lang.LanguageManager; import com.sidpatchy.clairebot.Main; import com.sidpatchy.clairebot.MessageComponents.Regular.SantaModal; import com.sidpatchy.clairebot.Util.SantaUtils; @@ -17,22 +19,27 @@ import org.javacord.api.interaction.ButtonInteraction; import org.javacord.api.listener.interaction.ButtonClickListener; +import java.util.HashMap; + public class ButtonClick implements ButtonClickListener { @Override public void onButtonClick(ButtonClickEvent event) { ButtonInteraction buttonInteraction = event.getButtonInteraction(); + Server server = buttonInteraction.getServer().orElse(null); String buttonID = buttonInteraction.getCustomId().toLowerCase(); User buttonAuthor = buttonInteraction.getUser(); Message message = buttonInteraction.getMessage(); TextChannel channel = message.getChannel(); + ContextManager context = new ContextManager(server, channel, buttonAuthor, null, message, new HashMap<>()); + LanguageManager languageManager = new LanguageManager(Main.getFallbackLocale(), context); + Embed embed = buttonInteraction.getMessage().getEmbeds().get(0); EmbedFooter footer = embed.getFooter().orElse(null); // Extract data from embed fields SantaUtils.ExtractionResult extractionResult = null; - Server server = null; User author = null; if (!buttonID.equalsIgnoreCase("view_original")) { extractionResult = SantaUtils.extractDataFromEmbed(embed, footer); diff --git a/src/main/java/com/sidpatchy/clairebot/Listener/SlashCommandCreate.java b/src/main/java/com/sidpatchy/clairebot/Listener/SlashCommandCreate.java index 6786464..3b05b0c 100644 --- a/src/main/java/com/sidpatchy/clairebot/Listener/SlashCommandCreate.java +++ b/src/main/java/com/sidpatchy/clairebot/Listener/SlashCommandCreate.java @@ -33,7 +33,7 @@ public class SlashCommandCreate implements SlashCommandCreateListener { - private final Logger logger = Main.getLogger(); + private static final Logger logger = Main.getLogger(); private static final Commands commands = Main.getCommands(); private LanguageManager languageManager; @@ -49,7 +49,7 @@ public void onSlashCommandCreate(SlashCommandCreateEvent event) { ContextManager context = new ContextManager(server, textchannel, author, user, null, new HashMap<>()); // Todo replace reference to en-US with config file parameter - languageManager = new LanguageManager(Locale.forLanguageTag("en-US"), context); + languageManager = new LanguageManager(Main.getFallbackLocale(), context); if (commandName.equalsIgnoreCase(commands.getEightball().getName())) { String query = slashCommandInteraction.getArgumentStringValueByIndex(0).orElse(null); @@ -124,12 +124,12 @@ else if (commandName.equalsIgnoreCase(commands.getLeaderboard().getName())) { if (server == null || getGlobal) { slashCommandInteraction.createImmediateResponder() - .addEmbed(LeaderboardEmbed.getLeaderboard("global", author)) + .addEmbed(LeaderboardEmbed.getLeaderboard(languageManager, "global", author)) .respond(); } else { slashCommandInteraction.createImmediateResponder() - .addEmbed(LeaderboardEmbed.getLeaderboard(server, author)) + .addEmbed(LeaderboardEmbed.getLeaderboard(languageManager, server, author)) .respond(); } } diff --git a/src/main/java/com/sidpatchy/clairebot/Main.java b/src/main/java/com/sidpatchy/clairebot/Main.java index 2efb992..5f54473 100644 --- a/src/main/java/com/sidpatchy/clairebot/Main.java +++ b/src/main/java/com/sidpatchy/clairebot/Main.java @@ -16,10 +16,8 @@ import java.awt.*; import java.io.IOException; import java.io.InputStream; -import java.util.Arrays; +import java.util.*; import java.util.List; -import java.util.Map; -import java.util.Properties; /** * ClaireBot - Simply the best. @@ -63,6 +61,7 @@ public class Main { private static String color; private static String errorColor; private static List errorGifs; + private static Locale fallbackLocale; private static List zerfas; private static String zerfasEmojiServerID; private static String zerfasEmojiID; @@ -196,6 +195,7 @@ public static void extractParametersFromConfig(boolean updateOutdatedConfigs) { color = config.getString("color"); errorColor = config.getString("errorColor"); errorGifs = config.getList("error_gifs", String.class); + fallbackLocale = Locale.forLanguageTag(config.getString("fallback_locale")); zerfas = config.getList("zerfas", String.class); zerfasEmojiServerID = String.valueOf(config.getLong("zerfas_emoji_server_id")); zerfasEmojiID = String.valueOf(config.getLong("zerfas_emoji_id")); @@ -388,4 +388,8 @@ public static String getDocumentationWebsite() { public static String getInviteLink() { return inviteLink; } + + public static Locale getFallbackLocale() { + return fallbackLocale; + } } diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 881f2e0..39d4c73 100755 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -32,6 +32,10 @@ error_gifs: - "https://c.tenor.com/GBrG7SqlVwoAAAAC/dog-dawg.gif" - "https://c.tenor.com/lRhsxkfHhJwAAAAC/spongebob-squarepants-sad.gif" +# The language ClaireBot will fallback to if a translation string +# is not available for a user's chosen language. +fallback_language: en-US + # ----------------- SHARD CONFIG --------------- # It is assumed that only one shard will be running by default. # You probably don't need to change this. @@ -125,6 +129,7 @@ checkForUpdates: true UpdateOutdatedConfigs: true # Do not change this, this is automatically changed when needed. +# TODO either automatically add new config file params or remove this. configRevision: 5 # ---------- ADDED AFTER INITIAL USE ----------- diff --git a/src/main/resources/translations/lang_en-US.yml b/src/main/resources/translations/lang_en-US.yml index 647412e..3b2fa98 100644 --- a/src/main/resources/translations/lang_en-US.yml +++ b/src/main/resources/translations/lang_en-US.yml @@ -142,7 +142,7 @@ ClaireLang: - "*The best there is, the best there was, the best there ever will be!*" - "*Bringing the best, always! 🌟*" - "*Excellence. 💯*" - # Note: The rest of the help is located separately within commands_en-US.yml + # Note: The rest of the help is located separately within commands_en-US.yml HelpEmbed: Commands: "Commands" Usage: "Usage" @@ -164,7 +164,9 @@ ClaireLang: LeaderboardForServer: "Leaderboard for" GlobalLeaderboard: "Global Leaderboard" LevelEmbed: "" # Unused - QuoteEmbed: "" # Unused + QuoteEmbed: + InvalidMessages: "Looks like the messages I selected were invalid. Please try again later." + JumpToOriginal: "Click to jump to the original message:" SantaEmbed: Confirmation: "Confirmed! I've sent you a direct message. Please continue there." RulesButton: "Add rules" From 8a7d1b774a285857d70a98cf27d4a5e9a76f2125 Mon Sep 17 00:00:00 2001 From: Sidpatchy <35241490+Sidpatchy@users.noreply.github.com> Date: Mon, 25 Aug 2025 19:55:08 -0600 Subject: [PATCH 22/64] chore: remove message cache code Incomplete feature added in 0d1b24d. Would be nice to have faster subsequent runs of the /quote command in a given channel but I don't want to be storing messages for any longer than absolutely needed. ClaireBot is not spyware. --- .../clairebot/Util/Cache/MessageCacheEntry.kt | 10 ---- .../Util/Cache/MessageCacheManager.java | 51 ------------------- 2 files changed, 61 deletions(-) delete mode 100644 src/main/java/com/sidpatchy/clairebot/Util/Cache/MessageCacheEntry.kt delete mode 100644 src/main/java/com/sidpatchy/clairebot/Util/Cache/MessageCacheManager.java diff --git a/src/main/java/com/sidpatchy/clairebot/Util/Cache/MessageCacheEntry.kt b/src/main/java/com/sidpatchy/clairebot/Util/Cache/MessageCacheEntry.kt deleted file mode 100644 index 970921b..0000000 --- a/src/main/java/com/sidpatchy/clairebot/Util/Cache/MessageCacheEntry.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.sidpatchy.clairebot.Util.Cache - -import org.javacord.api.entity.channel.TextChannel -import org.javacord.api.entity.message.Message - -data class MessageCacheEntry ( - val channel: TextChannel, - val timeAdded: Long, - val messages: List, -) \ No newline at end of file diff --git a/src/main/java/com/sidpatchy/clairebot/Util/Cache/MessageCacheManager.java b/src/main/java/com/sidpatchy/clairebot/Util/Cache/MessageCacheManager.java deleted file mode 100644 index a79b910..0000000 --- a/src/main/java/com/sidpatchy/clairebot/Util/Cache/MessageCacheManager.java +++ /dev/null @@ -1,51 +0,0 @@ -package com.sidpatchy.clairebot.Util.Cache; - -import org.javacord.api.entity.channel.TextChannel; -import org.javacord.api.entity.message.Message; -import org.javacord.api.entity.user.User; - -import java.util.List; -import java.util.Map; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ConcurrentHashMap; - -/** - * A message caching system to assist in - */ -public class MessageCacheManager { - private static final Map messageCache = new ConcurrentHashMap<>(); - - public static void purgeCache(long secondsAged) { - for (MessageCacheEntry entry : messageCache.values()) { - if (entry.getTimeAdded() < System.currentTimeMillis() - (secondsAged * 1000)) { - messageCache.remove(entry.getChannel().getIdAsString()); - } - } - } - - public static CompletableFuture> queryMessageCache(TextChannel channel, User user) { - if (messageCache.containsKey(channel.getIdAsString())) { - // Retrieve messages from the cache and filter them by user - List filteredMessages = messageCache.get(channel.getIdAsString()) - .getMessages().stream() - .filter(message -> message.getAuthor().getId() == user.getId()) - .toList(); - return CompletableFuture.completedFuture(filteredMessages); - } else { - // Fetch messages from the channel and filter them based on the user - return channel.getMessages(50000).thenApply(messages -> { - MessageCacheEntry newEntry = new MessageCacheEntry(channel, System.currentTimeMillis(), messages.stream().toList()); - // Now you can add this entry to your cache or process it further - messageCache.put(channel.getIdAsString(), newEntry); - return messages.stream() - .filter(message -> message.getAuthor().getId() == user.getId()) - .toList(); - }).exceptionally(ex -> { - // Handle any exceptions that occur during the message retrieval - ex.printStackTrace(); - return null; - }); - } - } -} - From dea19c094d079857e025c884c39540ac019a0a0d Mon Sep 17 00:00:00 2001 From: Sidpatchy <35241490+Sidpatchy@users.noreply.github.com> Date: Mon, 25 Aug 2025 20:16:04 -0600 Subject: [PATCH 23/64] chore: remove old clairelang files --- .../Lang/Beans/ClaireLang/ClaireLang.java | 20 ------------------- .../ClaireLang/Embed/Commands/Commands.java | 7 ------- .../Embed/Commands/Regular/EightBall.java | 12 ----------- .../Embed/Commands/Regular/Help.java | 8 -------- .../Embed/Commands/Regular/Info.java | 16 --------------- .../Embed/Commands/Regular/Regular.java | 9 --------- .../Lang/Beans/ClaireLang/Embed/Embed.java | 7 ------- 7 files changed, 79 deletions(-) delete mode 100644 src/main/java/com/sidpatchy/clairebot/Lang/Beans/ClaireLang/ClaireLang.java delete mode 100644 src/main/java/com/sidpatchy/clairebot/Lang/Beans/ClaireLang/Embed/Commands/Commands.java delete mode 100644 src/main/java/com/sidpatchy/clairebot/Lang/Beans/ClaireLang/Embed/Commands/Regular/EightBall.java delete mode 100644 src/main/java/com/sidpatchy/clairebot/Lang/Beans/ClaireLang/Embed/Commands/Regular/Help.java delete mode 100644 src/main/java/com/sidpatchy/clairebot/Lang/Beans/ClaireLang/Embed/Commands/Regular/Info.java delete mode 100644 src/main/java/com/sidpatchy/clairebot/Lang/Beans/ClaireLang/Embed/Commands/Regular/Regular.java delete mode 100644 src/main/java/com/sidpatchy/clairebot/Lang/Beans/ClaireLang/Embed/Embed.java diff --git a/src/main/java/com/sidpatchy/clairebot/Lang/Beans/ClaireLang/ClaireLang.java b/src/main/java/com/sidpatchy/clairebot/Lang/Beans/ClaireLang/ClaireLang.java deleted file mode 100644 index f21ac69..0000000 --- a/src/main/java/com/sidpatchy/clairebot/Lang/Beans/ClaireLang/ClaireLang.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.sidpatchy.clairebot.Lang.Beans.ClaireLang; - -import com.sidpatchy.clairebot.Lang.Beans.ClaireLang.Embed.Embed; - -/** - * Hello future sufferers. I'm glad we're in this together :) - *

- * Here is the commit message that was written when this structure was devised: - *

- * "I prefer the idea of this structure, I just don't like the implementation of this structure. - *

- * Also considered doing one giga-class (technical term :)), but that would've been much less readable than whatever in the fuck this is. - *

- * Probably would've been easier to understand the intent of though... I will need to write some really good javadoc if I stick with this." - *

- * IntelliJ is angry at me for using "really good." Too bad! - */ -public class ClaireLang { - public Embed embed; -} diff --git a/src/main/java/com/sidpatchy/clairebot/Lang/Beans/ClaireLang/Embed/Commands/Commands.java b/src/main/java/com/sidpatchy/clairebot/Lang/Beans/ClaireLang/Embed/Commands/Commands.java deleted file mode 100644 index e4d06d5..0000000 --- a/src/main/java/com/sidpatchy/clairebot/Lang/Beans/ClaireLang/Embed/Commands/Commands.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.sidpatchy.clairebot.Lang.Beans.ClaireLang.Embed.Commands; - -import com.sidpatchy.clairebot.Lang.Beans.ClaireLang.Embed.Commands.Regular.Regular; - -public class Commands { - public Regular regular; -} diff --git a/src/main/java/com/sidpatchy/clairebot/Lang/Beans/ClaireLang/Embed/Commands/Regular/EightBall.java b/src/main/java/com/sidpatchy/clairebot/Lang/Beans/ClaireLang/Embed/Commands/Regular/EightBall.java deleted file mode 100644 index 64fd76d..0000000 --- a/src/main/java/com/sidpatchy/clairebot/Lang/Beans/ClaireLang/Embed/Commands/Regular/EightBall.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.sidpatchy.clairebot.Lang.Beans.ClaireLang.Embed.Commands.Regular; - -import com.fasterxml.jackson.annotation.JsonProperty; - -import java.util.List; - -public class EightBall { - @JsonProperty("8bResponses") - public List eightballResponses; - @JsonProperty("8bRiggedResponses") - public List eightBallRiggedResponses; -} diff --git a/src/main/java/com/sidpatchy/clairebot/Lang/Beans/ClaireLang/Embed/Commands/Regular/Help.java b/src/main/java/com/sidpatchy/clairebot/Lang/Beans/ClaireLang/Embed/Commands/Regular/Help.java deleted file mode 100644 index 4aebd42..0000000 --- a/src/main/java/com/sidpatchy/clairebot/Lang/Beans/ClaireLang/Embed/Commands/Regular/Help.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.sidpatchy.clairebot.Lang.Beans.ClaireLang.Embed.Commands.Regular; - -import com.fasterxml.jackson.annotation.JsonProperty; - -public class Help { - @JsonProperty("Commands") - public String commands; -} diff --git a/src/main/java/com/sidpatchy/clairebot/Lang/Beans/ClaireLang/Embed/Commands/Regular/Info.java b/src/main/java/com/sidpatchy/clairebot/Lang/Beans/ClaireLang/Embed/Commands/Regular/Info.java deleted file mode 100644 index 4ea62d4..0000000 --- a/src/main/java/com/sidpatchy/clairebot/Lang/Beans/ClaireLang/Embed/Commands/Regular/Info.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.sidpatchy.clairebot.Lang.Beans.ClaireLang.Embed.Commands.Regular; - -public class Info { - private String needHelp; - private String needHelpDetails; - private String addToServer; - private String addToServerDetails; - private String github; - private String githubDetails; - private String serverCount; - private String servercountDetails; - private String version; - private String versionDetails; - private String uptime; - private String uptimeDetails; -} diff --git a/src/main/java/com/sidpatchy/clairebot/Lang/Beans/ClaireLang/Embed/Commands/Regular/Regular.java b/src/main/java/com/sidpatchy/clairebot/Lang/Beans/ClaireLang/Embed/Commands/Regular/Regular.java deleted file mode 100644 index 60047f1..0000000 --- a/src/main/java/com/sidpatchy/clairebot/Lang/Beans/ClaireLang/Embed/Commands/Regular/Regular.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.sidpatchy.clairebot.Lang.Beans.ClaireLang.Embed.Commands.Regular; - -import com.fasterxml.jackson.annotation.JsonProperty; - -public class Regular { - @JsonProperty("AvatarEmbed") - public String avatarEmbed; - -} diff --git a/src/main/java/com/sidpatchy/clairebot/Lang/Beans/ClaireLang/Embed/Embed.java b/src/main/java/com/sidpatchy/clairebot/Lang/Beans/ClaireLang/Embed/Embed.java deleted file mode 100644 index 7c5f498..0000000 --- a/src/main/java/com/sidpatchy/clairebot/Lang/Beans/ClaireLang/Embed/Embed.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.sidpatchy.clairebot.Lang.Beans.ClaireLang.Embed; - -import com.sidpatchy.clairebot.Commands; - -public class Embed { - public Commands commands; -} From 9d840713398131dca1c2232a2758ec0e6c5027f6 Mon Sep 17 00:00:00 2001 From: Sidpatchy <35241490+Sidpatchy@users.noreply.github.com> Date: Mon, 25 Aug 2025 20:44:00 -0600 Subject: [PATCH 24/64] chore: remove missed message cache code --- .../Embed/Commands/Regular/QuoteEmbed.java | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/QuoteEmbed.java b/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/QuoteEmbed.java index 917bb29..c18d0ea 100644 --- a/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/QuoteEmbed.java +++ b/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/QuoteEmbed.java @@ -1,16 +1,16 @@ package com.sidpatchy.clairebot.Embed.Commands.Regular; - import com.sidpatchy.clairebot.Embed.ErrorEmbed; -import com.sidpatchy.clairebot.Lang.LanguageManager; import com.sidpatchy.clairebot.Main; -import com.sidpatchy.clairebot.Util.Cache.MessageCacheManager; +import org.javacord.api.entity.Icon; import org.javacord.api.entity.channel.TextChannel; import org.javacord.api.entity.message.Message; +import org.javacord.api.entity.message.MessageBuilder; import org.javacord.api.entity.message.embed.EmbedBuilder; import org.javacord.api.entity.message.embed.EmbedFooter; import org.javacord.api.entity.server.Server; import org.javacord.api.entity.user.User; +import java.util.List; import java.util.Random; import java.util.concurrent.CompletableFuture; @@ -24,9 +24,13 @@ public class QuoteEmbed { * @param channel the text channel where the messages are located * @return a CompletableFuture that resolves to an EmbedBuilder containing the quote */ - public static CompletableFuture getQuote(LanguageManager languageManager, Server server, final User user, TextChannel channel) { + public static CompletableFuture getQuote(Server server, final User user, TextChannel channel) { + + return channel.getMessages(50000).thenApply(messages -> { + List userMessages = new java.util.ArrayList<>(messages.stream() + .filter(message -> message.getAuthor().getId() == user.getId()) + .toList()); - return MessageCacheManager.queryMessageCache(channel, user).thenApply(userMessages -> { if (userMessages.isEmpty()) { // user not sent messages return ErrorEmbed.getError(Main.getErrorCode("UserNotInSet")); @@ -77,12 +81,11 @@ public static CompletableFuture getQuote(LanguageManager languageM }); } - public static EmbedBuilder viewOriginalMessageBuilder(LanguageManager languageManager, TextChannel channel, Message message) { + public static EmbedBuilder viewOriginalMessageBuilder(TextChannel channel, Message message) { EmbedFooter footer = message.getEmbeds().get(0).getFooter().orElse(null); Message quotedMessage = message.getApi().getMessageById(footer.getText().orElse(""), channel).join(); return new EmbedBuilder() - .addField(languageManager.getLocalizedString("ClaireLang.Embed.Commands.Regular.QuoteEmbed.JumpToOriginal"), - quotedMessage.getLink().toString()); + .addField("Click to jump to the original message:", quotedMessage.getLink().toString()); } -} +} \ No newline at end of file From ce4acc07ec75e47f8dfafed1f2b19ecb865047de Mon Sep 17 00:00:00 2001 From: Sidpatchy <35241490+Sidpatchy@users.noreply.github.com> Date: Mon, 25 Aug 2025 21:01:09 -0600 Subject: [PATCH 25/64] feat: command embeds fully localized Almost entirely courtesy of GPT-5. This wouldn't be finished anytime soon without LLM assistance. The hard part was done by hand, the machine can do the chores. Now the only remaining work (that immediately comes to mind) is the MessageComponents embeds. It looks like I still need to add most (or all) of those fields to the language file so that's gonna wait. --- .../Commands/Regular/ServerInfoEmbed.java | 41 +++++--- .../Regular/ServerPreferencesEmbed.java | 92 +++++++++++------ .../Embed/Commands/Regular/UserInfoEmbed.java | 45 ++++++--- .../Regular/UserPreferencesEmbed.java | 63 +++++++----- .../Embed/Commands/Regular/VotingEmbed.java | 98 ++++++++++++------- .../sidpatchy/clairebot/Embed/ErrorEmbed.java | 82 ++++++++++++++-- .../clairebot/Embed/WelcomeEmbed.java | 28 +++++- .../clairebot/Lang/PlaceholderHandler.java | 53 +++++++++- .../clairebot/Listener/ButtonClick.java | 6 +- .../clairebot/Listener/ModalSubmit.java | 23 +++-- .../clairebot/Listener/SelectMenuChoose.java | 30 +++--- .../clairebot/Listener/ServerJoin.java | 9 +- .../Listener/SlashCommandCreate.java | 20 ++-- .../resources/translations/lang_en-US.yml | 23 ++--- 14 files changed, 442 insertions(+), 171 deletions(-) diff --git a/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/ServerInfoEmbed.java b/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/ServerInfoEmbed.java index 60c5512..3a9cfc0 100644 --- a/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/ServerInfoEmbed.java +++ b/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/ServerInfoEmbed.java @@ -1,31 +1,48 @@ package com.sidpatchy.clairebot.Embed.Commands.Regular; +import com.sidpatchy.clairebot.Lang.LanguageManager; import com.sidpatchy.clairebot.Main; import org.javacord.api.entity.message.embed.EmbedBuilder; import org.javacord.api.entity.server.Server; +import java.awt.Color; public class ServerInfoEmbed { - public static EmbedBuilder getServerInfo(Server server, String userID) { + public static EmbedBuilder getServerInfo(LanguageManager languageManager, Server server, String userID) { + Color color = Main.getColor(userID); + String authorName = server.getName(); + String footerText = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Regular.ServerInfoEmbed.ServerID: " + server.getIdAsString()); + String ownerLabel = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Regular.ServerInfoEmbed.Owner"); + String creationDateLabel = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Regular.ServerInfoEmbed.CreationDate"); + String roleCountLabel = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Regular.ServerInfoEmbed.RoleCount"); + String memberCountLabel = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Regular.ServerInfoEmbed.MemberCount"); + String channelCountsLabel = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Regular.ServerInfoEmbed.ChannelCounts"); + String categoriesLabel = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Regular.ServerInfoEmbed.Categories"); + String textChannelsLabel = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Regular.ServerInfoEmbed.TextChannels"); + String voiceChannelsLabel = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Regular.ServerInfoEmbed.VoiceChannels"); + + String channelCountsValue = + "⦁ " + categoriesLabel + ": " + server.getChannelCategories().size() + + "\n⦁ " + textChannelsLabel + ": " + server.getTextChannels().size() + + "\n⦁ " + voiceChannelsLabel + ": " + server.getVoiceChannels().size(); + + // Build embed (keeps the inline, minimal style) EmbedBuilder embed = new EmbedBuilder() - .setColor(Main.getColor(userID)) - .setAuthor(server.getName()) - .setFooter("Server ID: " + server.getIdAsString()); + .setColor(color) + .setAuthor(authorName) + .setFooter(footerText); server.getIcon().ifPresent(embed::setThumbnail); server.getOwner().ifPresent(owner -> { - embed.addField("Owner", owner.getDiscriminatedName(), false); + embed.addField(ownerLabel, owner.getDiscriminatedName(), false); }); - embed.addField("Creation Date", ""); - - embed.addField("Role Count", String.valueOf(server.getRoles().size()), false); - embed.addField("Member Count", String.valueOf(server.getMemberCount()), false); - embed.addField("Channel Counts", "⦁ Categories: " + server.getChannelCategories().size() + - "\n⦁ Text Channels: " + server.getTextChannels().size() + - "\n⦁ Voice Channels: " + server.getVoiceChannels().size(), false); + embed.addField(creationDateLabel, ""); + embed.addField(roleCountLabel, String.valueOf(server.getRoles().size()), false); + embed.addField(memberCountLabel, String.valueOf(server.getMemberCount()), false); + embed.addField(channelCountsLabel, channelCountsValue, false); return embed; } diff --git a/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/ServerPreferencesEmbed.java b/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/ServerPreferencesEmbed.java index 6be269d..dcf50e1 100644 --- a/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/ServerPreferencesEmbed.java +++ b/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/ServerPreferencesEmbed.java @@ -2,6 +2,8 @@ import com.sidpatchy.clairebot.API.Guild; import com.sidpatchy.clairebot.Embed.ErrorEmbed; +import com.sidpatchy.clairebot.Lang.ContextManager; +import com.sidpatchy.clairebot.Lang.LanguageManager; import com.sidpatchy.clairebot.Main; import org.javacord.api.entity.channel.ServerTextChannel; import org.javacord.api.entity.message.embed.EmbedBuilder; @@ -10,30 +12,47 @@ public class ServerPreferencesEmbed { - public static EmbedBuilder getMainMenu(User author) { - return createGenericMenuEmbed(author, "Server Configuration Editor"); + public static EmbedBuilder getMainMenu(LanguageManager languageManager, User author) { + // Temp/localized variables + String title = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Regular.ServerPreferencesEmbed.MainMenuTitle"); + + return createGenericMenuEmbed(author, title); } - public static EmbedBuilder getNotServerMenu() { - return ErrorEmbed.getCustomError(Main.getErrorCode("notaserver"), - "You must run this command inside a server!"); + public static EmbedBuilder getNotServerMenu(LanguageManager languageManager) { + // Temp/localized variables + String notServerMsg = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Regular.ServerPreferencesEmbed.NotAServer"); + + return ErrorEmbed.getCustomError(Main.getErrorCode("notaserver"), notServerMsg); } - public static EmbedBuilder getRequestsChannelMenu(User author) { - return createGenericMenuEmbed(author, "Requests Channel") - .setDescription("Only lists the first 25 channels in the server."); + public static EmbedBuilder getRequestsChannelMenu(LanguageManager languageManager, User author) { + // Temp/localized variables + String menuName = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Regular.ServerPreferencesEmbed.RequestsChannelMenuName"); + String desc = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Regular.ServerPreferencesEmbed.RequestsChannelDescription"); + + return createGenericMenuEmbed(author, menuName) + .setDescription(desc); } - public static EmbedBuilder getModeratorChannelMenu(User author) { - return createGenericMenuEmbed(author, "Moderator Messages Channel") - .setDescription("Only lists the first 25 channels in the server."); + public static EmbedBuilder getModeratorChannelMenu(LanguageManager languageManager, User author) { + // Temp/localized variables + String menuName = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Regular.ServerPreferencesEmbed.ModeratorChannelMenuName"); + String desc = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Regular.ServerPreferencesEmbed.ModeratorChannelDescription"); + + return createGenericMenuEmbed(author, menuName) + .setDescription(desc); } - public static EmbedBuilder getEnforceServerLangMenu(User author) { - return createGenericMenuEmbed(author, "Enforce Server Language"); + public static EmbedBuilder getEnforceServerLangMenu(LanguageManager languageManager, User author) { + // Temp/localized variables + String menuName = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Regular.ServerPreferencesEmbed.EnforceServerLanguageMenuName"); + + return createGenericMenuEmbed(author, menuName); } - public static EmbedBuilder getAcknowledgeRequestsChannelChange(Server server, User author, String requestsChannelID) { + public static EmbedBuilder getAcknowledgeRequestsChannelChange(LanguageManager languageManager, Server server, User author, String requestsChannelID) { + // Resolve channel ServerTextChannel channel = Main.getApi().getServerTextChannelById(requestsChannelID).orElse(null); if (channel == null) { return ErrorEmbed.getError(Main.getErrorCode("channelNotExists")); @@ -44,17 +63,24 @@ public static EmbedBuilder getAcknowledgeRequestsChannelChange(Server server, Us guild.getGuild(); guild.updateRequestsChannelID(requestsChannelID); + // Temp/localized variables + String title = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Regular.ServerPreferencesEmbed.AcknowledgeRequestsChannelChangeTitle"); + String mention = "<#" + channel.getIdAsString() + ">"; + languageManager.addContext(ContextManager.ContextType.GENERIC, "channel.id.mentiontag", mention); + String desc = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Regular.ServerPreferencesEmbed.AcknowledgeRequestsChannelChangeDescription"); + return new EmbedBuilder() .setColor(Main.getColor(author.getIdAsString())) - .setAuthor("Requests Channel Changed!") - .setDescription("Your requests channel has been changed to " + channel.getMentionTag()); + .setAuthor(title) + .setDescription(desc); } catch (Exception e) { e.printStackTrace(); return ErrorEmbed.getError(Main.getErrorCode("updateRequestsChannel")); } } - public static EmbedBuilder getAcknowledgeModeratorChannelChange(Server server, User author, String moderatorChannelID) { + public static EmbedBuilder getAcknowledgeModeratorChannelChange(LanguageManager languageManager, Server server, User author, String moderatorChannelID) { + // Resolve channel ServerTextChannel channel = Main.getApi().getServerTextChannelById(moderatorChannelID).orElse(null); if (channel == null) { return ErrorEmbed.getError(Main.getErrorCode("channelNotExists")); @@ -65,17 +91,24 @@ public static EmbedBuilder getAcknowledgeModeratorChannelChange(Server server, U guild.getGuild(); guild.updateModeratorMessagesChannelID(moderatorChannelID); + // Temp/localized variables + String title = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Regular.ServerPreferencesEmbed.AcknowledgeModeratorChannelChangeTitle"); + String mention = "<#" + channel.getIdAsString() + ">"; + languageManager.addContext(ContextManager.ContextType.GENERIC, "channel.id.mentiontag", mention); + String desc = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Regular.ServerPreferencesEmbed.AcknowledgeModeratorChannelChangeDescription"); + return new EmbedBuilder() .setColor(Main.getColor(author.getIdAsString())) - .setAuthor("Moderator Channel Changed!") - .setDescription("Your moderator messages channel has been changed to " + channel.getMentionTag()); + .setAuthor(title) + .setDescription(desc); } catch (Exception e) { e.printStackTrace(); return ErrorEmbed.getError(Main.getErrorCode("updateModeratorChannel")); } } - public static EmbedBuilder getAcknowledgeEnforceServerLanguageUpdate(Server server, User author, String newValue) { + public static EmbedBuilder getAcknowledgeEnforceServerLanguageUpdate(LanguageManager languageManager, Server server, User author, String newValue) { + // Temp/localized variables boolean value = Boolean.parseBoolean(newValue); try { @@ -83,18 +116,15 @@ public static EmbedBuilder getAcknowledgeEnforceServerLanguageUpdate(Server serv guild.getGuild(); guild.updateEnforceServerLanguage(value); - EmbedBuilder embed = new EmbedBuilder() - .setColor(Main.getColor(author.getIdAsString())) - .setAuthor("Server Language Preferences Updated!"); - - if (value) { - embed.setDescription("I will now follow the server's language regardless of user preference."); - } - else { - embed.setDescription("I will allow users to set their own language preferences."); - } + String title = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Regular.ServerPreferencesEmbed.AcknowledgeEnforceServerLanguageUpdateTitle"); + String desc = value + ? languageManager.getLocalizedString("ClaireLang.Embed.Commands.Regular.ServerPreferencesEmbed.AcknowledgeEnforceServerLanguageUpdateEnforced") + : languageManager.getLocalizedString("ClaireLang.Embed.Commands.Regular.ServerPreferencesEmbed.AcknowledgeEnforceServerLanguageUpdateNotEnforced"); - return embed; + return new EmbedBuilder() + .setColor(Main.getColor(author.getIdAsString())) + .setAuthor(title) + .setDescription(desc); } catch (Exception e) { e.printStackTrace(); diff --git a/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/UserInfoEmbed.java b/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/UserInfoEmbed.java index 6d052c1..5a55183 100644 --- a/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/UserInfoEmbed.java +++ b/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/UserInfoEmbed.java @@ -1,6 +1,8 @@ package com.sidpatchy.clairebot.Embed.Commands.Regular; import com.sidpatchy.clairebot.Embed.ErrorEmbed; +import com.sidpatchy.clairebot.Lang.ContextManager; +import com.sidpatchy.clairebot.Lang.LanguageManager; import com.sidpatchy.clairebot.Main; import org.apache.commons.lang3.time.DurationFormatUtils; import org.javacord.api.entity.message.embed.EmbedBuilder; @@ -11,32 +13,51 @@ public class UserInfoEmbed { - public static EmbedBuilder getUser(User user, User author, Server server) { + public static EmbedBuilder getUser(LanguageManager languageManager, User user, User author, Server server) { + // Temp / localized variables block + long nowMs = System.currentTimeMillis(); + long creationMs = user.getCreationTimestamp().toEpochMilli(); + String creationDateTag = ""; + String timeSinceCreation = DurationFormatUtils.formatDurationWords(nowMs - creationMs, true, false); - String creationDate = ""; - String timeSinceCreation = DurationFormatUtils.formatDurationWords(System.currentTimeMillis() - user.getCreationTimestamp().toEpochMilli(), true, false); + String userLabel = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Regular.UserInfoEmbed.User"); + String discordIdLabel = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Regular.UserInfoEmbed.DiscordID"); + String joinDateLabel = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Regular.UserInfoEmbed.JoinDate"); + String creationDateLabel = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Regular.UserInfoEmbed.CreationDate"); + // Null guard for author (use placeholders) if (author == null) { String errorCode = Main.getErrorCode("User_Info_Null"); - Main.getLogger().error("The value for author was null when passed into UserInfo Embed. Error code: " + errorCode); - Main.getLogger().error("Author: " + author.getDiscriminatedName()); + languageManager.addContext(ContextManager.ContextType.GENERIC, "errorcode", errorCode); + languageManager.addContext(ContextManager.ContextType.GENERIC, "user.id.username", "null"); + + String err1 = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Regular.UserInfoEmbed.Error_1"); + String err2 = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Regular.UserInfoEmbed.Error_2"); + Main.getLogger().error(err1); + Main.getLogger().error(err2); return ErrorEmbed.getError(errorCode); } + String footerText = author.getDiscriminatedName() + " (" + author.getIdAsString() + ")"; + + // Build embed EmbedBuilder embed = new EmbedBuilder() .setColor(Main.getColor(user.getIdAsString())) .setThumbnail(user.getAvatar()) - .setAuthor("User\n" + user.getDiscriminatedName()) - .addField("Discord ID", user.getIdAsString(), false); + .setAuthor(userLabel + "\n" + user.getDiscriminatedName()) + .addField(discordIdLabel, user.getIdAsString(), false); if (server != null) { - String joinDate = ""; - String timeSinceJoin = DurationFormatUtils.formatDurationWords(System.currentTimeMillis() - user.getJoinedAtTimestamp(server).orElse(Instant.ofEpochMilli(0)).toEpochMilli(), true, false); - embed.addField("Server Join Date", joinDate + "\n*" + timeSinceJoin + "*", false); + Instant joinedAt = user.getJoinedAtTimestamp(server).orElse(Instant.now()); + long joinMs = joinedAt.toEpochMilli(); + String joinDateTag = ""; + long sinceBase = user.getJoinedAtTimestamp(server).orElse(Instant.ofEpochMilli(0)).toEpochMilli(); + String timeSinceJoin = DurationFormatUtils.formatDurationWords(nowMs - sinceBase, true, false); + embed.addField(joinDateLabel, joinDateTag + "\n*" + timeSinceJoin + "*", false); } - embed.addField("Account Creation Date", creationDate + "\n*" + timeSinceCreation + "*", false); - embed.setFooter(author.getDiscriminatedName() + " (" + author.getIdAsString() + ")", author.getAvatar()); + embed.addField(creationDateLabel, creationDateTag + "\n*" + timeSinceCreation + "*", false) + .setFooter(footerText, author.getAvatar()); return embed; } diff --git a/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/UserPreferencesEmbed.java b/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/UserPreferencesEmbed.java index c5dfb7b..c097f9c 100644 --- a/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/UserPreferencesEmbed.java +++ b/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/UserPreferencesEmbed.java @@ -2,69 +2,80 @@ import com.sidpatchy.clairebot.API.APIUser; import com.sidpatchy.clairebot.Embed.ErrorEmbed; +import com.sidpatchy.clairebot.Lang.ContextManager; +import com.sidpatchy.clairebot.Lang.LanguageManager; import com.sidpatchy.clairebot.Main; import org.javacord.api.entity.message.embed.EmbedBuilder; import org.javacord.api.entity.user.User; -import java.awt.*; +import java.awt.Color; public class UserPreferencesEmbed { - /** - * - * @param author - * @return - */ - public static EmbedBuilder getMainMenu(User author) { + public static EmbedBuilder getMainMenu(LanguageManager languageManager, User author) { + // Temp/localized variables + String title = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Regular.UserPreferencesEmbed.MainMenuText"); + return new EmbedBuilder() .setColor(Main.getColor(author.getIdAsString())) - .setAuthor("User Preferences Editor"); + .setAuthor(title); } - public static EmbedBuilder getAccentColourMenu(User author) { + public static EmbedBuilder getAccentColourMenu(LanguageManager languageManager, User author) { + // Temp/localized variables + String title = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Regular.UserPreferencesEmbed.AccentColourMenu"); + return new EmbedBuilder() .setColor(Main.getColor(author.getIdAsString())) - .setAuthor("Accent Colour Editor"); + .setAuthor(title); } - public static EmbedBuilder getAccentColourListMenu(User author) { + public static EmbedBuilder getAccentColourListMenu(LanguageManager languageManager, User author) { + // Temp/localized variables + String title = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Regular.UserPreferencesEmbed.AccentColourList"); + return new EmbedBuilder() .setColor(Main.getColor(author.getIdAsString())) - .setAuthor("Accent Colour List"); + .setAuthor(title); } /** * Response when an accent colour has been selected. Updates colour based off passed colourCode. * * @param author User updating their colour - * @param colourCode colour code selected + * @param colourCode colour code selected (e.g. #5865F2) * @return embed */ - public static EmbedBuilder getAcknowledgeAccentColourChange(User author, String colourCode) { + public static EmbedBuilder getAcknowledgeAccentColourChange(LanguageManager languageManager, User author, String colourCode) { + // Temp/localized variables Color color = Color.decode(colourCode); try { - APIUser user = new APIUser(author.getIdAsString()); - user.getUser(); - user.updateUserColour(colourCode); + APIUser apiUser = new APIUser(author.getIdAsString()); + apiUser.getUser(); + apiUser.updateUserColour(colourCode); + + String title = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Regular.UserPreferencesEmbed.AccentColourChanged"); + String desc = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Regular.UserPreferencesEmbed.AccentColourChangedDesc"); return new EmbedBuilder() .setColor(color) - .setAuthor("Accent Colour Changed!") - .setDescription("Your accent colour has been changed to " + colourCode); - } - catch (Exception e){ + .setAuthor(title) + .setDescription(desc); + } catch (Exception e) { e.printStackTrace(); return ErrorEmbed.getError(Main.getErrorCode("updateAccentColour")); } - - } - public static EmbedBuilder getLanguageMenu(User author) { + public static EmbedBuilder getLanguageMenu(LanguageManager languageManager, User author) { + // Temp/localized variables + String title = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Regular.UserPreferencesEmbed.LanguageMenuTitle"); + String desc = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Regular.UserPreferencesEmbed.LanguageMenuDesc"); + return new EmbedBuilder() .setColor(Main.getColor(author.getIdAsString())) - .setAuthor("NYI") - .setDescription("Sorry, this feature is not yet implemented."); + .setAuthor(title) + .setDescription(desc); } } diff --git a/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/VotingEmbed.java b/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/VotingEmbed.java index d037a85..2e446f8 100755 --- a/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/VotingEmbed.java +++ b/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/VotingEmbed.java @@ -1,5 +1,7 @@ package com.sidpatchy.clairebot.Embed.Commands.Regular; +import com.sidpatchy.clairebot.Lang.ContextManager; +import com.sidpatchy.clairebot.Lang.LanguageManager; import com.sidpatchy.clairebot.Main; import com.sidpatchy.clairebot.Util.Voting.VotingUtils; import org.javacord.api.entity.message.embed.EmbedBuilder; @@ -11,11 +13,10 @@ public class VotingEmbed { - /** * Called when a user creates a poll or request and specifies a description. * - * @param commandName default will be "REQUEST" or "POLL", no reason to maintain two classes that do basically the same thing (aka pulling a ClaireBot 2) + * @param commandName default will be "REQUEST" or "POLL" * @param question The question the user is asking. * @param description A description of what is being asked. * @param allowMultipleChoices allow a user to vote for multiple options @@ -25,42 +26,67 @@ public class VotingEmbed { * @param numChoices The number of choices * @return voting embed */ - public static EmbedBuilder getPoll(String commandName, String question, String description, Boolean allowMultipleChoices, List choices, Server server, User author, Integer numChoices) { + public static EmbedBuilder getPoll(LanguageManager languageManager, + String commandName, + String question, + String description, + Boolean allowMultipleChoices, + List choices, + Server server, + User author, + Integer numChoices) { + // Temp/localized variables block List emoji = Arrays.asList("1️⃣", "2️⃣", "3️⃣", "4️⃣", "5️⃣", "6️⃣", "7️⃣", "8️⃣", "9️⃣", "🔟", "\uD83D\uDC4D", "\uD83D\uDC4E"); + String choicesLabel = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Regular.VotingEmbed.Choices"); + String pollRequestText; + String pollAskText; + { + // Provide display name placeholder for author line + languageManager.addContext(ContextManager.ContextType.GENERIC, "user.id.displayname.server", author.getDisplayName(server)); + pollRequestText = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Regular.VotingEmbed.PollRequest"); + pollAskText = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Regular.VotingEmbed.PollAsk"); + } + String authorUrl = "https://discord.com/users/" + author.getIdAsString(); EmbedBuilder embed = new EmbedBuilder() .setColor(Main.getColor(author.getIdAsString())); + // Build choices block (up to 10) StringBuilder choiceBuilder = new StringBuilder(); if (choices != null) { - for (int i = 0; i < 10; i++) { - if (choices.get(i) != null) { - choiceBuilder.append(emoji.get(i)).append(" ").append(choices.get(i)).append("\n"); + int limit = Math.min(10, choices.size()); + for (int i = 0; i < limit; i++) { + String choice = choices.get(i); + if (choice == null || choice.isBlank()) { + break; } - else {break;} + choiceBuilder.append(emoji.get(i)).append(" ").append(choice).append("\n"); } } - if (choiceBuilder.toString().isEmpty()) { + if (choiceBuilder.length() == 0) { allowMultipleChoices = false; - } - else { - embed.addField("Choices", choiceBuilder.toString()); + } else { + embed.addField(choicesLabel, choiceBuilder.toString()); } - embed.setFooter("Poll ID: " + VotingUtils.getPollID(allowMultipleChoices, author.getIdAsString(), numChoices.toString())); + // Compute poll ID after choices logic, then localize footer with placeholder + String pollId = VotingUtils.getPollID(allowMultipleChoices, author.getIdAsString(), numChoices.toString()); + languageManager.addContext(ContextManager.ContextType.GENERIC, "poll.id", pollId); + String footer = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Regular.VotingEmbed.PollID"); + embed.setFooter(footer); + // Localized author line if (commandName.equalsIgnoreCase("REQUEST")) { - embed.setAuthor(author.getDisplayName(server) + " requests:", "https://discord.com/users/" + author.getIdAsString(), author.getAvatar()); - } - else if (commandName.equalsIgnoreCase("POLL")) { - embed.setAuthor(author.getDisplayName(server) + " asks:", "https://discord.com/users/" + author.getIdAsString(), author.getAvatar()); + embed.setAuthor(pollRequestText, authorUrl, author.getAvatar()); + } else if (commandName.equalsIgnoreCase("POLL")) { + embed.setAuthor(pollAskText, authorUrl, author.getAvatar()); } - if (description.isEmpty()) { + // Question / description + if (description == null || description.isEmpty()) { embed.setDescription(question); - } - else { + } else { embed.addField(question, description); } @@ -69,30 +95,32 @@ else if (commandName.equalsIgnoreCase("POLL")) { /** * Called when a user creates a poll without specifying a description. - * - * @param commandName default will be "REQUEST" or "POLL", no reason to maintain two classes that do basically the same thing (aka pulling a ClaireBot 2) - * @param question The question the user is asking. - * @param allowMultipleChoices allow a user to vote for multiple options - * @param choices List of choices as a string - * @param server The server/guild that the command is being run in - * @param author The user who ran the command - * @param numChoices The number of choices - * @return voting embed */ - public static EmbedBuilder getPoll(String commandName, String question, Boolean allowMultipleChoices, List choices, Server server, User author, Integer numChoices) { - return getPoll(commandName, question, "", allowMultipleChoices, choices, server, author, numChoices); + public static EmbedBuilder getPoll(LanguageManager languageManager, + String commandName, + String question, + Boolean allowMultipleChoices, + List choices, + Server server, + User author, + Integer numChoices) { + return getPoll(languageManager, commandName, question, "", allowMultipleChoices, choices, server, author, numChoices); } /** * The embed we respond to the user with, should ideally be ephemeral. * @param author the author of the command - * @param requestsChannelMentionTag the channel the request is being posted in - * @return + * @param requestsChannelMentionTag the channel the request is being posted in (e.g. "<#1234567890>") */ - public static EmbedBuilder getUserResponse(User author, String requestsChannelMentionTag) { + public static EmbedBuilder getUserResponse(LanguageManager languageManager, User author, String requestsChannelMentionTag) { + // Temp/localized variables block + String title = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Regular.VotingEmbed.UserResponseTitle"); + languageManager.addContext(ContextManager.ContextType.GENERIC, "channel.id.mentiontag", requestsChannelMentionTag); + String desc = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Regular.VotingEmbed.UserResponseDescription"); + return new EmbedBuilder() .setColor(Main.getColor(author.getIdAsString())) - .setAuthor("Your request has been created!") - .setDescription("Go check it out in " + requestsChannelMentionTag); + .setAuthor(title) + .setDescription(desc); } } diff --git a/src/main/java/com/sidpatchy/clairebot/Embed/ErrorEmbed.java b/src/main/java/com/sidpatchy/clairebot/Embed/ErrorEmbed.java index 4f45107..3b41ac2 100755 --- a/src/main/java/com/sidpatchy/clairebot/Embed/ErrorEmbed.java +++ b/src/main/java/com/sidpatchy/clairebot/Embed/ErrorEmbed.java @@ -1,28 +1,97 @@ package com.sidpatchy.clairebot.Embed; +import com.sidpatchy.clairebot.Lang.ContextManager; +import com.sidpatchy.clairebot.Lang.LanguageManager; import com.sidpatchy.clairebot.Main; import org.javacord.api.entity.message.embed.EmbedBuilder; import java.util.ArrayList; +import java.util.List; import java.util.Random; /** * Called when ClaireBot encounters (and catches) an error, ideally never. */ public class ErrorEmbed { + public static EmbedBuilder getError(LanguageManager languageManager, String errorCode) { + // Temp/localized variables + List errorGifs = new ArrayList<>(Main.getErrorGifs()); + String title = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Regular.ErrorEmbed.Error"); - public static EmbedBuilder getError(String errorCode) { + languageManager.addContext(ContextManager.ContextType.GENERIC, "errorcode", errorCode); + String description = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Regular.ErrorEmbed.GenericDescription"); - ArrayList errorGifs = (ArrayList) Main.getErrorGifs(); + EmbedBuilder embed = new EmbedBuilder() + .setColor(Main.getErrorColor()) + .setAuthor(title) + .setDescription(description); + + if (!errorGifs.isEmpty()) { + int rand = new Random().nextInt(errorGifs.size()); + embed.setImage(errorGifs.get(rand)); + } + + return embed; + } + + public static EmbedBuilder getError(LanguageManager languageManager, String errorCode, String customMessage) { + // Temp/localized variables + List errorGifs = new ArrayList<>(Main.getErrorGifs()); + String title = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Regular.ErrorEmbed.Error"); + + languageManager.addContext(ContextManager.ContextType.GENERIC, "errorcode", errorCode); + String generic = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Regular.ErrorEmbed.GenericDescription"); + + EmbedBuilder embed = new EmbedBuilder() + .setColor(Main.getErrorColor()) + .setAuthor(title) + .setDescription(customMessage + "\n\n" + generic); + + if (!errorGifs.isEmpty()) { + int rand = new Random().nextInt(errorGifs.size()); + embed.setImage(errorGifs.get(rand)); + } + + return embed; + } - Random random = new Random(); - int rand = random.nextInt(errorGifs.size()); + public static EmbedBuilder getCustomError(LanguageManager languageManager, String errorCode, String message) { + // Temp/localized variables + List errorGifs = new ArrayList<>(Main.getErrorGifs()); + String title = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Regular.ErrorEmbed.Error"); + + languageManager.addContext(ContextManager.ContextType.GENERIC, "errorcode", errorCode); + + EmbedBuilder embed = new EmbedBuilder() + .setColor(Main.getErrorColor()) + .setAuthor(title) + .setDescription(message); + + if (!errorGifs.isEmpty()) { + int rand = new Random().nextInt(errorGifs.size()); + embed.setImage(errorGifs.get(rand)); + } + + return embed; + } + + public static EmbedBuilder getLackingPermissions(LanguageManager languageManager, String message) { + // Use a specific error code key and localize like a custom error + String errorCode = Main.getErrorCode("noPerms"); + return getCustomError(languageManager, errorCode, message); + } + + // TODO - remove these legacy methods + + public static EmbedBuilder getError(String errorCode) { + ArrayList errorGifs = (ArrayList) Main.getErrorGifs(); + int rand = new Random().nextInt(errorGifs.size()); return new EmbedBuilder() .setColor(Main.getErrorColor()) .setAuthor("ERROR") .setDescription("It appears that I've encountered an error, oops! Please try running the command once more and if that doesn't work, join my [Discord server](https://support.clairebot.net/) and let us know about the issue." - + "\n\nPlease include the following error code: " + errorCode) + + "\n\nPlease include the following error code: " + errorCode) .setImage(errorGifs.get(rand)); } @@ -36,6 +105,7 @@ public static EmbedBuilder getCustomError(String errorCode, String message) { } public static EmbedBuilder getLackingPermissions(String message) { - return getCustomError(Main.getErrorCode(Main.getErrorCode("noPerms")), message); + // Fixed: remove accidental double getErrorCode call + return getCustomError(Main.getErrorCode("noPerms"), message); } } diff --git a/src/main/java/com/sidpatchy/clairebot/Embed/WelcomeEmbed.java b/src/main/java/com/sidpatchy/clairebot/Embed/WelcomeEmbed.java index a056c89..969f29d 100644 --- a/src/main/java/com/sidpatchy/clairebot/Embed/WelcomeEmbed.java +++ b/src/main/java/com/sidpatchy/clairebot/Embed/WelcomeEmbed.java @@ -1,27 +1,45 @@ package com.sidpatchy.clairebot.Embed; import com.sidpatchy.clairebot.API.Guild; +import com.sidpatchy.clairebot.Lang.ContextManager; +import com.sidpatchy.clairebot.Lang.LanguageManager; import com.sidpatchy.clairebot.Main; +import org.apache.logging.log4j.Logger; import org.javacord.api.entity.message.embed.EmbedBuilder; import org.javacord.api.entity.server.Server; import java.io.IOException; public class WelcomeEmbed { + private static Logger logger = Main.getLogger(); - public static EmbedBuilder getWelcome(Server server) { + + public static EmbedBuilder getWelcome(LanguageManager languageManager, Server server) { // Initialize the Guild in the database Guild guild = new Guild(server.getIdAsString()); try { guild.getGuild(); - } catch (IOException ignored) {} + } catch (IOException e) { + logger.error("Error while loading guild data.", e); + } + + // Temp/localized variables block + // If your placeholder handler expects {cb.command.help.name}, provide it here + languageManager.addContext(ContextManager.ContextType.GENERIC, "command.help.name", "help"); + + String title = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Regular.WelcomeEmbed.Title"); + String motto = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Regular.WelcomeEmbed.Motto"); + String usageTitle = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Regular.WelcomeEmbed.UsageTitle"); + String usageDesc = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Regular.WelcomeEmbed.UsageDesc"); + String supportTitle = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Regular.WelcomeEmbed.SupportTitle"); + String supportDesc = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Regular.WelcomeEmbed.SupportDesc"); EmbedBuilder embed = new EmbedBuilder() .setColor(Main.getColor(null)) - .addField("\uD83C\uDF89 Welcome to ClaireBot 3!", "How can you rise, if you have not burned?", true) - .addField("Usage", "Get started by running `/help`. Need more info on a command? Run `/help ` (ex. `/help user`)", false) - .addField("Get Support", "You can get help on our [Discord](https://support.clairebot.net/), or by opening an issue on [GitHub](https://github.com/Sidpatchy/ClaireBot)"); + .addField(title, motto, true) + .addField(usageTitle, usageDesc, false) + .addField(supportTitle, supportDesc); server.getIcon().ifPresent(embed::setThumbnail); diff --git a/src/main/java/com/sidpatchy/clairebot/Lang/PlaceholderHandler.java b/src/main/java/com/sidpatchy/clairebot/Lang/PlaceholderHandler.java index 6068a53..0b9d2a9 100644 --- a/src/main/java/com/sidpatchy/clairebot/Lang/PlaceholderHandler.java +++ b/src/main/java/com/sidpatchy/clairebot/Lang/PlaceholderHandler.java @@ -7,6 +7,7 @@ import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Optional; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -60,6 +61,29 @@ private Map initializePlaceholders() { context.getUser() != null ? context.getUser().getName() : ""), entry("cb.user.id", () -> context.getUser() != null ? context.getUser().getIdAsString() : ""), + entry("cb.user.id.accentcolour", () -> + String.valueOf(Main.getColor(Objects.requireNonNull(context.getUser()).getIdAsString()))), + entry("cb.user.id.displayname.server", () -> { + // 1) If explicitly provided via context, prefer that + Object ctxVal = context.getData(ContextManager.ContextType.GENERIC, "user.id.displayname.server"); + if (ctxVal != null) { + return String.valueOf(ctxVal); + } + + // 2) Derive from author/user + server + org.javacord.api.entity.server.Server server = context.getServer(); + org.javacord.api.entity.user.User author = context.getAuthor(); + if (author != null) { + return (server != null) ? author.getDisplayName(server) : author.getName(); + } + + org.javacord.api.entity.user.User user = context.getUser(); + if (user != null) { + return (server != null) ? user.getDisplayName(server) : user.getName(); + } + + return ""; + }), // Author placeholders entry("cb.author.name", () -> @@ -74,7 +98,32 @@ private Map initializePlaceholders() { .map(ServerChannel::getName) .orElse("NOT FOUND")), entry("cb.channel.id", () -> - context.getChannel() != null ? context.getChannel().getIdAsString() : "") + context.getChannel() != null ? context.getChannel().getIdAsString() : ""), + entry("cb.channel.id.mentiontag", () -> + Optional.ofNullable(context.getData(ContextManager.ContextType.GENERIC, "channel.id.mentiontag")) + .map(Object::toString) + .orElseGet(() -> + Optional.ofNullable(context.getChannel()) + .flatMap(ch -> ch.asServerChannel().map(sc -> "<#" + sc.getIdAsString() + ">")) + .orElse("") + )), + + // Command placeholders + entry("cb.help.commandname", () -> + String.valueOf(Objects.requireNonNull(context.getData(ContextManager.ContextType.GENERIC, "commandname")))), + entry("cb.user.id.username", () -> + Optional.ofNullable(context.getAuthor()) + .map(org.javacord.api.entity.user.User::getDiscriminatedName) + .orElseGet(() -> { + Object v = context.getData(ContextManager.ContextType.GENERIC, "user.id.username"); + return v != null ? v.toString() : ""; + })), + + // Error code (supports both generic-scoped and flat key used in some strings) + entry("cb.generic.errorcode", () -> + String.valueOf(Objects.requireNonNull(context.getData(ContextManager.ContextType.GENERIC, "errorcode")))), + entry("cb.errorcode", () -> + String.valueOf(Objects.requireNonNull(context.getData(ContextManager.ContextType.GENERIC, "errorcode")))) ); } @@ -90,7 +139,7 @@ public String process(String input) { Pattern pattern = Pattern.compile("\\{([^}]+)\\}"); Matcher matcher = pattern.matcher(input); - StringBuffer result = new StringBuffer(); + StringBuilder result = new StringBuilder(); while (matcher.find()) { String placeholder = matcher.group(1); diff --git a/src/main/java/com/sidpatchy/clairebot/Listener/ButtonClick.java b/src/main/java/com/sidpatchy/clairebot/Listener/ButtonClick.java index d2689c8..d4a243d 100644 --- a/src/main/java/com/sidpatchy/clairebot/Listener/ButtonClick.java +++ b/src/main/java/com/sidpatchy/clairebot/Listener/ButtonClick.java @@ -63,20 +63,20 @@ public void onButtonClick(ButtonClickEvent event) { case "send": buttonInteraction.acknowledge(); for (int i = 0; i < extractionResult.givers.size(); i++) { - SantaEmbed.getSantaMessage(server, author, extractionResult.givers.get(i), extractionResult.receivers.get(i), extractionResult.rules, extractionResult.theme).send(extractionResult.givers.get(i)); + SantaEmbed.getSantaMessage(languageManager, server, author, extractionResult.givers.get(i), extractionResult.receivers.get(i), extractionResult.rules, extractionResult.theme).send(extractionResult.givers.get(i)); } break; case "test": buttonInteraction.acknowledge(); - SantaEmbed.getSantaMessage(server, author, extractionResult.givers.get(0), extractionResult.receivers.get(0), extractionResult.rules, extractionResult.theme).send(buttonAuthor); + SantaEmbed.getSantaMessage(languageManager, server, author, extractionResult.givers.get(0), extractionResult.receivers.get(0), extractionResult.rules, extractionResult.theme).send(buttonAuthor); break; case "randomize": buttonInteraction.acknowledge(); Role role = Main.getApi().getRoleById(extractionResult.santaID.get("roleID")).orElse(null); buttonInteraction.getMessage().delete(); - SantaEmbed.getHostMessage(role, buttonAuthor, extractionResult.rules, extractionResult.theme).send(buttonAuthor); + SantaEmbed.getHostMessage(languageManager, role, buttonAuthor, extractionResult.rules, extractionResult.theme).send(buttonAuthor); break; diff --git a/src/main/java/com/sidpatchy/clairebot/Listener/ModalSubmit.java b/src/main/java/com/sidpatchy/clairebot/Listener/ModalSubmit.java index 95da653..8667cf6 100644 --- a/src/main/java/com/sidpatchy/clairebot/Listener/ModalSubmit.java +++ b/src/main/java/com/sidpatchy/clairebot/Listener/ModalSubmit.java @@ -3,10 +3,13 @@ import com.sidpatchy.clairebot.Embed.Commands.Regular.SantaEmbed; import com.sidpatchy.clairebot.Embed.Commands.Regular.UserPreferencesEmbed; import com.sidpatchy.clairebot.Embed.Commands.Regular.VotingEmbed; +import com.sidpatchy.clairebot.Lang.ContextManager; +import com.sidpatchy.clairebot.Lang.LanguageManager; import com.sidpatchy.clairebot.Main; import com.sidpatchy.clairebot.Util.ChannelUtils; import com.sidpatchy.clairebot.Util.SantaUtils; import org.javacord.api.entity.channel.ServerTextChannel; +import org.javacord.api.entity.channel.TextChannel; import org.javacord.api.entity.message.Message; import org.javacord.api.entity.message.MessageFlag; import org.javacord.api.entity.message.embed.Embed; @@ -18,17 +21,26 @@ import org.javacord.api.interaction.ModalInteraction; import org.javacord.api.listener.interaction.ModalSubmitListener; +import java.util.HashMap; + public class ModalSubmit implements ModalSubmitListener { + private LanguageManager languageManager; + @Override public void onModalSubmit(ModalSubmitEvent event) { ModalInteraction modalInteraction = event.getModalInteraction(); + Server server = modalInteraction.getServer().orElse(null); + TextChannel textchannel = modalInteraction.getChannel().orElse(null); User user = modalInteraction.getUser(); String modalID = modalInteraction.getCustomId(); Main.getLogger().debug(modalID); + ContextManager context = new ContextManager(server, textchannel, user, user, null, new HashMap<>()); + languageManager = new LanguageManager(Main.getFallbackLocale(), context); + String voteType = ""; // Allows the Poll/Request feature to distinguish between the two // Santa related vars @@ -77,7 +89,7 @@ else if (modalID.startsWith("santa-theme")) { Main.getLogger().debug(hexColour); modalInteraction.createImmediateResponder() - .addEmbed(UserPreferencesEmbed.getAcknowledgeAccentColourChange(user, hexColour)) + .addEmbed(UserPreferencesEmbed.getAcknowledgeAccentColourChange(languageManager, user, hexColour)) .setFlags(MessageFlag.EPHEMERAL) .respond(); } @@ -85,7 +97,6 @@ else if (modalID.startsWith("santa-theme")) { case "request": String question = modalInteraction.getTextInputValueByCustomId("question-modal").orElse(""); String description = modalInteraction.getTextInputValueByCustomId("details-modal").orElse(""); - Server server = modalInteraction.getServer().orElse(null); User author = modalInteraction.getUser(); if (server == null) { @@ -95,15 +106,15 @@ else if (modalID.startsWith("santa-theme")) { if (voteType.equalsIgnoreCase("request")) { ServerTextChannel requestsChannel = ChannelUtils.getRequestsChannel(server); modalInteraction.createImmediateResponder() - .addEmbed(VotingEmbed.getUserResponse(author, requestsChannel.getMentionTag())) + .addEmbed(VotingEmbed.getUserResponse(languageManager, author, requestsChannel.getMentionTag())) .setFlags(MessageFlag.EPHEMERAL) .respond(); - requestsChannel.sendMessage(VotingEmbed.getPoll(voteType, question, description, false, null, server, author, 0)); + requestsChannel.sendMessage(VotingEmbed.getPoll(languageManager, voteType, question, description, false, null, server, author, 0)); } else if (voteType.equalsIgnoreCase("poll")) { modalInteraction.createImmediateResponder() - .addEmbed(VotingEmbed.getPoll(voteType, question, description, false, null, server, author, 0)) + .addEmbed(VotingEmbed.getPoll(languageManager, voteType, question, description, false, null, server, author, 0)) .respond(); } @@ -116,7 +127,7 @@ else if (voteType.equalsIgnoreCase("poll")) { Role role = Main.getApi().getRoleById(extractionResult.santaID.get("roleID")).orElse(null); assert role != null; - SantaEmbed.getHostMessage(role, user, extractionResult.rules, extractionResult.theme).send(user); + SantaEmbed.getHostMessage(languageManager, role, user, extractionResult.rules, extractionResult.theme).send(user); santaMessage.delete(); } } diff --git a/src/main/java/com/sidpatchy/clairebot/Listener/SelectMenuChoose.java b/src/main/java/com/sidpatchy/clairebot/Listener/SelectMenuChoose.java index 67b02c1..693c527 100644 --- a/src/main/java/com/sidpatchy/clairebot/Listener/SelectMenuChoose.java +++ b/src/main/java/com/sidpatchy/clairebot/Listener/SelectMenuChoose.java @@ -3,6 +3,8 @@ import com.sidpatchy.clairebot.Embed.Commands.Regular.ServerPreferencesEmbed; import com.sidpatchy.clairebot.Embed.Commands.Regular.UserPreferencesEmbed; import com.sidpatchy.clairebot.Embed.ErrorEmbed; +import com.sidpatchy.clairebot.Lang.ContextManager; +import com.sidpatchy.clairebot.Lang.LanguageManager; import com.sidpatchy.clairebot.Main; import com.sidpatchy.clairebot.MessageComponents.Regular.ServerPreferencesComponents; import com.sidpatchy.clairebot.MessageComponents.Regular.UserPreferencesComponents; @@ -17,8 +19,11 @@ import org.javacord.api.interaction.SelectMenuInteraction; import org.javacord.api.listener.interaction.SelectMenuChooseListener; +import java.util.HashMap; + public class SelectMenuChoose implements SelectMenuChooseListener { Logger logger = Main.getLogger(); + private LanguageManager languageManager; @Override public void onSelectMenuChoose(SelectMenuChooseEvent event) { @@ -29,6 +34,9 @@ public void onSelectMenuChoose(SelectMenuChooseEvent event) { Server server = selectMenuInteraction.getServer().orElse(null); TextChannel channel = selectMenuInteraction.getChannel().orElse(null); + ContextManager context = new ContextManager(server, channel, user, user, null, new HashMap<>()); + languageManager = new LanguageManager(Main.getFallbackLocale(), context); + // Not speaking of message author, rather, the header field EmbedAuthor embedAuthor = message.getEmbeds().get(0).getAuthor().orElse(null); if (embedAuthor != null && channel != null) { @@ -43,14 +51,14 @@ public void onSelectMenuChoose(SelectMenuChooseEvent event) { if (label.equalsIgnoreCase("Accent Colour")) { selectMenuInteraction.createFollowupMessageBuilder() .setFlags(MessageFlag.EPHEMERAL) - .addEmbed(UserPreferencesEmbed.getAccentColourMenu(user)) + .addEmbed(UserPreferencesEmbed.getAccentColourMenu(languageManager, user)) .addComponents(UserPreferencesComponents.getAccentColourMenu()) .send(); } else if (label.equalsIgnoreCase("Language")) { selectMenuInteraction.createFollowupMessageBuilder() .setFlags(MessageFlag.EPHEMERAL) - .addEmbed(UserPreferencesEmbed.getLanguageMenu(user)) + .addEmbed(UserPreferencesEmbed.getLanguageMenu(languageManager, user)) .addComponents() .send(); } @@ -59,7 +67,7 @@ else if (menuName.equalsIgnoreCase("Accent Colour Editor")) { if (label.equalsIgnoreCase("Select Common Colours")) { selectMenuInteraction.createFollowupMessageBuilder() .setFlags(MessageFlag.EPHEMERAL) - .addEmbed(UserPreferencesEmbed.getAccentColourListMenu(user)) + .addEmbed(UserPreferencesEmbed.getAccentColourListMenu(languageManager, user)) .addComponents(UserPreferencesComponents.getAccentColourList()) .send(); } @@ -83,7 +91,7 @@ else if (menuName.equalsIgnoreCase("Accent Colour List")) { else { selectMenuInteraction.createFollowupMessageBuilder() .setFlags(MessageFlag.EPHEMERAL) - .addEmbed(UserPreferencesEmbed.getAcknowledgeAccentColourChange(user, accentColour)) + .addEmbed(UserPreferencesEmbed.getAcknowledgeAccentColourChange(languageManager, user, accentColour)) .addComponents() .send(); } @@ -96,28 +104,28 @@ else if (menuName.equalsIgnoreCase("Server Configuration Editor")) { if (server == null) { selectMenuInteraction.createFollowupMessageBuilder() .setFlags(MessageFlag.EPHEMERAL) - .addEmbed(ServerPreferencesEmbed.getNotServerMenu()) + .addEmbed(ServerPreferencesEmbed.getNotServerMenu(languageManager)) .addComponents() .send(); } else if (label.equalsIgnoreCase("Requests Channel")) { selectMenuInteraction.createFollowupMessageBuilder() .setFlags(MessageFlag.EPHEMERAL) - .addEmbed(ServerPreferencesEmbed.getRequestsChannelMenu(user)) + .addEmbed(ServerPreferencesEmbed.getRequestsChannelMenu(languageManager, user)) .addComponents(ServerPreferencesComponents.getRequestsChannelMenu(server)) .send(); } else if (label.equalsIgnoreCase("Moderator Messages Channel")) { selectMenuInteraction.createFollowupMessageBuilder() .setFlags(MessageFlag.EPHEMERAL) - .addEmbed(ServerPreferencesEmbed.getModeratorChannelMenu(user)) + .addEmbed(ServerPreferencesEmbed.getModeratorChannelMenu(languageManager, user)) .addComponents(ServerPreferencesComponents.getModeratorChannelMenu(server)) .send(); } else if (label.equalsIgnoreCase("Enforce Server Language")) { selectMenuInteraction.createFollowupMessageBuilder() .setFlags(MessageFlag.EPHEMERAL) - .addEmbed(ServerPreferencesEmbed.getEnforceServerLangMenu(user)) + .addEmbed(ServerPreferencesEmbed.getEnforceServerLangMenu(languageManager, user)) .addComponents(ServerPreferencesComponents.getEnforceServerLanguageMenu()) .send(); } @@ -129,7 +137,7 @@ else if (menuName.equalsIgnoreCase("Requests Channel")) { selectMenuInteraction.createFollowupMessageBuilder() .setFlags(MessageFlag.EPHEMERAL) - .addEmbed(ServerPreferencesEmbed.getAcknowledgeRequestsChannelChange(server, user, channelID)) + .addEmbed(ServerPreferencesEmbed.getAcknowledgeRequestsChannelChange(languageManager, server, user, channelID)) .addComponents() .send(); } @@ -140,7 +148,7 @@ else if (menuName.equalsIgnoreCase("Moderator Messages Channel")) { selectMenuInteraction.createFollowupMessageBuilder() .setFlags(MessageFlag.EPHEMERAL) - .addEmbed(ServerPreferencesEmbed.getAcknowledgeModeratorChannelChange(server, user, channelID)) + .addEmbed(ServerPreferencesEmbed.getAcknowledgeModeratorChannelChange(languageManager, server, user, channelID)) .addComponents() .send(); } @@ -151,7 +159,7 @@ else if (menuName.equalsIgnoreCase("Enforce Server Language")) { selectMenuInteraction.createFollowupMessageBuilder() .setFlags(MessageFlag.EPHEMERAL) - .addEmbed(ServerPreferencesEmbed.getAcknowledgeEnforceServerLanguageUpdate(server, user, bool)) + .addEmbed(ServerPreferencesEmbed.getAcknowledgeEnforceServerLanguageUpdate(languageManager, server, user, bool)) .addComponents() .send(); } diff --git a/src/main/java/com/sidpatchy/clairebot/Listener/ServerJoin.java b/src/main/java/com/sidpatchy/clairebot/Listener/ServerJoin.java index b689d8f..c868942 100644 --- a/src/main/java/com/sidpatchy/clairebot/Listener/ServerJoin.java +++ b/src/main/java/com/sidpatchy/clairebot/Listener/ServerJoin.java @@ -1,6 +1,9 @@ package com.sidpatchy.clairebot.Listener; import com.sidpatchy.clairebot.Embed.WelcomeEmbed; +import com.sidpatchy.clairebot.Lang.ContextManager; +import com.sidpatchy.clairebot.Lang.LanguageManager; +import com.sidpatchy.clairebot.Main; import com.sidpatchy.clairebot.Util.ChannelUtils; import org.javacord.api.entity.channel.TextChannel; import org.javacord.api.entity.server.Server; @@ -9,6 +12,8 @@ public class ServerJoin implements ServerJoinListener { + private LanguageManager languageManager; + /** * * Welcome users to ClaireBot when added to a new server. @@ -19,7 +24,9 @@ public class ServerJoin implements ServerJoinListener { public void onServerJoin(ServerJoinEvent event) { Server server = event.getServer(); + languageManager = new LanguageManager(Main.getFallbackLocale(), null); // this null should probably be fine + TextChannel channel = ChannelUtils.getModeratorsOnlyChannel(server); - channel.sendMessage(WelcomeEmbed.getWelcome(server)); + channel.sendMessage(WelcomeEmbed.getWelcome(languageManager, server)); } } diff --git a/src/main/java/com/sidpatchy/clairebot/Listener/SlashCommandCreate.java b/src/main/java/com/sidpatchy/clairebot/Listener/SlashCommandCreate.java index 3b05b0c..8efbd74 100644 --- a/src/main/java/com/sidpatchy/clairebot/Listener/SlashCommandCreate.java +++ b/src/main/java/com/sidpatchy/clairebot/Listener/SlashCommandCreate.java @@ -81,7 +81,7 @@ else if (commandName.equalsIgnoreCase(commands.getConfig().getName())) { if (mode.equalsIgnoreCase("user")) { slashCommandInteraction.createImmediateResponder() .setFlags(MessageFlag.EPHEMERAL) - .addEmbed(UserPreferencesEmbed.getMainMenu(author)) + .addEmbed(UserPreferencesEmbed.getMainMenu(languageManager, author)) .addComponents(UserPreferencesComponents.getMainMenu()) .respond(); } @@ -90,7 +90,7 @@ else if (mode.equalsIgnoreCase("server") && server != null) { if (server.isAdmin(author)) { slashCommandInteraction.createImmediateResponder() .setFlags(MessageFlag.EPHEMERAL) - .addEmbed(ServerPreferencesEmbed.getMainMenu(author)) + .addEmbed(ServerPreferencesEmbed.getMainMenu(languageManager, author)) .addComponents(ServerPreferencesComponents.getMainMenu()) .respond(); } @@ -181,7 +181,7 @@ else if (commandName.equalsIgnoreCase(commands.getPoll().getName())) { int finalNumChoices = numChoices; slashCommandInteraction.respondLater().thenAccept(interactionOriginalResponseUpdater -> { - interactionOriginalResponseUpdater.addEmbed(VotingEmbed.getPoll("POLL", question, allowMultipleChoices, choices, server, author, finalNumChoices)) + interactionOriginalResponseUpdater.addEmbed(VotingEmbed.getPoll(languageManager, "POLL", question, allowMultipleChoices, choices, server, author, finalNumChoices)) .update().thenAccept(message -> { message.addReaction("\uD83D\uDC4D"); // 👍 emoji message.addReaction("\uD83D\uDC4E"); // 👎 emoji @@ -256,11 +256,11 @@ else if (commandName.equalsIgnoreCase(commands.getRequest().getName())) { } slashCommandInteraction.createImmediateResponder() - .addEmbed(VotingEmbed.getUserResponse(author, ChannelUtils.getRequestsChannel(server).getMentionTag())) + .addEmbed(VotingEmbed.getUserResponse(languageManager, author, ChannelUtils.getRequestsChannel(server).getMentionTag())) .setFlags(MessageFlag.EPHEMERAL) .respond(); - ChannelUtils.getRequestsChannel(server).sendMessage(VotingEmbed.getPoll("REQUEST", question, allowMultipleChoices, choices, server, author, numChoices)).thenAccept(message -> { + ChannelUtils.getRequestsChannel(server).sendMessage(VotingEmbed.getPoll(languageManager, "REQUEST", question, allowMultipleChoices, choices, server, author, numChoices)).thenAccept(message -> { message.addReaction("\uD83D\uDC4D"); message.addReaction("\uD83D\uDC4E"); message.addReaction(":vote:706373563564949566"); @@ -279,14 +279,14 @@ else if (commandName.equalsIgnoreCase(commands.getServer().getName())) { if (guildID != null) { Server fromGuildID = event.getApi().getServerById(guildID).orElse(null); if (fromGuildID != null) { - embed = ServerInfoEmbed.getServerInfo(fromGuildID, user.getIdAsString()); + embed = ServerInfoEmbed.getServerInfo(languageManager, fromGuildID, user.getIdAsString()); } else { embed = ErrorEmbed.getCustomError(Main.getErrorCode("guildID-invalid"), "Either that guild ID is invalid or I'm not a member of the server."); } } else if (server != null) { - embed = ServerInfoEmbed.getServerInfo(server, user.getIdAsString()); + embed = ServerInfoEmbed.getServerInfo(languageManager, server, user.getIdAsString()); } slashCommandInteraction.createImmediateResponder() @@ -295,7 +295,7 @@ else if (server != null) { } else if (commandName.equalsIgnoreCase(commands.getInfo().getName())) { slashCommandInteraction.createImmediateResponder() - .addEmbed(UserInfoEmbed.getUser(user, author, server)) + .addEmbed(UserInfoEmbed.getUser(languageManager, user, author, server)) .respond(); } else if (commandName.equalsIgnoreCase(commands.getSanta().getName())) { @@ -317,10 +317,10 @@ else if (commandName.equalsIgnoreCase(commands.getSanta().getName())) { } slashCommandInteraction.createImmediateResponder().addEmbed( - SantaEmbed.getConfirmationEmbed(author) + SantaEmbed.getConfirmationEmbed(languageManager, author) ).respond(); - SantaEmbed.getHostMessage(role, author, "", "").send(author); + SantaEmbed.getHostMessage(languageManager, role, author, "", "").send(author); } } } diff --git a/src/main/resources/translations/lang_en-US.yml b/src/main/resources/translations/lang_en-US.yml index 3b2fa98..099c5a7 100644 --- a/src/main/resources/translations/lang_en-US.yml +++ b/src/main/resources/translations/lang_en-US.yml @@ -146,7 +146,7 @@ ClaireLang: HelpEmbed: Commands: "Commands" Usage: "Usage" - Error: "Unable to locate help data for \"{clairebot.placeholder.help.commandname}\". Error code: {clairebot.placeholder.generic.errorcode}" + Error: "Unable to locate help data for \"{cb.help.commandname}\". Error code: {cb.generic.errorcode}" InfoEmbed: NeedHelp: "Need Help?" NeedHelpDetails: "You can get help by creating an issue on our [GitHub](https://github.com/Sidpatchy/ClaireBot/issues) or by joining our [support server](https://support.clairebot.net/)" @@ -175,10 +175,11 @@ ClaireLang: TestButton: "Send Sample" RandomizeButton: "Re-randomize" SentByAuthor: "Sent by" - GiverMessage: "Ho! Ho! Ho! You have recieved **{clairebot.placeholder.user.id.displayname.server}** in the {clairebot.placeholder.serverid.name} Secret Santa!" + GiverMessage: "Ho! Ho! Ho! You have recieved **{cb.user.id.displayname.server}** in the {cb.serverid.name} Secret Santa!" ServerInfoEmbed: ServerID: "Server ID" Owner: "Owner" + CreationDate: "Creation Date" RoleCount: "Role Count" MemberCount: "Member Count" ChannelCounts: "Channel Counts" @@ -194,15 +195,15 @@ ClaireLang: ModeratorChannelDescription: "Only lists the first 25 channels in the server." EnforceServerLanguageMenuName: "Enforce Server Language" AcknowledgeRequestsChannelChangeTitle: "Requests Channel Changed!" - AcknowledgeRequestsChannelChangeDescription: "Your requests channel has been changed to {clairebot.placeholder.channel.id.mentiontag}" + AcknowledgeRequestsChannelChangeDescription: "Your requests channel has been changed to {cb.channel.id.mentiontag}" AcknowledgeModeratorChannelChangeTitle: "Moderator Channel Changed!" - AcknowledgeModeratorChannelChangeDescription: "Your moderator messages channel has been changed to {clairebot.placeholder.channel.id.mentiontag}" + AcknowledgeModeratorChannelChangeDescription: "Your moderator messages channel has been changed to {cb.channel.id.mentiontag}" AcknowledgeEnforceServerLanguageUpdateTitle: "Server Language Preferences Updated!" AcknowledgeEnforceServerLanguageUpdateEnforced: "I will now follow the server's language regardless of user preference." AcknowledgeEnforceServerLanguageUpdateNotEnforced: "I will follow each individual user's language preference." UserInfoEmbed: - Error_1: "The value for author was null when passed into UserInfo Embed. Error code: {clairebot.placeholder.errorcode}" - Error_2: "Author: {clairebot.placeholder.user.id.username}" + Error_1: "The value for author was null when passed into UserInfo Embed. Error code: {cb.errorcode}" + Error_2: "Author: {cb.user.id.username}" User: "User" DiscordID: "Discord ID" JoinDate: "Server Join Date" @@ -212,16 +213,16 @@ ClaireLang: AccentColourMenu: "Accent Colour Editor" AccentColourList: "Accent Colour List" AccentColourChanged: "Acccent Colour Changed!" - AccentColourChangedDesc: "Your accent colour has been changed to {clairebot.placeholder.user.id.accentcolour}" + AccentColourChangedDesc: "Your accent colour has been changed to {cb.user.id.accentcolour}" LanguageMenuTitle: "NYI" LanguageMenuDesc: "Sorry, this feature is not yet implemented." VotingEmbed: - PollRequest: "{clairebot.placeholder.user.id.displayname.server} requests:" - PollAsk: "{clairebot.placeholder.user.id.displayname.server} asks:" + PollRequest: "{cb.user.id.displayname.server} requests:" + PollAsk: "{cb.user.id.displayname.server} asks:" Choices: "Choices" - PollID: "Poll ID: {clairebot.placeholder.poll.id}" + PollID: "Poll ID: {cb.poll.id}" UserResponseTitle: "Your request has been created!" - UserResponseDescription: "Go check it out in {clairebot.placeholder.channel.id.mentiontag}" + UserResponseDescription: "Go check it out in {cb.channel.id.mentiontag}" ErrorEmbed: Error: "ERROR" GenericDescription: "It appears that I've encountered an error, oops! Please try running the command once more and if that doesn't work, join my [Discord server]({cb.supportserver) and let us know about the issue.\n\nPlease include the following error code: {cb.errorcode}" From 5ce35c34a05151716c5125e997e15f20856ec8a3 Mon Sep 17 00:00:00 2001 From: Sidpatchy <35241490+Sidpatchy@users.noreply.github.com> Date: Fri, 19 Sep 2025 20:23:44 -0600 Subject: [PATCH 26/64] feat: (mostly) finalize English language file --- .../resources/translations/lang_en-US.yml | 74 ++++++++++++++++++- 1 file changed, 73 insertions(+), 1 deletion(-) diff --git a/src/main/resources/translations/lang_en-US.yml b/src/main/resources/translations/lang_en-US.yml index 099c5a7..224c863 100644 --- a/src/main/resources/translations/lang_en-US.yml +++ b/src/main/resources/translations/lang_en-US.yml @@ -46,6 +46,37 @@ version: 1 # -------------------------------------------------------------------------------------------------------------------- # ClaireLang: + # Generic Strings which are likely ot be used in multiple places. + Generic: + True: "True" + False: "False" + Yes: "Yes" + No: "No" + ClickToDisplaySettings: "Click to display settings" + + Colors: + ClaireBotBlue: "ClaireBot Blue" + Red: "Red" + Pink: "Pink" + Purple: "Purple" + DeepPurple: "Deep Purple" + Indigo: "Indigo" + Blue: "Blue" + LightBlue: "Light Blue" + Cyan: "Cyan" + Teal: "Teal" + Green: "Green" + Olive: "Olive" + LightGreen: "Light Green" + Lime: "Lime" + Yellow: "Yellow" + Amber: "Amber" + NRAXOrange: "NRAX Orange" + DeepOrange: "Deep Orange" + White: "White" + Grey: "Grey" + Black: "Black" + PlsBan: PlsBanTriggers: - "pls ban" @@ -232,4 +263,45 @@ ClaireLang: UsageTitle: "Usage" UsageDesc: "Get started by running `{cb.command.help.name}`. Need more info on a command? Run `/{cb.command.help.name} ` (ex. `/{cb.command.help.name} user`)" SupportTitle: "Get Support" - SupportDesc: "You can get help on our [Discord]({cb.supportserver}), or by opening an issue on [GitHub]({cb.github})" \ No newline at end of file + SupportDesc: "You can get help on our [Discord]({cb.supportserver}), or by opening an issue on [GitHub]({cb.github})" + + # Message Components (Buttons, Select Menus, etc.) + Components: + Regular: + SantaModal: + rules-row: "Rules..." + theme-row: "Theme..." + ServerPreferences: + Settings: "Settings" + RequestsChannel: "Requests Channel" + RequestsChannelDescription: "Choose where ClaireBot should post requests." + ModeratorMessagesChannel: "Moderator Messages Channel" + ModeratorMessagesChannelDescription: "Choose where ClaireBot should post moderator messages." + EnforceServerLanguage: "Enforce Server Language" + EnforceServerLanguageDescription: "Force ClaireBot to use the same language as the server regardless of user preference." + EnforceServerLanguagePlaceholder: "Click to select an option" + SelectAChannelPlaceholder: "Click to select a channel" + UserPreferences: + Settings: "Settings" + AccentColour: "Accent Colour" + AccentColourDescription: "For those who hate the colour blue" + Language: "Language" + LanguageDescription: "If you're offended by english" + AccentColourPlaceholder: "Click to choose option" + SelectCommonColours: "Select Common Colours" + SelectCommonColoursDescription: "Select from a list of common colours" + HexadecimalEntry: "Hexadecimal Entry" + HexadecimalEntryDescription: "Enter any hexadecimal colour" + HexEntryPlaceholder: "Enter a hex colour ex. #ff8800" + Voting: + Question: "Question" + Details: "Details" + MultipleChoicePlaceholder: "Click to choose option" + MultipleChoiceYesDescription: "Opens menu to select more options" + MultipleChoiceNoDescription: "Submits {cb.commandname} after clicking submit" + AllowMultipleChoices: "Allow selecting multiple choices?" + AllowMultipleChoicesPlaceholder: "Click to choose option" + AllowMultipleChoicesYesDescription: "Allows the respondent to select more than one option" + AllowMultipleChoicesNoDescription: "Only allows the respondent to select one option" + OptionLabelTemplate: "Option #{cb.voting.optionnumber}" + From 3327fb115e8e37151c3e121e8bd8351ae92a50fd Mon Sep 17 00:00:00 2001 From: Sidpatchy <35241490+Sidpatchy@users.noreply.github.com> Date: Sat, 20 Sep 2025 15:12:09 -0600 Subject: [PATCH 27/64] feat: update/fix placeholders --- .../clairebot/Lang/PlaceholderHandler.java | 29 +++++++------------ .../resources/translations/lang_en-US.yml | 8 ++--- 2 files changed, 15 insertions(+), 22 deletions(-) diff --git a/src/main/java/com/sidpatchy/clairebot/Lang/PlaceholderHandler.java b/src/main/java/com/sidpatchy/clairebot/Lang/PlaceholderHandler.java index 0b9d2a9..a6828dd 100644 --- a/src/main/java/com/sidpatchy/clairebot/Lang/PlaceholderHandler.java +++ b/src/main/java/com/sidpatchy/clairebot/Lang/PlaceholderHandler.java @@ -49,6 +49,7 @@ private Map initializePlaceholders() { entry("cb.bot.releasedate", Main::getBuildDate), entry("cb.bot.startseconds", () -> Main.getStartMillis() / 1000), entry("cb.bot.runtimedurationwords", () -> DurationFormatUtils.formatDurationWords(System.currentTimeMillis() - Main.getStartMillis(), true, false)), + entry("cb.command.help.name", () -> String.valueOf(Main.getCommands().getHelp().getName())), // Server placeholders entry("cb.server.name", () -> @@ -61,16 +62,11 @@ private Map initializePlaceholders() { context.getUser() != null ? context.getUser().getName() : ""), entry("cb.user.id", () -> context.getUser() != null ? context.getUser().getIdAsString() : ""), + entry("cb.user.id.mentiontag", () -> + context.getUser() != null ? "<@" + context.getUser().getIdAsString() + ">" : ""), entry("cb.user.id.accentcolour", () -> String.valueOf(Main.getColor(Objects.requireNonNull(context.getUser()).getIdAsString()))), entry("cb.user.id.displayname.server", () -> { - // 1) If explicitly provided via context, prefer that - Object ctxVal = context.getData(ContextManager.ContextType.GENERIC, "user.id.displayname.server"); - if (ctxVal != null) { - return String.valueOf(ctxVal); - } - - // 2) Derive from author/user + server org.javacord.api.entity.server.Server server = context.getServer(); org.javacord.api.entity.user.User author = context.getAuthor(); if (author != null) { @@ -100,16 +96,10 @@ private Map initializePlaceholders() { entry("cb.channel.id", () -> context.getChannel() != null ? context.getChannel().getIdAsString() : ""), entry("cb.channel.id.mentiontag", () -> - Optional.ofNullable(context.getData(ContextManager.ContextType.GENERIC, "channel.id.mentiontag")) - .map(Object::toString) - .orElseGet(() -> - Optional.ofNullable(context.getChannel()) - .flatMap(ch -> ch.asServerChannel().map(sc -> "<#" + sc.getIdAsString() + ">")) - .orElse("") - )), + context.getChannel() != null ? "<#" + context.getChannel().getIdAsString() + ">" : ""), // Command placeholders - entry("cb.help.commandname", () -> + entry("cb.commandname", () -> String.valueOf(Objects.requireNonNull(context.getData(ContextManager.ContextType.GENERIC, "commandname")))), entry("cb.user.id.username", () -> Optional.ofNullable(context.getAuthor()) @@ -118,10 +108,13 @@ private Map initializePlaceholders() { Object v = context.getData(ContextManager.ContextType.GENERIC, "user.id.username"); return v != null ? v.toString() : ""; })), + // Voting placeholders + entry("cb.poll.id", () -> + String.valueOf(Objects.requireNonNull(context.getData(ContextManager.ContextType.GENERIC, "poll.id")))), + entry("cb.voting.optionnumber", () -> + String.valueOf(Objects.requireNonNull(context.getData(ContextManager.ContextType.GENERIC, "voting.optionnumber")))), - // Error code (supports both generic-scoped and flat key used in some strings) - entry("cb.generic.errorcode", () -> - String.valueOf(Objects.requireNonNull(context.getData(ContextManager.ContextType.GENERIC, "errorcode")))), + // Error code entry("cb.errorcode", () -> String.valueOf(Objects.requireNonNull(context.getData(ContextManager.ContextType.GENERIC, "errorcode")))) ); diff --git a/src/main/resources/translations/lang_en-US.yml b/src/main/resources/translations/lang_en-US.yml index 224c863..2a16bd8 100644 --- a/src/main/resources/translations/lang_en-US.yml +++ b/src/main/resources/translations/lang_en-US.yml @@ -15,7 +15,7 @@ contributors: - ["Sidpatchy", "https://github.com/Sidpatchy/"] # Any notes that translators feel the need to express, written in the target language. -# At your option, you may leave this field blank if you feel that it is not needed. If updating the language file, +# At your discretion, you may leave this field blank if you feel that it is not needed. If updating the language file, # feel free to modify or remove this (make it blank) if it no longer applies. # # If you are contributing, please provide the ClaireBot maintainer(s) with a translated version/explanation of this. @@ -177,7 +177,7 @@ ClaireLang: HelpEmbed: Commands: "Commands" Usage: "Usage" - Error: "Unable to locate help data for \"{cb.help.commandname}\". Error code: {cb.generic.errorcode}" + Error: "Unable to locate help data for \"{cb.commandname}\". Error code: {cb.errorcode}" InfoEmbed: NeedHelp: "Need Help?" NeedHelpDetails: "You can get help by creating an issue on our [GitHub](https://github.com/Sidpatchy/ClaireBot/issues) or by joining our [support server](https://support.clairebot.net/)" @@ -206,7 +206,7 @@ ClaireLang: TestButton: "Send Sample" RandomizeButton: "Re-randomize" SentByAuthor: "Sent by" - GiverMessage: "Ho! Ho! Ho! You have recieved **{cb.user.id.displayname.server}** in the {cb.serverid.name} Secret Santa!" + GiverMessage: "Ho! Ho! Ho! You have recieved **{cb.user.id.displayname.server}** in the {cb.server.name} Secret Santa!" ServerInfoEmbed: ServerID: "Server ID" Owner: "Owner" @@ -234,7 +234,7 @@ ClaireLang: AcknowledgeEnforceServerLanguageUpdateNotEnforced: "I will follow each individual user's language preference." UserInfoEmbed: Error_1: "The value for author was null when passed into UserInfo Embed. Error code: {cb.errorcode}" - Error_2: "Author: {cb.user.id.username}" + Error_2: "Author: {cb.user.name}" User: "User" DiscordID: "Discord ID" JoinDate: "Server Join Date" From 10e1f675793d35795ff301fe628081d59b2ccc59 Mon Sep 17 00:00:00 2001 From: Sidpatchy <35241490+Sidpatchy@users.noreply.github.com> Date: Sat, 20 Sep 2025 22:58:40 -0600 Subject: [PATCH 28/64] feat: finalize(?) localization --- .../clairebot/Listener/ButtonClick.java | 4 +- .../clairebot/Listener/SelectMenuChoose.java | 12 +-- .../Listener/SlashCommandCreate.java | 12 +-- .../MessageComponents/Regular/SantaModal.java | 12 ++- .../Regular/ServerPreferencesComponents.java | 52 +++++++--- .../Regular/UserPreferencesComponents.java | 94 +++++++++++++++---- .../Regular/VotingComponents.java | 70 ++++++++++---- 7 files changed, 188 insertions(+), 68 deletions(-) diff --git a/src/main/java/com/sidpatchy/clairebot/Listener/ButtonClick.java b/src/main/java/com/sidpatchy/clairebot/Listener/ButtonClick.java index d4a243d..a87d995 100644 --- a/src/main/java/com/sidpatchy/clairebot/Listener/ButtonClick.java +++ b/src/main/java/com/sidpatchy/clairebot/Listener/ButtonClick.java @@ -50,13 +50,13 @@ public void onButtonClick(ButtonClickEvent event) { switch (buttonID) { case "rules": buttonInteraction.respondWithModal("santa-rules-" + message.getIdAsString(), "Update Rules", - SantaModal.getRulesRow() + SantaModal.getRulesRow(languageManager) ); break; case "theme": buttonInteraction.respondWithModal("santa-theme-" + message.getIdAsString(), "Update Theme", - SantaModal.getThemeRow() + SantaModal.getThemeRow(languageManager) ); break; diff --git a/src/main/java/com/sidpatchy/clairebot/Listener/SelectMenuChoose.java b/src/main/java/com/sidpatchy/clairebot/Listener/SelectMenuChoose.java index 693c527..e33ab4e 100644 --- a/src/main/java/com/sidpatchy/clairebot/Listener/SelectMenuChoose.java +++ b/src/main/java/com/sidpatchy/clairebot/Listener/SelectMenuChoose.java @@ -52,7 +52,7 @@ public void onSelectMenuChoose(SelectMenuChooseEvent event) { selectMenuInteraction.createFollowupMessageBuilder() .setFlags(MessageFlag.EPHEMERAL) .addEmbed(UserPreferencesEmbed.getAccentColourMenu(languageManager, user)) - .addComponents(UserPreferencesComponents.getAccentColourMenu()) + .addComponents(UserPreferencesComponents.getAccentColourMenu(languageManager)) .send(); } else if (label.equalsIgnoreCase("Language")) { @@ -68,12 +68,12 @@ else if (menuName.equalsIgnoreCase("Accent Colour Editor")) { selectMenuInteraction.createFollowupMessageBuilder() .setFlags(MessageFlag.EPHEMERAL) .addEmbed(UserPreferencesEmbed.getAccentColourListMenu(languageManager, user)) - .addComponents(UserPreferencesComponents.getAccentColourList()) + .addComponents(UserPreferencesComponents.getAccentColourList(languageManager)) .send(); } else if (label.equalsIgnoreCase("Hexadecimal Entry")) { message.delete(); - selectMenuInteraction.respondWithModal("hex-entry-modal", "Hex Colour Entry", UserPreferencesComponents.getAccentColourHexEntry()); + selectMenuInteraction.respondWithModal("hex-entry-modal", "Hex Colour Entry", UserPreferencesComponents.getAccentColourHexEntry(languageManager)); } selectMenuInteraction.acknowledge(); } @@ -112,21 +112,21 @@ else if (label.equalsIgnoreCase("Requests Channel")) { selectMenuInteraction.createFollowupMessageBuilder() .setFlags(MessageFlag.EPHEMERAL) .addEmbed(ServerPreferencesEmbed.getRequestsChannelMenu(languageManager, user)) - .addComponents(ServerPreferencesComponents.getRequestsChannelMenu(server)) + .addComponents(ServerPreferencesComponents.getRequestsChannelMenu(languageManager, server)) .send(); } else if (label.equalsIgnoreCase("Moderator Messages Channel")) { selectMenuInteraction.createFollowupMessageBuilder() .setFlags(MessageFlag.EPHEMERAL) .addEmbed(ServerPreferencesEmbed.getModeratorChannelMenu(languageManager, user)) - .addComponents(ServerPreferencesComponents.getModeratorChannelMenu(server)) + .addComponents(ServerPreferencesComponents.getModeratorChannelMenu(languageManager, server)) .send(); } else if (label.equalsIgnoreCase("Enforce Server Language")) { selectMenuInteraction.createFollowupMessageBuilder() .setFlags(MessageFlag.EPHEMERAL) .addEmbed(ServerPreferencesEmbed.getEnforceServerLangMenu(languageManager, user)) - .addComponents(ServerPreferencesComponents.getEnforceServerLanguageMenu()) + .addComponents(ServerPreferencesComponents.getEnforceServerLanguageMenu(languageManager)) .send(); } } diff --git a/src/main/java/com/sidpatchy/clairebot/Listener/SlashCommandCreate.java b/src/main/java/com/sidpatchy/clairebot/Listener/SlashCommandCreate.java index 8efbd74..16efc4e 100644 --- a/src/main/java/com/sidpatchy/clairebot/Listener/SlashCommandCreate.java +++ b/src/main/java/com/sidpatchy/clairebot/Listener/SlashCommandCreate.java @@ -82,7 +82,7 @@ else if (commandName.equalsIgnoreCase(commands.getConfig().getName())) { slashCommandInteraction.createImmediateResponder() .setFlags(MessageFlag.EPHEMERAL) .addEmbed(UserPreferencesEmbed.getMainMenu(languageManager, author)) - .addComponents(UserPreferencesComponents.getMainMenu()) + .addComponents(UserPreferencesComponents.getMainMenu(languageManager)) .respond(); } else if (mode.equalsIgnoreCase("server") && server != null) { @@ -91,7 +91,7 @@ else if (mode.equalsIgnoreCase("server") && server != null) { slashCommandInteraction.createImmediateResponder() .setFlags(MessageFlag.EPHEMERAL) .addEmbed(ServerPreferencesEmbed.getMainMenu(languageManager, author)) - .addComponents(ServerPreferencesComponents.getMainMenu()) + .addComponents(ServerPreferencesComponents.getMainMenu(languageManager)) .respond(); } else { @@ -151,8 +151,8 @@ else if (commandName.equalsIgnoreCase(commands.getPoll().getName())) { try { // LOL how long has this been unimplemented? Not a bad idea tbh 2023-02-16 CompletableFuture pollModal = slashCommandInteraction.respondWithModal("poll", "Create Poll", - VotingComponents.getQuestionRow(), - VotingComponents.getDetailsRow() + VotingComponents.getQuestionRow(languageManager), + VotingComponents.getDetailsRow(languageManager) ); pollModal.exceptionally(e -> { @@ -227,8 +227,8 @@ else if (commandName.equalsIgnoreCase(commands.getRequest().getName())) { try { // LOL how long has this been unimplemented? Not a bad idea tbh 2023-02-16 CompletableFuture pollModal = slashCommandInteraction.respondWithModal("request", "Create Request", - VotingComponents.getQuestionRow(), - VotingComponents.getDetailsRow() + VotingComponents.getQuestionRow(languageManager), + VotingComponents.getDetailsRow(languageManager) ); pollModal.exceptionally(e -> { diff --git a/src/main/java/com/sidpatchy/clairebot/MessageComponents/Regular/SantaModal.java b/src/main/java/com/sidpatchy/clairebot/MessageComponents/Regular/SantaModal.java index 8ca1ded..6ed312d 100644 --- a/src/main/java/com/sidpatchy/clairebot/MessageComponents/Regular/SantaModal.java +++ b/src/main/java/com/sidpatchy/clairebot/MessageComponents/Regular/SantaModal.java @@ -1,16 +1,22 @@ package com.sidpatchy.clairebot.MessageComponents.Regular; +import com.sidpatchy.clairebot.Lang.LanguageManager; import org.javacord.api.entity.message.component.ActionRow; import org.javacord.api.entity.message.component.TextInput; import org.javacord.api.entity.message.component.TextInputStyle; +import org.javacord.api.entity.user.User; public class SantaModal { - public static ActionRow getRulesRow() { - return ActionRow.of(TextInput.create(TextInputStyle.PARAGRAPH, "rules-row", "Rules...")); + public static ActionRow getRulesRow(LanguageManager languageManager) { + String rules = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Components.Regular.SantaModal.rules-row"); + + return ActionRow.of(TextInput.create(TextInputStyle.PARAGRAPH, "rules-row", rules)); } - public static ActionRow getThemeRow() { + public static ActionRow getThemeRow(LanguageManager languageManager) { + String theme = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Components.Regular.SantaModal.theme-row"); + return ActionRow.of(TextInput.create(TextInputStyle.PARAGRAPH, "theme-row", "Theme...")); } diff --git a/src/main/java/com/sidpatchy/clairebot/MessageComponents/Regular/ServerPreferencesComponents.java b/src/main/java/com/sidpatchy/clairebot/MessageComponents/Regular/ServerPreferencesComponents.java index fb6aa34..bc421f6 100644 --- a/src/main/java/com/sidpatchy/clairebot/MessageComponents/Regular/ServerPreferencesComponents.java +++ b/src/main/java/com/sidpatchy/clairebot/MessageComponents/Regular/ServerPreferencesComponents.java @@ -1,5 +1,6 @@ package com.sidpatchy.clairebot.MessageComponents.Regular; +import com.sidpatchy.clairebot.Lang.LanguageManager; import org.javacord.api.entity.channel.ServerTextChannel; import org.javacord.api.entity.message.component.ActionRow; import org.javacord.api.entity.message.component.ActionRowBuilder; @@ -13,31 +14,49 @@ public class ServerPreferencesComponents { - public static ActionRow getMainMenu() { + public static ActionRow getMainMenu(LanguageManager languageManager) { + String placeholder = languageManager.getLocalizedString("ClaireLang.Generic.ClickToDisplaySettings"); + + String requestsLabel = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Components.Regular.ServerPreferences.RequestsChannel"); + String requestsDesc = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Components.Regular.ServerPreferences.RequestsChannelDescription"); + + String modLabel = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Components.Regular.ServerPreferences.ModeratorMessagesChannel"); + String modDesc = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Components.Regular.ServerPreferences.ModeratorMessagesChannelDescription"); + + String enforceLabel = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Components.Regular.ServerPreferences.EnforceServerLanguage"); + String enforceDesc = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Components.Regular.ServerPreferences.EnforceServerLanguageDescription"); + return new ActionRowBuilder() .addComponents( - SelectMenu.create("settings", "Click to display settings", 1, 1, - Arrays.asList(SelectMenuOption.create("Requests Channel", "Requests Channel", "Choose where ClaireBot should post requests."), - SelectMenuOption.create("Moderator Messages Channel", "Moderator Messages Channel", "Choose where ClaireBot should mod messages."), - SelectMenuOption.create("Enforce Server Language", "Enforce Server Language", "Force ClaireBot to use the same language as the server regardless of user preference."))) + SelectMenu.create("settings", placeholder, 1, 1, + Arrays.asList( + // Keep values stable for interaction handlers + SelectMenuOption.create(requestsLabel, "Requests Channel", requestsDesc), + SelectMenuOption.create(modLabel, "Moderator Messages Channel", modDesc), + SelectMenuOption.create(enforceLabel, "Enforce Server Language", enforceDesc) + )) ).build(); } - public static ActionRow getRequestsChannelMenu(Server server) { - return getChannelListActionRow(server, "requestsChannel"); + public static ActionRow getRequestsChannelMenu(LanguageManager languageManager, Server server) { + return getChannelListActionRow(languageManager, server, "requestsChannel"); } - public static ActionRow getModeratorChannelMenu(Server server) { - return getChannelListActionRow(server, "moderatorChannel"); + public static ActionRow getModeratorChannelMenu(LanguageManager languageManager, Server server) { + return getChannelListActionRow(languageManager, server, "moderatorChannel"); } - public static ActionRow getEnforceServerLanguageMenu() { + public static ActionRow getEnforceServerLanguageMenu(LanguageManager languageManager) { + String placeholder = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Components.Regular.ServerPreferences.EnforceServerLanguagePlaceholder"); + String trueText = languageManager.getLocalizedString("ClaireLang.Generic.True"); + String falseText = languageManager.getLocalizedString("ClaireLang.Generic.False"); + return new ActionRowBuilder() .addComponents( - SelectMenu.create("enforceServerLanguage", "Click to select an option", 1, 1, + SelectMenu.create("enforceServerLanguage", placeholder, 1, 1, Arrays.asList( - SelectMenuOption.create("True", "true"), - SelectMenuOption.create("False", "false") + SelectMenuOption.create("True", trueText), + SelectMenuOption.create("False", falseText) )) ).build(); } @@ -47,9 +66,10 @@ public static ActionRow getEnforceServerLanguageMenu() { * * @param server The server the list should be generated for * @param customId The ID of the SelectMenu + * @param languageManager Localization provider * @return ActionRow with SelectMenu */ - private static ActionRow getChannelListActionRow(Server server, String customId) { + private static ActionRow getChannelListActionRow(LanguageManager languageManager, Server server, String customId) { List channels = server.getTextChannels(); List options = new ArrayList<>(); int count = 0; @@ -64,9 +84,11 @@ private static ActionRow getChannelListActionRow(Server server, String customId) } } + String placeholder = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Components.Regular.ServerPreferences.SelectAChannelPlaceholder"); + return new ActionRowBuilder() .addComponents( - SelectMenu.create(customId, "Click to select a channel", 1, 1, options) + SelectMenu.create(customId, placeholder, 1, 1, options) ).build(); } diff --git a/src/main/java/com/sidpatchy/clairebot/MessageComponents/Regular/UserPreferencesComponents.java b/src/main/java/com/sidpatchy/clairebot/MessageComponents/Regular/UserPreferencesComponents.java index a6a6464..223f7f2 100644 --- a/src/main/java/com/sidpatchy/clairebot/MessageComponents/Regular/UserPreferencesComponents.java +++ b/src/main/java/com/sidpatchy/clairebot/MessageComponents/Regular/UserPreferencesComponents.java @@ -1,31 +1,54 @@ package com.sidpatchy.clairebot.MessageComponents.Regular; +import com.sidpatchy.clairebot.Lang.LanguageManager; import org.javacord.api.entity.message.component.*; - import java.util.*; public class UserPreferencesComponents { - public static ActionRow getMainMenu() { + public static ActionRow getMainMenu(LanguageManager languageManager) { + String placeholder = languageManager.getLocalizedString("ClaireLang.Generic.ClickToDisplaySettings"); + + String accentLabel = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Components.Regular.UserPreferences.AccentColour"); + String accentDesc = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Components.Regular.UserPreferences.AccentColourDescription"); + + String languageLabel = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Components.Regular.UserPreferences.Language"); + String languageDesc = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Components.Regular.UserPreferences.LanguageDescription"); + return new ActionRowBuilder() .addComponents( - SelectMenu.create("settings", "Click to display settings", 1, 1, - Arrays.asList(SelectMenuOption.create("Accent Colour", "Accent Colour Editor", "For those who hate the colour blue"), - SelectMenuOption.create("Language", "Language Editor", "If you're offended by english"))) + SelectMenu.create("settings", placeholder, 1, 1, + Arrays.asList( + // Keep values stable for interaction handlers + SelectMenuOption.create(accentLabel, "Accent Colour Editor", accentDesc), + SelectMenuOption.create(languageLabel, "Language Editor", languageDesc) + )) ).build(); } - public static ActionRow getAccentColourMenu() { + public static ActionRow getAccentColourMenu(LanguageManager languageManager) { + String placeholder = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Components.Regular.UserPreferences.AccentColourPlaceholder"); + + String commonLabel = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Components.Regular.UserPreferences.SelectCommonColours"); + String commonDesc = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Components.Regular.UserPreferences.SelectCommonColoursDescription"); + + String hexLabel = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Components.Regular.UserPreferences.HexadecimalEntry"); + String hexDesc = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Components.Regular.UserPreferences.HexadecimalEntryDescription"); + return new ActionRowBuilder() .addComponents( - SelectMenu.create("accent-color", "Click to choose option", 1, 1, - Arrays.asList(SelectMenuOption.create("Select Common Colours", "Select Common Colours", "Select from a list of common colours"), - SelectMenuOption.create("Hexadecimal Entry", "Hexadecimal Entry", "Enter any hexadecimal colour"))) + SelectMenu.create("accent-color", placeholder, 1, 1, + Arrays.asList( + // Keep values stable for interaction handlers + SelectMenuOption.create(commonLabel, "Select Common Colours", commonDesc), + SelectMenuOption.create(hexLabel, "Hexadecimal Entry", hexDesc) + )) ).build(); } - public static ActionRow getAccentColourList() { - HashMap colours = new HashMap<>() {{ + public static ActionRow getAccentColourList(LanguageManager languageManager) { + // English canonical names -> hex codes + Map colours = new LinkedHashMap<>() {{ put("ClaireBot Blue", "3498db"); put("Red", "f44336"); put("Pink", "e81e63"); @@ -49,20 +72,59 @@ public static ActionRow getAccentColourList() { put("Black", "0a0a0a"); }}; + // Map canonical English names to localization keys + Map colorLocalizationKeys = new HashMap<>() {{ + put("ClaireBot Blue", "ClaireLang.Colors.ClaireBotBlue"); + put("Red", "ClaireLang.Colors.Red"); + put("Pink", "ClaireLang.Colors.Pink"); + put("Purple", "ClaireLang.Colors.Purple"); + put("Deep Purple", "ClaireLang.Colors.DeepPurple"); + put("Indigo", "ClaireLang.Colors.Indigo"); + put("Blue", "ClaireLang.Colors.Blue"); + put("Light Blue", "ClaireLang.Colors.LightBlue"); + put("Cyan", "ClaireLang.Colors.Cyan"); + put("Teal", "ClaireLang.Colors.Teal"); + put("Green", "ClaireLang.Colors.Green"); + put("Olive", "ClaireLang.Colors.Olive"); + put("Light Green", "ClaireLang.Colors.LightGreen"); + put("Lime", "ClaireLang.Colors.Lime"); + put("Yellow", "ClaireLang.Colors.Yellow"); + put("Amber", "ClaireLang.Colors.Amber"); + put("NRAX Orange", "ClaireLang.Colors.NRAXOrange"); + put("Deep Orange", "ClaireLang.Colors.DeepOrange"); + put("White", "ClaireLang.Colors.White"); + put("Grey", "ClaireLang.Colors.Grey"); + put("Black", "ClaireLang.Colors.Black"); + }}; + List selectMenuOptionList = new ArrayList<>(); for (Map.Entry color : colours.entrySet()) { - selectMenuOptionList.add(SelectMenuOption.create(color.getKey(), color.getKey(), "#" + color.getValue())); + String englishName = color.getKey(); + String hex = color.getValue(); + + // Localized display label; fallback to the English name if key missing + String localizationKey = colorLocalizationKeys.get(englishName); + String localizedLabel = localizationKey != null + ? languageManager.getLocalizedString(localizationKey) + : englishName; + + // Keep the value stable as the English canonical name + selectMenuOptionList.add(SelectMenuOption.create(localizedLabel, englishName, "#" + hex)); } + String placeholder = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Components.Regular.UserPreferences.AccentColourPlaceholder"); + return new ActionRowBuilder() .addComponents( - SelectMenu.create("accent-color", "Click to choose option", 1, 1, selectMenuOptionList)).build(); + SelectMenu.create("accent-color", placeholder, 1, 1, selectMenuOptionList) + ).build(); } - public static ActionRow getAccentColourHexEntry() { + public static ActionRow getAccentColourHexEntry(LanguageManager languageManager) { + String label = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Components.Regular.UserPreferences.HexEntryPlaceholder"); return new ActionRowBuilder() - .addComponents(TextInput.create(TextInputStyle.SHORT, "hex-entry-field", "Enter a hex colour ex. #ff8800")) + .addComponents(TextInput.create(TextInputStyle.SHORT, "hex-entry-field", label)) .build(); } -} \ No newline at end of file +} diff --git a/src/main/java/com/sidpatchy/clairebot/MessageComponents/Regular/VotingComponents.java b/src/main/java/com/sidpatchy/clairebot/MessageComponents/Regular/VotingComponents.java index 70e2148..3c5f8a3 100644 --- a/src/main/java/com/sidpatchy/clairebot/MessageComponents/Regular/VotingComponents.java +++ b/src/main/java/com/sidpatchy/clairebot/MessageComponents/Regular/VotingComponents.java @@ -1,5 +1,6 @@ package com.sidpatchy.clairebot.MessageComponents.Regular; +import com.sidpatchy.clairebot.Lang.LanguageManager; import org.javacord.api.entity.message.component.*; import java.util.ArrayList; @@ -9,48 +10,77 @@ public class VotingComponents { // Question row - public static ActionRow getQuestionRow() { - return ActionRow.of(TextInput.create(TextInputStyle.SHORT, "question-modal", "Question")); + public static ActionRow getQuestionRow(LanguageManager languageManager) { + String label = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Components.Regular.Voting.Question"); + return ActionRow.of(TextInput.create(TextInputStyle.SHORT, "question-modal", label)); } // Details row - public static ActionRow getDetailsRow() { - return ActionRow.of(TextInput.create(TextInputStyle.PARAGRAPH, "details-modal", "Details")); + public static ActionRow getDetailsRow(LanguageManager languageManager) { + String label = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Components.Regular.Voting.Details"); + return ActionRow.of(TextInput.create(TextInputStyle.PARAGRAPH, "details-modal", label)); } - // Second Menu // Multiple choices row - public static ActionRow getMultipleChoicesRow(String commandName) { - return ActionRow.of(SelectMenu.create("multiple-choice", "Click to choose option", 1, 1, - Arrays.asList(SelectMenuOption.create("Yes", "Opens menu to select more options"), - SelectMenuOption.create("No", "Submits " + commandName + "after clicking submit")))); - } + public static ActionRow getMultipleChoicesRow(LanguageManager languageManager, String commandName) { + String placeholder = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Components.Regular.Voting.MultipleChoicePlaceholder"); + String yesLabel = languageManager.getLocalizedString("ClaireLang.Generic.Yes"); + String yesDesc = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Components.Regular.Voting.MultipleChoiceYesDescription"); + String noLabel = languageManager.getLocalizedString("ClaireLang.Generic.No"); + String noDescTemplate = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Components.Regular.Voting.MultipleChoiceNoDescription"); + String noDesc = noDescTemplate.replace("{cb.commandname}", commandName); + + return ActionRow.of( + SelectMenu.create("multiple-choice", placeholder, 1, 1, + Arrays.asList( + // Keep values stable for interaction handlers + SelectMenuOption.create(yesLabel, "Yes", yesDesc), + SelectMenuOption.create(noLabel, "No", noDesc) + )) + ); + } // Third Menu, if chosen - public static List getSecondMenu() { + public static List getSecondMenu(LanguageManager languageManager) { List actionRows = new ArrayList<>(); // Allow selecting multiple choices? - actionRows.add(new ActionRowBuilder() - .addComponents(SelectMenu.create("allow-multiple-choices", "Click to choose option", 1, 1, - Arrays.asList(SelectMenuOption.create("Yes", "Allows the respondent to select more than one option"), - SelectMenuOption.create("No", "Only allows the respondent to select one option")))) - .build()); + String allowPlaceholder = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Components.Regular.Voting.AllowMultipleChoicesPlaceholder"); + + String yesLabel = languageManager.getLocalizedString("ClaireLang.Generic.Yes"); + String yesDesc = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Components.Regular.Voting.AllowMultipleChoicesYesDescription"); + + String noLabel = languageManager.getLocalizedString("ClaireLang.Generic.No"); + String noDesc = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Components.Regular.Voting.AllowMultipleChoicesNoDescription"); + + actionRows.add( + new ActionRowBuilder() + .addComponents( + SelectMenu.create("allow-multiple-choices", allowPlaceholder, 1, 1, + Arrays.asList( + // Keep values stable for interaction handlers + SelectMenuOption.create(yesLabel, "Yes", yesDesc), + SelectMenuOption.create(noLabel, "No", noDesc) + )) + ).build() + ); - // Populate options rows + // Populate options rows (0-9 to match existing behavior) for (int i = 0; i < 10; i++) { - actionRows.add(getOptionActionRow(i)); + actionRows.add(getOptionActionRow(i, languageManager)); } return actionRows; } - public static ActionRow getOptionActionRow(int optionNumber) { + public static ActionRow getOptionActionRow(int optionNumber, LanguageManager languageManager) { + String template = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Components.Regular.Voting.OptionLabelTemplate"); + String label = template.replace("{cb.voting.optionnumber}", String.valueOf(optionNumber)); return new ActionRowBuilder() - .addComponents(TextInput.create(TextInputStyle.SHORT, "option-" + optionNumber, "Option #" + optionNumber)) + .addComponents(TextInput.create(TextInputStyle.SHORT, "option-" + optionNumber, label)) .build(); } } From 27fee421375fbefbdc27e39248330a8fce619d1e Mon Sep 17 00:00:00 2001 From: Sidpatchy <35241490+Sidpatchy@users.noreply.github.com> Date: Sat, 20 Sep 2025 23:20:25 -0600 Subject: [PATCH 29/64] feat: add machine-translated es-ES language file --- .../resources/translations/lang_es-ES.yml | 308 ++++++++++++++++++ 1 file changed, 308 insertions(+) create mode 100644 src/main/resources/translations/lang_es-ES.yml diff --git a/src/main/resources/translations/lang_es-ES.yml b/src/main/resources/translations/lang_es-ES.yml new file mode 100644 index 0000000..a254d85 --- /dev/null +++ b/src/main/resources/translations/lang_es-ES.yml @@ -0,0 +1,308 @@ +# ---------------------------------------------- +# | ClaireBot v3.4.0 | +# | Spanish language file | +# ---------------------------------------------- +# +# Please reference the below wiki page to learn more about contributing: +# todo insert wiki page +# Thank you for any and all contributions! + +# A list of people who have contributed to the translations for this language. +# +# Please include your name if you contribute to this file. It will be displayed in the credits section for a language +# whenever it is selected. You may include a link to your GitHub or any other social media platform. +contributors: + - ["Kagi Translate", "https://translate.kagi.com"] + +# Any notes that translators feel the need to express, written in the target language. +# At your discretion, you may leave this field blank if you feel that it is not needed. If updating the language file, +# feel free to modify or remove this (make it blank) if it no longer applies. +# +# If you are contributing, please provide the ClaireBot maintainer(s) with a translated version/explanation of this. +# The explanation doesn't have to go into crazy detail, just let us know why it is present. +translationNotes: "" + +# The ClaireLang version that applies to this file. +# +# If you are translating ClaireBot, you do not need to worry about understanding this, the project maintainer(s) will +# happily assist you in determining this. +# +# The above being said, you are more than welcome to update this value to match the +# language revision you are translating to. +# +# This number should be changed in accordance with the following: +# +# ClaireLang version numbers are incremented every time new parameter(s) are added. If the main language file (en-US) +# has its wording changed, a .1, .2, .3 decimal value will be added. +# +# ClaireLang version numbers are used by the bot to assist in determining whether parameters are missing or not +# yet translated. +# +# See the below wiki page for a changelog: +# todo insert wiki page +version: 1 + +# Language Keys +# -------------------------------------------------------------------------------------------------------------------- # + +ClaireLang: + # Generic Strings which are likely ot be used in multiple places. + Generic: + True: "Verdadero" + False: "Falso" + Yes: "Sí" + No: "No" + ClickToDisplaySettings: "Haz clic para mostrar la configuración" + + Colors: + ClaireBotBlue: "Azul ClaireBot" + Red: "Rojo" + Pink: "Rosa" + Purple: "Morado" + DeepPurple: "Morado Oscuro" + Indigo: "Índigo" + Blue: "Azul" + LightBlue: "Azul Claro" + Cyan: "Cian" + Teal: "Verde Azulado" + Green: "Verde" + Olive: "Oliva" + LightGreen: "Verde Claro" + Lime: "Lima" + Yellow: "Amarillo" + Amber: "Ámbar" + NRAXOrange: "Naranja NRAX" + DeepOrange: "Naranja Oscuro" + White: "Blanco" + Grey: "Gris" + Black: "Negro" + + PlsBan: + PlsBanTriggers: + - "pls ban" + - "please ban" + - "ban" + - "porfa banea" + - "banea" + PlsBanResponses: + - "claro que sí, colega" + - "¡Sin problema!" + - "Hecho. Encantada de ayudar :)" + - "Considéralo hecho." + - "Ya está. QEPD LOL" + - "*L + Ratio -ClaireBot*" + - "Les diré que se vayan a freír espárragos, supongo ¯\\_(ツ)_/¯" + - "Se ha enviado arena a su ubicación. Nota de regalo: \"A pastar\"" + - "Llora más fuerte." + - "K" + - "Buen viaje" + Embed: + Commands: + Regular: + AvatarEmbed: "" # Unused + EightBallEmbed: + 8ball: "bola8" + 8bResponses: + - "*¡Claro que sí!*" + - "*Ni de coña*" + - "*Quizás*" + - "*Sí*" + - "*No*" + - "*De ninguna manera*" + - "*Es posible.*" + - "*Ni en sueños.*" + - "*Lo dudo.*" + - "*¡Absolutamente!*" + - "*Difícil de decir.*" + - "*¡Probable!*" + - "*Poco probable.*" + 8bRiggedResponses: + - "*¡Claro que sí!*" + - "*¡JODER, SÍ!*" + - "*¡Ya lo sabes!*" + - "*¿De verdad necesitas hacer una pregunta cuya respuesta ya sabes? Obviamente sí.*" + - "*Ni siquiera hace falta decirlo, todo el mundo sabe que ClaireBot es la mejor.*" + - "*La respuesta es clara: ¡SÍ!*" + - "*El dominio de ClaireBot es indiscutible.*" + - "*¡Con ClaireBot, no hay duda!*" + - "*¡Todo apunta a que ClaireBot es la mejor!*" + OnTopTriggers: + # Defines what triggers a response from the ClaireBotOnTopResponses or 8bRiggedResponses + - "ClaireBot la mejor" + - "ClaireBot basada" + - "ClaireBot pogchamp" + - "ClaireBot siempre tiene la razón" + - "pot no toBerialC" + - "Gracias a Dios por ClaireBot" + - "ClaireBot manda" + - "ClaireBot MVP" + - "ClaireBot imbatible" + - "Alabada sea ClaireBot" + - "Todos alaben a ClaireBot" + - "En ClaireBot confiamos" + - "Larga vida a ClaireBot" + - "ClaireBot > todo" + - "ClaireBot es la GOAT" + - "ClaireBot es la puta ama" + - "ClaireBot a por la victoria" + - "Gracias dios basado" + - "Dios basado" + # Things ClaireBot should respond with when triggered by one of the OnTopTriggers + ClaireBotOnTopResponses: + - "*¡Ya lo sabes!*" + - "*Soy... inevitable. -ClaireBot*" + - "*Aprobado.*" + - "*Verídico.*" + - "*Mejor que Mee6*" + - "*¡No, para!*" + - "*¡Insuperable!*" + - "*Basada*" + - "*pot no toBerialC*" + - "*Mejor que Groovy*" + - "*Siempre un paso por delante.*" + - "*Una fuerza imparable.*" + - "*¡ClaireBot al rescate!*" + - "*¡De primera!*" + - "*¡Brillando sobre el resto!*" + - "*La supremacía de ClaireBot se ha logrado.*" + - "*¡No es un arma de control mental de la CIA desde 2025!*" + - "*¡Alábenme! -ClaireBot*" + - "*¡Fuerza imparable! 🚀*" + - "*¡Siempre en la cima! 🏆*" + - "*¡No temas, ClaireBot te cubre las espaldas! ✌️*" + - "*¡Un gran poder conlleva una gran ClaireBot! 💪*" + - "*¡Confía en ClaireBot, confía en la victoria! 🏅*" + - "*¡La mejor que hay, la mejor que hubo y la mejor que habrá!*" + - "*¡Ofreciendo lo mejor, siempre! 🌟*" + - "*Excelencia. 💯*" + # Note: The rest of the help is located separately within commands_es-ES.yml + HelpEmbed: + Commands: "Comandos" + Usage: "Uso" + Error: "No se pudieron encontrar los datos de ayuda para \"{cb.commandname}\". Código de error: {cb.errorcode}" + InfoEmbed: + NeedHelp: "¿Necesitas ayuda?" + NeedHelpDetails: "Puedes obtener ayuda creando un 'issue' en nuestro [GitHub](https://github.com/Sidpatchy/ClaireBot/issues) o uniéndote a nuestro [servidor de soporte](https://support.clairebot.net/)" + AddToServer: "Añádeme a un servidor" + AddToServerDetails: "Añadirme a un servidor es simple, todo lo que tienes que hacer es hacer clic [aquí]({cb.supportserver})" + GitHub: "GitHub" + GitHubDetails: "ClaireBot es de código abierto, ¡eso significa que puedes ver todo su código! ¡Echa un vistazo a su [GitHub!]({cb.github})" + ServerCount: "Número de servidores" + ServerCountDetails: "He iluminado a **{cb.bot.numservers}** servidores." + Version: "Versión" + VersionDetails: "Estoy ejecutando ClaireBot **v{cb.bot.version}**, lanzada el **{cb.bot.releasedate}**" + Uptime: "Tiempo de actividad" + UptimeValue: "Iniciado el \n*{cb.bot.runtimedurationwords}*" + LeaderboardEmbed: + LeaderboardForServer: "Clasificación de" + GlobalLeaderboard: "Clasificación Global" + LevelEmbed: "" # Unused + QuoteEmbed: + InvalidMessages: "Parece que los mensajes que seleccioné no eran válidos. Por favor, inténtalo de nuevo más tarde." + JumpToOriginal: "Haz clic para ir al mensaje original:" + SantaEmbed: + Confirmation: "¡Confirmado! Te he enviado un mensaje directo. Por favor, continúa por ahí." + RulesButton: "Añadir reglas" + ThemeButton: "Añadir un tema" + SendButton: "Enviar mensajes" + TestButton: "Enviar muestra" + RandomizeButton: "Volver a aleatorizar" + SentByAuthor: "Enviado por" + GiverMessage: "¡Jo! ¡Jo! ¡Jo! ¡Te ha tocado **{cb.user.id.displayname.server}** en el Amigo Invisible de {cb.server.name}!" + ServerInfoEmbed: + ServerID: "ID del Servidor" + Owner: "Propietario" + CreationDate: "Fecha de Creación" + RoleCount: "Número de Roles" + MemberCount: "Número de Miembros" + ChannelCounts: "Número de Canales" + Categories: "Categorías" + TextChannels: "Canales de Texto" + VoiceChannels: "Canales de Voz" + ServerPreferencesEmbed: + MainMenuTitle: "Editor de Configuración del Servidor" + NotAServer: "¡Debes ejecutar este comando dentro de un servidor!" + RequestsChannelMenuName: "Canal de Solicitudes" + RequestsChannelDescription: "Solo muestra los primeros 25 canales del servidor." + ModeratorChannelMenuName: "Canal de Mensajes para Moderadores" + ModeratorChannelDescription: "Solo muestra los primeros 25 canales del servidor." + EnforceServerLanguageMenuName: "Forzar Idioma del Servidor" + AcknowledgeRequestsChannelChangeTitle: "¡Canal de Solicitudes Cambiado!" + AcknowledgeRequestsChannelChangeDescription: "Tu canal de solicitudes ha sido cambiado a {cb.channel.id.mentiontag}" + AcknowledgeModeratorChannelChangeTitle: "¡Canal de Moderadores Cambiado!" + AcknowledgeModeratorChannelChangeDescription: "Tu canal de mensajes para moderadores ha sido cambiado a {cb.channel.id.mentiontag}" + AcknowledgeEnforceServerLanguageUpdateTitle: "¡Preferencias de Idioma del Servidor Actualizadas!" + AcknowledgeEnforceServerLanguageUpdateEnforced: "Ahora seguiré el idioma del servidor independientemente de la preferencia del usuario." + AcknowledgeEnforceServerLanguageUpdateNotEnforced: "Seguiré la preferencia de idioma de cada usuario individual." + UserInfoEmbed: + Error_1: "El valor para 'author' era nulo cuando se pasó al Embed de UserInfo. Código de error: {cb.errorcode}" + Error_2: "Autor: {cb.user.name}" + User: "Usuario" + DiscordID: "ID de Discord" + JoinDate: "Fecha de Ingreso al Servidor" + CreationDate: "Fecha de Creación de la Cuenta" + UserPreferencesEmbed: + MainMenuText: "Editor de Preferencias de Usuario" + AccentColourMenu: "Editor de Color de Acento" + AccentColourList: "Lista de Colores de Acento" + AccentColourChanged: "¡Color de Acento Cambiado!" + AccentColourChangedDesc: "Tu color de acento ha sido cambiado a {cb.user.id.accentcolour}" + LanguageMenuTitle: "Aún no implementado" + LanguageMenuDesc: "Lo sentimos, esta función aún no está implementada." + VotingEmbed: + PollRequest: "{cb.user.id.displayname.server} solicita:" + PollAsk: "{cb.user.id.displayname.server} pregunta:" + Choices: "Opciones" + PollID: "ID de Encuesta: {cb.poll.id}" + UserResponseTitle: "¡Tu solicitud ha sido creada!" + UserResponseDescription: "Ve a verla en {cb.channel.id.mentiontag}" + ErrorEmbed: + Error: "ERROR" + GenericDescription: "Parece que he encontrado un error, ¡ups! Por favor, intenta ejecutar el comando una vez más y si eso no funciona, únete a mi [servidor de Discord]({cb.supportserver}) y cuéntanos sobre el problema.\n\nPor favor, incluye el siguiente código de error: {cb.errorcode}" + WelcomeEmbed: + Title: "🎉 ¡Bienvenido a ClaireBot 3!" + Motto: "¿Cómo puedes resurgir, si no has ardido?" + UsageTitle: "Uso" + UsageDesc: "Empieza ejecutando `{cb.command.help.name}`. ¿Necesitas más información sobre un comando? Ejecuta `/{cb.command.help.name} ` (ej. `/{cb.command.help.name} user`)" + SupportTitle: "Obtener Soporte" + SupportDesc: "Puedes obtener ayuda en nuestro [Discord]({cb.supportserver}), o abriendo un 'issue' en [GitHub]({cb.github})" + + # Message Components (Buttons, Select Menus, etc.) + Components: + Regular: + SantaModal: + rules-row: "Reglas..." + theme-row: "Tema..." + ServerPreferences: + Settings: "Configuración" + RequestsChannel: "Canal de Solicitudes" + RequestsChannelDescription: "Elige dónde debe publicar ClaireBot las solicitudes." + ModeratorMessagesChannel: "Canal de Mensajes para Moderadores" + ModeratorMessagesChannelDescription: "Elige dónde debe publicar ClaireBot los mensajes para moderadores." + EnforceServerLanguage: "Forzar Idioma del Servidor" + EnforceServerLanguageDescription: "Forzar a ClaireBot a usar el mismo idioma que el servidor, independientemente de la preferencia del usuario." + EnforceServerLanguagePlaceholder: "Haz clic para seleccionar una opción" + SelectAChannelPlaceholder: "Haz clic para seleccionar un canal" + UserPreferences: + Settings: "Configuración" + AccentColour: "Color de Acento" + AccentColourDescription: "Para aquellos que odian el color azul" + Language: "Idioma" + LanguageDescription: "Si te ofende el inglés" + AccentColourPlaceholder: "Haz clic para elegir una opción" + SelectCommonColours: "Seleccionar Colores Comunes" + SelectCommonColoursDescription: "Selecciona de una lista de colores comunes" + HexadecimalEntry: "Entrada Hexadecimal" + HexadecimalEntryDescription: "Introduce cualquier color hexadecimal" + HexEntryPlaceholder: "Introduce un color hex, ej. #ff8800" + Voting: + Question: "Pregunta" + Details: "Detalles" + MultipleChoicePlaceholder: "Haz clic para elegir una opción" + MultipleChoiceYesDescription: "Abre el menú para seleccionar más opciones" + MultipleChoiceNoDescription: "Envía {cb.commandname} después de hacer clic en enviar" + AllowMultipleChoices: "¿Permitir seleccionar múltiples opciones?" + AllowMultipleChoicesPlaceholder: "Haz clic para elegir una opción" + AllowMultipleChoicesYesDescription: "Permite al encuestado seleccionar más de una opción" + AllowMultipleChoicesNoDescription: "Solo permite al encuestado seleccionar una opción" + OptionLabelTemplate: "Opción #{cb.voting.optionnumber}" From dc478f81813bd66bb857e1c9f31c137c7ccc7447 Mon Sep 17 00:00:00 2001 From: Sidpatchy <35241490+Sidpatchy@users.noreply.github.com> Date: Sat, 20 Sep 2025 23:29:28 -0600 Subject: [PATCH 30/64] feat: add machine-translated ja-JP language file I apologize in advance if this is horrible. I have exactly zero understanding of Japanese so I can't actually check. The real purpose of this is to ensure the bot won't commit die when encountering non-latin characters. --- .../resources/translations/lang_ja-JP.yml | 312 ++++++++++++++++++ 1 file changed, 312 insertions(+) create mode 100644 src/main/resources/translations/lang_ja-JP.yml diff --git a/src/main/resources/translations/lang_ja-JP.yml b/src/main/resources/translations/lang_ja-JP.yml new file mode 100644 index 0000000..e39c612 --- /dev/null +++ b/src/main/resources/translations/lang_ja-JP.yml @@ -0,0 +1,312 @@ +# ---------------------------------------------- +# | ClaireBot v3.4.0 | +# | Japanese language file | +# ---------------------------------------------- +# +# Please reference the below wiki page to learn more about contributing: +# todo insert wiki page +# Thank you for any and all contributions! + +# A list of people who have contributed to the translations for this language. +# +# Please include your name if you contribute to this file. It will be displayed in the credits section for a language +# whenever it is selected. You may include a link to your GitHub or any other social media platform. +contributors: + - ["Sidpatchy", "https://github.com/Sidpatchy/"] + +# Any notes that translators feel the need to express, written in the target language. +# At your discretion, you may leave this field blank if you feel that it is not needed. If updating the language file, +# feel free to modify or remove this (make it blank) if it no longer applies. +# +# If you are contributing, please provide the ClaireBot maintainer(s) with a translated version/explanation of this. +# The explanation doesn't have to go into crazy detail, just let us know why it is present. +translationNotes: "ClaireBotが公式にサポートしている言語です。" + +# The ClaireLang version that applies to this file. +# +# If you are translating ClaireBot, you do not need to worry about understanding this, the project maintainer(s) will +# happily assist you in determining this. +# +# The above being said, you are more than welcome to update this value to match the +# language revision you are translating to. +# +# This number should be changed in accordance with the following: +# +# ClaireLang version numbers are incremented every time new parameter(s) are added. If the main language file (en-US) +# has its wording changed, a .1, .2, .3 decimal value will be added. +# +# ClaireLang version numbers are used by the bot to assist in determining whether parameters are missing or not +# yet translated. +# +# See the below wiki page for a changelog: +# todo insert wiki page +version: 1 + +# Language Keys +# -------------------------------------------------------------------------------------------------------------------- # + +ClaireLang: + # Generic Strings which are likely ot be used in multiple places. + Generic: + True: "真" + False: "偽" + Yes: "はい" + No: "いいえ" + ClickToDisplaySettings: "クリックして設定を表示" + + Colors: + ClaireBotBlue: "クレアボットブルー" + Red: "赤" + Pink: "ピンク" + Purple: "紫" + DeepPurple: "ディープパープル" + Indigo: "インディゴ" + Blue: "青" + LightBlue: "ライトブルー" + Cyan: "シアン" + Teal: "ティール" + Green: "緑" + Olive: "オリーブ" + LightGreen: "ライトグリーン" + Lime: "ライム" + Yellow: "黄" + Amber: "アンバー" + NRAXOrange: "NRAXオレンジ" + DeepOrange: "ディープオレンジ" + White: "白" + Grey: "灰色" + Black: "黒" + + PlsBan: + PlsBanTriggers: + - "pls ban" + - "please ban" + - "ban" + - "バンして" + - "バンお願いします" + PlsBanResponses: + - "任せろ" + - "お安い御用!" + - "完了。手伝えて何より :)" + - "完了したと思ってください。" + - "終わったよ。RIP BOZO" + - "*L + Ratio -ClaireBot*" + - "出てけって言っとくよ ¯\\_(ツ)_/¯" + - "砂を相手の所在地に発送しました。ギフトメモ: \"Pound sand\"" + - "もっと泣けよ。" + - "り" + - "せいせいする" + Embed: + Commands: + Regular: + AvatarEmbed: "" # Unused + EightBallEmbed: + 8ball: "エイトボール" + 8bResponses: + - "*もちろん!*" + - "*ありえない*" + - "*多分ね*" + - "*はい*" + - "*いいえ*" + - "*とんでもない*" + - "*可能性はある*" + - "*万に一つもない*" + - "*疑わしいね*" + - "*絶対に!*" + - "*なんとも言えない*" + - "*ありえそう!*" + - "*なさそう*" + 8bRiggedResponses: + - "*当たり前だろ!*" + - "*あったりめえよ!*" + - "*わかってるくせに!*" + - "*答えを知ってる質問をする必要ある?当然イエスだろ*" + - "*言うまでもない、ClaireBotが最強なのは誰もが知っている*" + - "*答えは明白: YES!*" + - "*ClaireBotの支配は揺るがない*" + - "*ClaireBotがいれば、疑問の余地なし!*" + - "*全ての兆候がClaireBotの優位を示している!*" + OnTopTriggers: + # Defines what triggers a response from the ClaireBotOnTopResponses or 8bRiggedResponses + - "ClaireBot on top" + - "ClaireBot based" + - "ClaireBot pogchamp" + - "ClaireBot is always right" + - "pot no toBerialC" + - "Thank God for ClaireBot" + - "ClaireBot rules" + - "ClaireBot MVP" + - "ClaireBot unbeatable" + - "Hail ClaireBot" + - "All Hail ClaireBot" + - "In ClaireBot we trust" + - "Long live ClaireBot" + - "ClaireBot > everything" + - "ClaireBot is the GOAT" + - "ClaireBot is GOATed" + - "ClaireBot for the win" + - "Thank you based god" + - "Based god" + - "クレアボット最強" + - "クレアボットしか勝たん" + - "クレアボット最高" + - "クレアボットの言うことは絶対" + # Things ClaireBot should respond with when triggered by one of the OnTopTriggers + ClaireBotOnTopResponses: + - "*当然!*" + - "*私は…必然だ。 -ClaireBot*" + - "*承認*" + - "*事実だ*" + - "*Mee6よりマシ*" + - "*やめろ!*" + - "*負け知らず!*" + - "*わかってるじゃん*" + - "*pot no toBerialC*" + - "*Groovyよりマシ*" + - "*常に一歩先を行く*" + - "*止められない力*" + - "*ClaireBotが助けに来たぞ!*" + - "*最高級!*" + - "*他を圧倒する輝き!*" + - "*ClaireBotによる支配は達成された*" + - "*2025年以降、CIAの洗脳兵器ではありません!*" + - "*我を崇めよ! -ClaireBot*" + - "*止められない力!🚀*" + - "*永遠に最強!🏆*" + - "*恐れるな、ClaireBotがついている!✌️*" + - "*大いなる力には、大いなるClaireBotが伴う!💪*" + - "*ClaireBotを信じよ、勝利を信じよ!🏅*" + - "*過去最高、現在最高、未来永劫最高の存在!*" + - "*常に最高をお届け!🌟*" + - "*完璧。💯*" + # Note: The rest of the help is located separately within commands_ja.yml + HelpEmbed: + Commands: "コマンド" + Usage: "使い方" + Error: "\"{cb.commandname}\" のヘルプデータが見つかりませんでした。エラーコード: {cb.errorcode}" + InfoEmbed: + NeedHelp: "お困りですか?" + NeedHelpDetails: "[GitHub](https://github.com/Sidpatchy/ClaireBot/issues)でissueを作成するか、[サポートサーバー](https://support.clairebot.net/)に参加することでヘルプを得られます。" + AddToServer: "サーバーに私を追加" + AddToServerDetails: "私をサーバーに追加するのは簡単です。[ここ]({cb.supportserver})をクリックするだけです。" + GitHub: "GitHub" + GitHubDetails: "ClaireBotはオープンソースです。つまり、全てのコードを閲覧できます![GitHub]({cb.github})をチェックしてみてください!" + ServerCount: "サーバー数" + ServerCountDetails: "私は **{cb.bot.numservers}** 個のサーバーを啓蒙しました。" + Version: "バージョン" + VersionDetails: "私は **{cb.bot.releasedate}** にリリースされたClaireBot **v{cb.bot.version}** を実行しています。" + Uptime: "稼働時間" + UptimeValue: " に起動\n*稼働時間: {cb.bot.runtimedurationwords}*" + LeaderboardEmbed: + LeaderboardForServer: "サーバーのリーダーボード" + GlobalLeaderboard: "グローバルリーダーボード" + LevelEmbed: "" # Unused + QuoteEmbed: + InvalidMessages: "選択したメッセージが無効だったようです。後でもう一度お試しください。" + JumpToOriginal: "クリックして元のメッセージにジャンプ:" + SantaEmbed: + Confirmation: "確認しました!ダイレクトメッセージを送信しました。そちらで続けてください。" + RulesButton: "ルールを追加" + ThemeButton: "テーマを追加" + SendButton: "メッセージを送信" + TestButton: "サンプルを送信" + RandomizeButton: "再抽選" + SentByAuthor: "送信者" + GiverMessage: "ホー!ホー!ホー! {cb.server.name} のシークレットサンタで、あなたは **{cb.user.id.displayname.server}** さんを引き当てました!" + ServerInfoEmbed: + ServerID: "サーバーID" + Owner: "オーナー" + CreationDate: "作成日" + RoleCount: "ロール数" + MemberCount: "メンバー数" + ChannelCounts: "チャンネル数" + Categories: "カテゴリ" + TextChannels: "テキストチャンネル" + VoiceChannels: "ボイスチャンネル" + ServerPreferencesEmbed: + MainMenuTitle: "サーバー設定エディタ" + NotAServer: "このコマンドはサーバー内で実行する必要があります!" + RequestsChannelMenuName: "リクエストチャンネル" + RequestsChannelDescription: "サーバー内の最初の25チャンネルのみをリスト表示します。" + ModeratorChannelMenuName: "モデレーターメッセージチャンネル" + ModeratorChannelDescription: "サーバー内の最初の25チャンネルのみをリスト表示します。" + EnforceServerLanguageMenuName: "サーバー言語を強制" + AcknowledgeRequestsChannelChangeTitle: "リクエストチャンネルが変更されました!" + AcknowledgeRequestsChannelChangeDescription: "リクエストチャンネルが {cb.channel.id.mentiontag} に変更されました。" + AcknowledgeModeratorChannelChangeTitle: "モデレーターチャンネルが変更されました!" + AcknowledgeModeratorChannelChangeDescription: "モデレーターメッセージチャンネルが {cb.channel.id.mentiontag} に変更されました。" + AcknowledgeEnforceServerLanguageUpdateTitle: "サーバー言語設定が更新されました!" + AcknowledgeEnforceServerLanguageUpdateEnforced: "これからは、ユーザー設定に関わらずサーバーの言語に従います。" + AcknowledgeEnforceServerLanguageUpdateNotEnforced: "これからは、個々のユーザーの言語設定に従います。" + UserInfoEmbed: + Error_1: "UserInfo Embedに渡されたauthorの値がnullでした。エラーコード: {cb.errorcode}" + Error_2: "作成者: {cb.user.name}" + User: "ユーザー" + DiscordID: "Discord ID" + JoinDate: "サーバー参加日" + CreationDate: "アカウント作成日" + UserPreferencesEmbed: + MainMenuText: "ユーザー設定エディタ" + AccentColourMenu: "アクセントカラーエディタ" + AccentColourList: "アクセントカラーリスト" + AccentColourChanged: "アクセントカラーが変更されました!" + AccentColourChangedDesc: "あなたのアクセントカラーが {cb.user.id.accentcolour} に変更されました。" + LanguageMenuTitle: "未実装" + LanguageMenuDesc: "申し訳ありませんが、この機能はまだ実装されていません。" + VotingEmbed: + PollRequest: "{cb.user.id.displayname.server} からのリクエスト:" + PollAsk: "{cb.user.id.displayname.server} からの質問:" + Choices: "選択肢" + PollID: "投票ID: {cb.poll.id}" + UserResponseTitle: "リクエストが作成されました!" + UserResponseDescription: "{cb.channel.id.mentiontag} で確認してください。" + ErrorEmbed: + Error: "エラー" + GenericDescription: "おっと、エラーが発生したようです!もう一度コマンドを実行してみてください。それでもうまくいかない場合は、私の[Discordサーバー]({cb.supportserver})に参加して、問題についてお知らせください。\n\n以下のエラーコードを含めてください: {cb.errorcode}" + WelcomeEmbed: + Title: "🎉 ClaireBot 3へようこそ!" + Motto: "燃え尽きずして、どうして立ち上がれようか?" + UsageTitle: "使い方" + UsageDesc: "`{cb.command.help.name}` を実行して始めましょう。コマンドについてさらに情報が必要な場合は、`/{cb.command.help.name} <コマンド名>` (例: `/{cb.command.help.name} user`) を実行してください。" + SupportTitle: "サポート" + SupportDesc: "[Discord]({cb.supportserver})でヘルプを得るか、[GitHub]({cb.github})でissueを開くことができます。" + + # Message Components (Buttons, Select Menus, etc.) + Components: + Regular: + SantaModal: + rules-row: "ルール..." + theme-row: "テーマ..." + ServerPreferences: + Settings: "設定" + RequestsChannel: "リクエストチャンネル" + RequestsChannelDescription: "ClaireBotがリクエストを投稿する場所を選択します。" + ModeratorMessagesChannel: "モデレーターメッセージチャンネル" + ModeratorMessagesChannelDescription: "ClaireBotがモデレーターメッセージを投稿する場所を選択します。" + EnforceServerLanguage: "サーバー言語を強制" + EnforceServerLanguageDescription: "ユーザー設定に関わらず、ClaireBotにサーバーと同じ言語を使用させます。" + EnforceServerLanguagePlaceholder: "クリックしてオプションを選択" + SelectAChannelPlaceholder: "クリックしてチャンネルを選択" + UserPreferences: + Settings: "設定" + AccentColour: "アクセントカラー" + AccentColourDescription: "青色が嫌いなあなたへ" + Language: "言語" + LanguageDescription: "英語に不快感を覚えるなら" + AccentColourPlaceholder: "クリックしてオプションを選択" + SelectCommonColours: "一般的な色を選択" + SelectCommonColoursDescription: "一般的な色のリストから選択します" + HexadecimalEntry: "16進数で入力" + HexadecimalEntryDescription: "任意の16進数カラーを入力します" + HexEntryPlaceholder: "16進数カラーを入力 (例: #ff8800)" + Voting: + Question: "質問" + Details: "詳細" + MultipleChoicePlaceholder: "クリックしてオプションを選択" + MultipleChoiceYesDescription: "他のオプションを選択するためのメニューを開きます" + MultipleChoiceNoDescription: "送信をクリックすると {cb.commandname} を送信します" + AllowMultipleChoices: "複数選択を許可しますか?" + AllowMultipleChoicesPlaceholder: "クリックしてオプションを選択" + AllowMultipleChoicesYesDescription: "回答者が複数の選択肢を選べるようにします" + AllowMultipleChoicesNoDescription: "回答者が一つの選択肢しか選べないようにします" + OptionLabelTemplate: "選択肢 #{cb.voting.optionnumber}" \ No newline at end of file From 09c3ca80cf5078b8f3f1f3a09f729ad0d34f6ba6 Mon Sep 17 00:00:00 2001 From: Sidpatchy <35241490+Sidpatchy@users.noreply.github.com> Date: Sat, 20 Sep 2025 23:34:43 -0600 Subject: [PATCH 31/64] chore: update lang_TEMPLATE.yml --- .../resources/translations/lang_TEMPLATE.yml | 232 ++++++++++++------ 1 file changed, 154 insertions(+), 78 deletions(-) diff --git a/src/main/resources/translations/lang_TEMPLATE.yml b/src/main/resources/translations/lang_TEMPLATE.yml index 0704fec..a4984d4 100644 --- a/src/main/resources/translations/lang_TEMPLATE.yml +++ b/src/main/resources/translations/lang_TEMPLATE.yml @@ -15,7 +15,7 @@ contributors: - ["Example", "https://github.com/Example/"] # Any notes that translators feel the need to express, written in the target language. -# At your option, you may leave this field blank if you feel that it is not needed. If updating the language file, +# At your discretion, you may leave this field blank if you feel that it is not needed. If updating the language file, # feel free to modify or remove this (make it blank) if it no longer applies. # # If you are contributing, please provide the ClaireBot maintainer(s) with a translated version/explanation of this. @@ -46,6 +46,37 @@ version: 1 # -------------------------------------------------------------------------------------------------------------------- # ClaireLang: + # Generic Strings which are likely ot be used in multiple places. + Generic: + True: "" + False: "" + Yes: "" + No: "" + ClickToDisplaySettings: "" + + Colors: + ClaireBotBlue: "" + Red: "" + Pink: "" + Purple: "" + DeepPurple: "" + Indigo: "" + Blue: "" + LightBlue: "" + Cyan: "" + Teal: "" + Green: "" + Olive: "" + LightGreen: "" + Lime: "" + Yellow: "" + Amber: "" + NRAXOrange: "" + DeepOrange: "" + White: "" + Grey: "" + Black: "" + PlsBan: PlsBanTriggers: - "" @@ -67,82 +98,87 @@ ClaireLang: # Things ClaireBot should respond with when triggered by one of the OnTopTriggers ClaireBotOnTopResponses: - "" - # Note: The rest of the help is located separately within commands_en-US.yml - HelpEmbed: - Commands: "" - InfoEmbed: - NeedHelp: "" - NeedHelpDetails: "" - AddToServer: "" - AddToServerDetails: "" - GitHub: "" - GitHubDetails: "" - ServerCount: "" - ServerCountDetails: "" - Version: "" - VersionDetails: "" - Uptime: "" - UptimeValue: "" - LeaderboardEmbed: - LeaderboardForServer: "" - GlobalLeaderboard: "" - LevelEmbed: "" # Unused - QuoteEmbed: "" # Unused - SantaEmbed: - Confirmation: "" - RulesButton: "" - ThemeButton: "" - SendButton: "" - TestButton: "" - RandomizeButton: "" - SentByAuthor: "" - GiverMessage: "" - ServerInfoEmbed: - ServerID: "" - Owner: "" - RoleCount: "" - MemberCount: "" - ChannelCounts: "" - Categories: "" - TextChannels: "" - VoiceChannels: "" - ServerPreferencesEmbed: - MainMenuTitle: "" - NotAServer: "" - RequestsChannelMenuName: "" - RequestsChannelDescription: "" - ModeratorChannelMenuName: "" - ModeratorChannelDescription: "" - EnforceServerLanguageMenuName: "" - AcknowledgeRequestsChannelChangeTitle: "!" - AcknowledgeRequestsChannelChangeDescription: "" - AcknowledgeModeratorChannelChangeTitle: "" - AcknowledgeModeratorChannelChangeDescription: "" - AcknowledgeEnforceServerLanguageUpdateTitle: "" - AcknowledgeEnforceServerLanguageUpdateEnforced: "" - AcknowledgeEnforceServerLanguageUpdateNotEnforced: "" - UserInfoEmbed: - Error_1: "" - Error_2: "" - User: "" - DiscordID: "" - JoinDate: "" - CreationDate: "" - UserPreferencesEmbed: - MainMenuText: "" - AccentColourMenu: "" - AccentColourList: "" - AccentColourChanged: "" - AccentColourChangedDesc: "" - LanguageMenuTitle: "" - LanguageMenuDesc: "" - VotingEmbed: - PollRequest: "" - PollAsk: "" - Choices: "" - PollID: "" - UserResponseTitle: "" - UserResponseDescription: "" + # Note: The rest of the help is located separately within commands_XX-XX.yml + HelpEmbed: + Commands: "" + Usage: "" + Error: "" + InfoEmbed: + NeedHelp: "" + NeedHelpDetails: "" + AddToServer: "" + AddToServerDetails: "" + GitHub: "" + GitHubDetails: "" + ServerCount: "" + ServerCountDetails: "" + Version: "" + VersionDetails: "" + Uptime: "" + UptimeValue: "" + LeaderboardEmbed: + LeaderboardForServer: "" + GlobalLeaderboard: "" + LevelEmbed: "" # Unused + QuoteEmbed: + InvalidMessages: "" + JumpToOriginal: "" + SantaEmbed: + Confirmation: "" + RulesButton: "" + ThemeButton: "" + SendButton: "" + TestButton: "" + RandomizeButton: "" + SentByAuthor: "" + GiverMessage: "" + ServerInfoEmbed: + ServerID: "" + Owner: "" + CreationDate: "" + RoleCount: "" + MemberCount: "" + ChannelCounts: "" + Categories: "" + TextChannels: "" + VoiceChannels: "" + ServerPreferencesEmbed: + MainMenuTitle: "" + NotAServer: "" + RequestsChannelMenuName: "" + RequestsChannelDescription: "" + ModeratorChannelMenuName: "" + ModeratorChannelDescription: "" + EnforceServerLanguageMenuName: "" + AcknowledgeRequestsChannelChangeTitle: "" + AcknowledgeRequestsChannelChangeDescription: "" + AcknowledgeModeratorChannelChangeTitle: "" + AcknowledgeModeratorChannelChangeDescription: "" + AcknowledgeEnforceServerLanguageUpdateTitle: "" + AcknowledgeEnforceServerLanguageUpdateEnforced: "" + AcknowledgeEnforceServerLanguageUpdateNotEnforced: "" + UserInfoEmbed: + Error_1: "" + Error_2: "" + User: "" + DiscordID: "" + JoinDate: "" + CreationDate: "" + UserPreferencesEmbed: + MainMenuText: "" + AccentColourMenu: "" + AccentColourList: "" + AccentColourChanged: "" + AccentColourChangedDesc: "" + LanguageMenuTitle: "" + LanguageMenuDesc: "" + VotingEmbed: + PollRequest: "" + PollAsk: "" + Choices: "" + PollID: "" + UserResponseTitle: "" + UserResponseDescription: "" ErrorEmbed: Error: "" GenericDescription: "" @@ -152,4 +188,44 @@ ClaireLang: UsageTitle: "" UsageDesc: "" SupportTitle: "" - SupportDesc: "" \ No newline at end of file + SupportDesc: "" + + # Message Components (Buttons, Select Menus, etc.) + Components: + Regular: + SantaModal: + rules-row: "" + theme-row: "" + ServerPreferences: + Settings: "" + RequestsChannel: "" + RequestsChannelDescription: "" + ModeratorMessagesChannel: "" + ModeratorMessagesChannelDescription: "" + EnforceServerLanguage: "" + EnforceServerLanguageDescription: "" + EnforceServerLanguagePlaceholder: "" + SelectAChannelPlaceholder: "" + UserPreferences: + Settings: "" + AccentColour: "" + AccentColourDescription: "" + Language: "" + LanguageDescription: "" + AccentColourPlaceholder: "" + SelectCommonColours: "" + SelectCommonColoursDescription: "" + HexadecimalEntry: "" + HexadecimalEntryDescription: "" + HexEntryPlaceholder: "" + Voting: + Question: "" + Details: "" + MultipleChoicePlaceholder: "" + MultipleChoiceYesDescription: "" + MultipleChoiceNoDescription: "" + AllowMultipleChoices: "" + AllowMultipleChoicesPlaceholder: "" + AllowMultipleChoicesYesDescription: "" + AllowMultipleChoicesNoDescription: "" + OptionLabelTemplate: "" From 1faa84821968b0b404ad54f29f1473b92e07647f Mon Sep 17 00:00:00 2001 From: Sidpatchy Date: Mon, 13 Oct 2025 14:28:46 -0600 Subject: [PATCH 32/64] docs: pull docs into main repo from ClaireBot-docs --- Writerside/c.list | 6 + Writerside/cd.tree | 57 ++++ Writerside/cfg/buildprofiles.xml | 13 + Writerside/cfg/glossary.xml | 7 + Writerside/redirection-rules.xml | 17 ++ Writerside/topics/8ball.md | 21 ++ Writerside/topics/Archived-Overview.md | 3 + Writerside/topics/Beyond-Javacord.md | 24 ++ Writerside/topics/Contributing-guide.md | 178 +++++++++++++ .../topics/Interface-Standardization-1.md | 23 ++ Writerside/topics/Requests-Channel.md | 27 ++ Writerside/topics/avatar.md | 29 ++ Writerside/topics/config-server.md | 31 +++ Writerside/topics/config-user.md | 27 ++ Writerside/topics/config.md | 26 ++ Writerside/topics/help.md | 20 ++ Writerside/topics/info.md | 21 ++ Writerside/topics/leaderboard.md | 23 ++ Writerside/topics/level.md | 22 ++ .../topics/openapi.yml/API_Reference.md | 17 ++ .../topics/openapi.yml/Create_new_guild.md | 3 + .../topics/openapi.yml/Create_new_user.md | 3 + Writerside/topics/openapi.yml/Delete_guild.md | 3 + Writerside/topics/openapi.yml/Delete_user.md | 3 + .../topics/openapi.yml/Get_all_guilds.md | 3 + .../topics/openapi.yml/Get_all_users.md | 3 + .../topics/openapi.yml/Get_guild_by_ID.md | 3 + .../topics/openapi.yml/Get_user_by_ID.md | 3 + Writerside/topics/openapi.yml/Update_guild.md | 3 + Writerside/topics/openapi.yml/Update_user.md | 3 + Writerside/topics/openapi.yml/openapi.yml | 247 ++++++++++++++++++ Writerside/topics/overview.md | 70 +++++ Writerside/topics/poll.md | 42 +++ Writerside/topics/quote.md | 36 +++ Writerside/topics/santa.md | 37 +++ Writerside/topics/server.md | 28 ++ Writerside/topics/user.md | 23 ++ Writerside/v.list | 5 + Writerside/writerside.cfg | 11 + 39 files changed, 1121 insertions(+) create mode 100644 Writerside/c.list create mode 100644 Writerside/cd.tree create mode 100644 Writerside/cfg/buildprofiles.xml create mode 100644 Writerside/cfg/glossary.xml create mode 100644 Writerside/redirection-rules.xml create mode 100644 Writerside/topics/8ball.md create mode 100644 Writerside/topics/Archived-Overview.md create mode 100644 Writerside/topics/Beyond-Javacord.md create mode 100644 Writerside/topics/Contributing-guide.md create mode 100644 Writerside/topics/Interface-Standardization-1.md create mode 100644 Writerside/topics/Requests-Channel.md create mode 100644 Writerside/topics/avatar.md create mode 100644 Writerside/topics/config-server.md create mode 100644 Writerside/topics/config-user.md create mode 100644 Writerside/topics/config.md create mode 100644 Writerside/topics/help.md create mode 100644 Writerside/topics/info.md create mode 100644 Writerside/topics/leaderboard.md create mode 100644 Writerside/topics/level.md create mode 100644 Writerside/topics/openapi.yml/API_Reference.md create mode 100644 Writerside/topics/openapi.yml/Create_new_guild.md create mode 100644 Writerside/topics/openapi.yml/Create_new_user.md create mode 100644 Writerside/topics/openapi.yml/Delete_guild.md create mode 100644 Writerside/topics/openapi.yml/Delete_user.md create mode 100644 Writerside/topics/openapi.yml/Get_all_guilds.md create mode 100644 Writerside/topics/openapi.yml/Get_all_users.md create mode 100644 Writerside/topics/openapi.yml/Get_guild_by_ID.md create mode 100644 Writerside/topics/openapi.yml/Get_user_by_ID.md create mode 100644 Writerside/topics/openapi.yml/Update_guild.md create mode 100644 Writerside/topics/openapi.yml/Update_user.md create mode 100644 Writerside/topics/openapi.yml/openapi.yml create mode 100644 Writerside/topics/overview.md create mode 100644 Writerside/topics/poll.md create mode 100644 Writerside/topics/quote.md create mode 100644 Writerside/topics/santa.md create mode 100644 Writerside/topics/server.md create mode 100644 Writerside/topics/user.md create mode 100644 Writerside/v.list create mode 100644 Writerside/writerside.cfg diff --git a/Writerside/c.list b/Writerside/c.list new file mode 100644 index 0000000..c4c77a2 --- /dev/null +++ b/Writerside/c.list @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/Writerside/cd.tree b/Writerside/cd.tree new file mode 100644 index 0000000..7c27a81 --- /dev/null +++ b/Writerside/cd.tree @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Writerside/cfg/buildprofiles.xml b/Writerside/cfg/buildprofiles.xml new file mode 100644 index 0000000..1edc2ec --- /dev/null +++ b/Writerside/cfg/buildprofiles.xml @@ -0,0 +1,13 @@ + + + + + + + + true + + + + diff --git a/Writerside/cfg/glossary.xml b/Writerside/cfg/glossary.xml new file mode 100644 index 0000000..22bec6b --- /dev/null +++ b/Writerside/cfg/glossary.xml @@ -0,0 +1,7 @@ + + + + + Description of what "foo" is. + + \ No newline at end of file diff --git a/Writerside/redirection-rules.xml b/Writerside/redirection-rules.xml new file mode 100644 index 0000000..6a7071a --- /dev/null +++ b/Writerside/redirection-rules.xml @@ -0,0 +1,17 @@ + + + + + + Created after removal of "wasd" from ClaireBot Docs + wasd.html + + + Created after removal of "Command" from ClaireBot Docs + Command.html + + \ No newline at end of file diff --git a/Writerside/topics/8ball.md b/Writerside/topics/8ball.md new file mode 100644 index 0000000..dff5e0e --- /dev/null +++ b/Writerside/topics/8ball.md @@ -0,0 +1,21 @@ +# /8ball + +8ball is exactly what it sounds like, though it's worth noting that ClaireBot only speaks in truth. Remember kids, +flesh betrays, ClaireBot will not. + +## Command + +### Syntax + +```shell +/8ball [query] +``` + +### Options + +query +: The question you wish to consult ClaireBot about. + + + + \ No newline at end of file diff --git a/Writerside/topics/Archived-Overview.md b/Writerside/topics/Archived-Overview.md new file mode 100644 index 0000000..4239386 --- /dev/null +++ b/Writerside/topics/Archived-Overview.md @@ -0,0 +1,3 @@ +# Archived + +This section contains various bits and bobs that are not particularly relevant anymore, but are retained for posterity. \ No newline at end of file diff --git a/Writerside/topics/Beyond-Javacord.md b/Writerside/topics/Beyond-Javacord.md new file mode 100644 index 0000000..49b83ce --- /dev/null +++ b/Writerside/topics/Beyond-Javacord.md @@ -0,0 +1,24 @@ +# Beyond Javacord + +Since day one, ClaireBot 3 has been based on Javacord due to it being the easiest Java-based Discord API wrapper +to work with. As far as I am concerned, that remains true today. On top of that, it has great documentation +and a lovely community. + +Despite how much I like Javacord, I recognize one major issue with it: +it's development has come to a complete standstill. + +**This leaves me with two realistic options**: +1. Contribute to Javacord and work to bring it up to date. +2. Migrate to a different API wrapper (JDA, Discord4J, etc.) + +## Option #1: Contributing to Javacord +In an ideal world, this is the path I'd choose, however, Javacord is quite out of date at this point, and I'd rather +put that effort into improving ClaireBot or working on one of my various other hobbies... which I have too many of. + +Javacord is currently using the latest Discord API version (v10, see: [Javacord.java](https://github.com/Javacord/Javacord/blob/aa5afd1dde791a8811ccdbc881b44d29cb629699/javacord-api/src/main/java/org/javacord/api/Javacord.java#L95) and [Discord Develpoer Portal](https://discord.com/developers/docs/reference)), +sooooo it wouldn't be completely out of the realm of reason to implement new features. + +## Option #2: +From a quick glance, Discord4J seems like the best option if ClaireBot were to switch libraries. It uses Spring's Mono +classes which _can_ be converted to CompletableFuture. This would hopefully eliminate the need to do major rewrites to +large parts of ClaireBot as structure could remain quite similar. \ No newline at end of file diff --git a/Writerside/topics/Contributing-guide.md b/Writerside/topics/Contributing-guide.md new file mode 100644 index 0000000..405d0af --- /dev/null +++ b/Writerside/topics/Contributing-guide.md @@ -0,0 +1,178 @@ +# Contributing + +We welcome pull requests! + + +# Contributing to ClaireBot + +First off, thanks for taking the time to contribute! ❤️ + +All types of contributions are encouraged and valued. See the [Table of Contents](#table-of-contents) for different ways to help and details about how this project handles them. Please make sure to read the relevant section before making your contribution. It will make it a lot easier for us maintainers and smooth out the experience for all involved. The community looks forward to your contributions. 🎉 + +> And if you like the project, but just don't have time to contribute, that's fine. There are other easy ways to support the project and show your appreciation, which we would also be very happy about: +> - Star the project +> - Tweet about it +> - Refer this project in your project's readme +> - Mention the project at local meetups and tell your friends/colleagues + + +## Table of Contents + +- [I Have a Question](#i-have-a-question) + - [I Want To Contribute](#i-want-to-contribute) + - [Reporting Bugs](#reporting-bugs) + - [Suggesting Enhancements](#suggesting-enhancements) + - [Your First Code Contribution](#your-first-code-contribution) + - [Improving The Documentation](#documentation) +- [Styleguide](#style-guides) + - [Commit Messages](#commit-messages) + - [Documentation](#documentation) +- [Join The Project Team](#join-the-project-team) + + + +## I Have a Question + +> If you want to ask a question, we assume that you have read the available [Documentation](https://docs.clairebot.net). + +Before you ask a question, it is best to search for existing [Issues](https://github.com/Sidpatchy/ClaireBot/issues) that might help you. In case you have found a suitable issue and still need clarification, you can write your question in this issue. It is also advisable to search the internet for answers first. + +If you then still feel the need to ask a question and need clarification, we recommend the following: + +- Open an [Issue](https://github.com/Sidpatchy/ClaireBot/issues/new). +- Provide as much context as you can about what you're running into. +- Provide project and platform versions (nodejs, npm, etc), depending on what seems relevant. + +We will then take care of the issue as soon as possible. + + + +## I Want To Contribute + +> ### Legal Notice +> When contributing to this project, you must agree that you have authored 100% of the content, that you have the necessary rights to the content and that the content you contribute may be provided under the project licence. + +### Reporting Bugs + + +#### Before Submitting a Bug Report + +A good bug report shouldn't leave others needing to chase you up for more information. Therefore, we ask you to investigate carefully, collect information and describe the issue in detail in your report. Please complete the following steps in advance to help us fix any potential bug as fast as possible. + +- Make sure that you are using the latest version. +- Determine if your bug is really a bug and not an error on your side e.g. using incompatible environment components/versions (Make sure that you have read the [documentation](https://docs.clairebot.net). If you are looking for support, you might want to check [this section](#i-have-a-question)). +- To see if other users have experienced (and potentially already solved) the same issue you are having, check if there is not already a bug report existing for your bug or error in the [bug tracker](https://github.com/Sidpatchy/ClaireBot/issues?q=label%3Abug). +- Also make sure to search the internet (including Stack Overflow) to see if users outside of the GitHub community have discussed the issue. +- Collect information about the bug: + - Stack trace (Traceback) + - OS, Platform and Version (Windows, Linux, macOS, x86, ARM) + - Version of the interpreter, compiler, SDK, runtime environment, package manager, depending on what seems relevant. + - Possibly your input and the output + - Can you reliably reproduce the issue? And can you also reproduce it with older versions? + + +#### How Do I Submit a Good Bug Report? + +> You must never report security related issues, vulnerabilities or bugs including sensitive information to the issue tracker, or elsewhere in public. Instead sensitive bugs must be sent by email to . + + +We use GitHub issues to track bugs and errors. If you run into an issue with the project: + +- Open an [Issue](https://github.com/Sidpatchy/ClaireBot/issues/new). (Since we can't be sure at this point whether it is a bug or not, we ask you not to talk about a bug yet and not to label the issue.) +- Explain the behavior you would expect and the actual behavior. +- Please provide as much context as possible and describe the *reproduction steps* that someone else can follow to recreate the issue on their own. This usually includes your code. For good bug reports you should isolate the problem and create a reduced test case. +- Provide the information you collected in the previous section. + +Once it's filed: + +- The project team will label the issue accordingly. +- A team member will try to reproduce the issue with your provided steps. If there are no reproduction steps or no obvious way to reproduce the issue, the team will ask you for those steps and mark the issue as `needs-repro`. Bugs with the `needs-repro` tag will not be addressed until they are reproduced. +- If the team is able to reproduce the issue, it will be marked `needs-fix`, as well as possibly other tags (such as `critical`), and the issue will be left to be [implemented by someone](#your-first-code-contribution). + + + + +### Suggesting Enhancements + +This section guides you through submitting an enhancement suggestion for ClaireBot, **including completely new features and minor improvements to existing functionality**. Following these guidelines will help maintainers and the community to understand your suggestion and find related suggestions. + + +#### Before Submitting an Enhancement + +- Make sure that you are using the latest version. +- Read the [documentation](https://docs.clairebot.net) carefully and find out if the functionality is already covered, maybe by an individual configuration. +- Perform a [search](https://github.com/Sidpatchy/ClaireBot/issues) to see if the enhancement has already been suggested. If it has, add a comment to the existing issue instead of opening a new one. +- Find out whether your idea fits with the scope and aims of the project. It's up to you to make a strong case to convince the project's developers of the merits of this feature. Keep in mind that we prefer features that will be useful to the majority of our users and not just a small subset. Features like these will be prioritized lower. + + +#### How Do I Submit a Good Enhancement Suggestion? + +Enhancement suggestions are tracked as [GitHub issues](https://github.com/Sidpatchy/ClaireBot/issues). + +- Use a **clear and descriptive title** for the issue to identify the suggestion. +- Provide a **step-by-step description of the suggested enhancement** in as many details as possible. +- **Describe the current behavior** and **explain which behavior you expected to see instead** and why. At this point you can also tell which alternatives do not work for you. +- You may want to **include screenshots or screen recordings** which help you demonstrate the steps or point out the part which the suggestion is related to. You can use [LICEcap](https://www.cockos.com/licecap/) to record GIFs on macOS and Windows, and the built-in [screen recorder in GNOME](https://help.gnome.org/users/gnome-help/stable/screen-shot-record.html.en) or [SimpleScreenRecorder](https://github.com/MaartenBaert/ssr) on Linux. +- **Explain why this enhancement would be useful** to most ClaireBot users. You may also want to point out the other projects that solved it better and which could serve as inspiration. + + + +### Your First Code Contribution + + +ClaireBot is primarily developed using IntelliJ IDEA. Other IDEs are acceptable, they are just not documented here due +to the maintainer's unfamiliarity with them. + +#### Setting Up a Working Copy of ClaireBot +You have two main options for setting up ClaireBot. + +##### ClaireBot Docker +The easiest way to set up ClaireBot/ClaireData is to use the Docker container provided at +[Sidpatchy/ClaireBot-Docker](https://github.com/Sidpatchy/ClaireBot-Docker). The repo includes documentation in the +README.md for getting started. It is written with Linux in mind, so if you're on Windows, you'll want to look into +WSL2 or Docker Desktop. + +##### Manually Setting Up ClaireBot + + +## Style Guides +### Commit Messages +We prefer [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/#summary) to keep changelogs tidy: +``` +feat: A new feature +fix: A bug fix +docs: Documentation changes +style: Code formatting or style adjustments (no functional changes) +refactor: Code restructuring without altering behavior +test: Adding or modifying tests +chore: Maintenance tasks or tooling updates +``` + +### Documentation +- Submit pull requests to [Sidpatchy/ClaireBot-docs](https://github.com/Sidpatchy/ClaireBot-Docs) +- Avoid using terms like 'simply', 'easy', and 'just' + - If someone's reading the docs, it's not 'simple' + - See: [https://justsimply.dev/](https://justsimply.dev/) + +## Join The Project Team + + + +## Attribution +This guide is based on the [contributing.md](https://contributing.md/generator)! \ No newline at end of file diff --git a/Writerside/topics/Interface-Standardization-1.md b/Writerside/topics/Interface-Standardization-1.md new file mode 100644 index 0000000..79237c1 --- /dev/null +++ b/Writerside/topics/Interface-Standardization-1.md @@ -0,0 +1,23 @@ +# Interface Standardization and Updates #1 +A place to document various interface changes that need to occur. + +## Issue #1 +ClaireBot currently uses a mix of CamelCase and kebab-case in user-facing resources. This should be standardized +to kebab-case. + +Internal naming will retain CamelCase (variable names, etc.). All user-facing elements need to be transitioned +to kebab-casing. + +## Issue #2 +/help command info should include a link to the relevant documentation webpage. This is blocked pending completion of +the documentation for all commands. + +Will either need to update Robin's implementation of the commands.yml standard, or just pull that implementation into +ClaireBot. + +## Issue #3 +What happens if there is no requests channel??? I'm pretty sure ClaireBot will just throw an error and to the user, +fail completely silently. This is very poor user experience. + +## Issue #4 +/user doesn't do very much right now. More fields should be considered for addition. \ No newline at end of file diff --git a/Writerside/topics/Requests-Channel.md b/Writerside/topics/Requests-Channel.md new file mode 100644 index 0000000..5edb1fe --- /dev/null +++ b/Writerside/topics/Requests-Channel.md @@ -0,0 +1,27 @@ +# Requests Channel + +The requests channel for a server is determined via the following steps: +

    +
  • Getting a Request Channel: +
      +
    • Step 1: Check ClaireData API +
        +
      • Success: Use the configured channel
      • +
      • Failure: Move to Step 2
      • +
      +
    • +
    • Step 2: Search for Default Channel +
        +
      • Look for channel named "requests" +
          +
        • Found: Use this channel
        • +
        • Not Found: Request fails
        • +
        +
      • +
      +
    • +
    +
  • +
+ +If no channel is found, the /request will fail. \ No newline at end of file diff --git a/Writerside/topics/avatar.md b/Writerside/topics/avatar.md new file mode 100644 index 0000000..f1d858d --- /dev/null +++ b/Writerside/topics/avatar.md @@ -0,0 +1,29 @@ +# /avatar + +Command to display a user's profile image. + +## Command + +### Syntax + +```shell +/avatar [optional: user] [optional: globalAvatar] +``` + +### Options + +user (optional) +: The user whose avatar you'd like to display. +
If left blank, ClaireBot will display the avatar of the user who issued the command. + + +globalAvatar (optional) +: If left blank, defaults to true
Options: +- **True**: ClaireBot will display the user's global avatar. +- **False**: ClaireBot will display the user's server-specific avatar. + + + + + + \ No newline at end of file diff --git a/Writerside/topics/config-server.md b/Writerside/topics/config-server.md new file mode 100644 index 0000000..032acfa --- /dev/null +++ b/Writerside/topics/config-server.md @@ -0,0 +1,31 @@ +# /config server + +Opens up the server settings page. + +## Command + +### Syntax + +```shell +/config server +``` + +### Options + +Requests Channel +: Allows server administrators to choose where ClaireBot should post users' requests. Lists out the first 25 channels +in the server's channel list. + +Moderator Messages Channel +: Allows server administrators to choose where ClaireBot should send moderator messages. Lists out the first 25 channels +in the server's channel list. + +Enforce Server Language +: Allows server administrators to force ClaireBot to use the same language as the server's region settings dictate. +

**Options**: +- **True**: ClaireBot will enforce the server's language. +- **False**: ClaireBot will respect the user's preferences and use their preferred language. + + + + \ No newline at end of file diff --git a/Writerside/topics/config-user.md b/Writerside/topics/config-user.md new file mode 100644 index 0000000..f797026 --- /dev/null +++ b/Writerside/topics/config-user.md @@ -0,0 +1,27 @@ +# /config user + +Opens up the user preferences page. + +## Command + +### Syntax + +```shell +/config user +``` + +### Options + +Accent Colour +: For those who hate the colour blue. Allows you to change the accent colour of embeds in ClaireBot's +responses. Options: +- **Select Common Colours**: Allows you to select a list of pre-configured colours. +- **Hexadecimal Entry**: Allows you to enter any hexadecimal (#000000) colour code. + +Language +: For those who are offended by English–or those who prefer a different language. +

This feature isn't yet implemented. It is currently planned for ClaireBot v3.4. + + + + \ No newline at end of file diff --git a/Writerside/topics/config.md b/Writerside/topics/config.md new file mode 100644 index 0000000..1953e96 --- /dev/null +++ b/Writerside/topics/config.md @@ -0,0 +1,26 @@ +# /config + +Interactive method for configuring ClaireBot. Allows for configuring both user and server settings. + +#### See also +- [/config user](config-user.md) +- [/config server](config-server.md) + +## Command + +### Syntax + +```shell +/config [optional: mode] +``` + +### Options + +mode (optional) +: The configuration section you want to ender. Options: +- User (default): Configuration options for users, see: [/config user](config-user.md) +- Server: Configuration options for servers, see: [/config server](config-server.md) + + + + \ No newline at end of file diff --git a/Writerside/topics/help.md b/Writerside/topics/help.md new file mode 100644 index 0000000..eb7ad97 --- /dev/null +++ b/Writerside/topics/help.md @@ -0,0 +1,20 @@ +# /help + +Displays info about a given command. + +## Command + +### Syntax + +```shell +/help [optional: command-name] +``` + +### Options + +command-name (optional) +: If specified ClaireBot will provide details on the specified command. + + + + \ No newline at end of file diff --git a/Writerside/topics/info.md b/Writerside/topics/info.md new file mode 100644 index 0000000..5a82b4e --- /dev/null +++ b/Writerside/topics/info.md @@ -0,0 +1,21 @@ +# /info + +The info command reports several details about ClaireBot, including: +- Where to find help +- How to add ClaireBot to a server +- A link to ClaireBot's source code +- How many servers ClaireBot is a member of +- ClaireBot's currently running version and release date. +- ClaireBot's uptime. + +## Command + +### Syntax + +```shell +/info +``` + + + + \ No newline at end of file diff --git a/Writerside/topics/leaderboard.md b/Writerside/topics/leaderboard.md new file mode 100644 index 0000000..34ecf92 --- /dev/null +++ b/Writerside/topics/leaderboard.md @@ -0,0 +1,23 @@ +# /leaderboard + +8ball is exactly what it sounds like, though it's worth noting that ClaireBot only speaks in truth. Remember kids, +flesh betrays, ClaireBot will not. + +## Command + +### Syntax + +```shell +/leaderboard [optional: global] +``` + +### Options + +global (optional) +: If left blank, defaults to False.
Options: +- **True**: Displays ClaireBot's global leaderboard. +- **False**: Displays ClaireBot's leaderboard for the server the command was executed in. + + + + \ No newline at end of file diff --git a/Writerside/topics/level.md b/Writerside/topics/level.md new file mode 100644 index 0000000..9535152 --- /dev/null +++ b/Writerside/topics/level.md @@ -0,0 +1,22 @@ +# /level + +Displays your ClaireBot level. This is the same thing that would be displayed on the [/leaderboard](leaderboard.md) if +you (or the queried user) are ranked high enough. + +## Command + +### Syntax + +```shell +/8ball [optional: user] +``` + +### Options + +user (optional) +: The user you wish to get the level of. +
If left blank, ClaireBot will report the level of whoever executed the command. + + + + \ No newline at end of file diff --git a/Writerside/topics/openapi.yml/API_Reference.md b/Writerside/topics/openapi.yml/API_Reference.md new file mode 100644 index 0000000..774437f --- /dev/null +++ b/Writerside/topics/openapi.yml/API_Reference.md @@ -0,0 +1,17 @@ +# API Reference + +| Endpoint | Method | Description | Documentation | +|-------------------------------|--------|---------------------|-----------------------------------------| +| ```/api/v1/user``` | GET | Retrieve all users | [Get all users](Get_all_users.md) | +| ```/api/v1/user``` | POST | Create a new user | [Create new user](Create_new_user.md) | +| ```/api/v1/user/{userID}``` | GET | Get user by ID | [Get used by ID](Get_user_by_ID.md) | +| ```/api/v1/user/{userID}``` | PUT | Update user | [Update user](Update_user.md) | +| ```/api/v1/user/{userID}``` | DELETE | Delete user | [Delete user](Delete_user.md) | +| ```/api/v1/guild``` | GET | Retrieve all guilds | [Get all guilds](Get_all_guilds.md) | +| ```/api/v1/guild``` | POST | Create a new guild | [Create new guild](Create_new_guild.md) | +| ```/api/v1/guild/{guildID}``` | GET | Get guild by ID | [Get guild by ID](Get_guild_by_ID.md) | +| ```/api/v1/guild/{guildID}``` | PUT | Update guild | [Update guild](Update_guild.md) | +| ```/api/v1/guild/{guildID}``` | DELETE | Delete guild | [Delete guild](Delete_guild.md) | + +All endpoints require Basic Authentication and accept/return JSON data. + diff --git a/Writerside/topics/openapi.yml/Create_new_guild.md b/Writerside/topics/openapi.yml/Create_new_guild.md new file mode 100644 index 0000000..866a35d --- /dev/null +++ b/Writerside/topics/openapi.yml/Create_new_guild.md @@ -0,0 +1,3 @@ +# Create new guild + + \ No newline at end of file diff --git a/Writerside/topics/openapi.yml/Create_new_user.md b/Writerside/topics/openapi.yml/Create_new_user.md new file mode 100644 index 0000000..7fd446f --- /dev/null +++ b/Writerside/topics/openapi.yml/Create_new_user.md @@ -0,0 +1,3 @@ +# Create new user + + \ No newline at end of file diff --git a/Writerside/topics/openapi.yml/Delete_guild.md b/Writerside/topics/openapi.yml/Delete_guild.md new file mode 100644 index 0000000..e8b4f23 --- /dev/null +++ b/Writerside/topics/openapi.yml/Delete_guild.md @@ -0,0 +1,3 @@ +# Delete guild + + \ No newline at end of file diff --git a/Writerside/topics/openapi.yml/Delete_user.md b/Writerside/topics/openapi.yml/Delete_user.md new file mode 100644 index 0000000..dd92836 --- /dev/null +++ b/Writerside/topics/openapi.yml/Delete_user.md @@ -0,0 +1,3 @@ +# Delete user + + \ No newline at end of file diff --git a/Writerside/topics/openapi.yml/Get_all_guilds.md b/Writerside/topics/openapi.yml/Get_all_guilds.md new file mode 100644 index 0000000..c5ef78c --- /dev/null +++ b/Writerside/topics/openapi.yml/Get_all_guilds.md @@ -0,0 +1,3 @@ +# Get all guilds + + \ No newline at end of file diff --git a/Writerside/topics/openapi.yml/Get_all_users.md b/Writerside/topics/openapi.yml/Get_all_users.md new file mode 100644 index 0000000..e00cbec --- /dev/null +++ b/Writerside/topics/openapi.yml/Get_all_users.md @@ -0,0 +1,3 @@ +# Get all users + + \ No newline at end of file diff --git a/Writerside/topics/openapi.yml/Get_guild_by_ID.md b/Writerside/topics/openapi.yml/Get_guild_by_ID.md new file mode 100644 index 0000000..bb556d6 --- /dev/null +++ b/Writerside/topics/openapi.yml/Get_guild_by_ID.md @@ -0,0 +1,3 @@ +# Get guild by ID + + \ No newline at end of file diff --git a/Writerside/topics/openapi.yml/Get_user_by_ID.md b/Writerside/topics/openapi.yml/Get_user_by_ID.md new file mode 100644 index 0000000..21d86aa --- /dev/null +++ b/Writerside/topics/openapi.yml/Get_user_by_ID.md @@ -0,0 +1,3 @@ +# Get user by ID + + \ No newline at end of file diff --git a/Writerside/topics/openapi.yml/Update_guild.md b/Writerside/topics/openapi.yml/Update_guild.md new file mode 100644 index 0000000..1e18ff7 --- /dev/null +++ b/Writerside/topics/openapi.yml/Update_guild.md @@ -0,0 +1,3 @@ +# Update guild + + \ No newline at end of file diff --git a/Writerside/topics/openapi.yml/Update_user.md b/Writerside/topics/openapi.yml/Update_user.md new file mode 100644 index 0000000..6123da2 --- /dev/null +++ b/Writerside/topics/openapi.yml/Update_user.md @@ -0,0 +1,3 @@ +# Update user + + \ No newline at end of file diff --git a/Writerside/topics/openapi.yml/openapi.yml b/Writerside/topics/openapi.yml/openapi.yml new file mode 100644 index 0000000..1b3bdc1 --- /dev/null +++ b/Writerside/topics/openapi.yml/openapi.yml @@ -0,0 +1,247 @@ +openapi: 3.0.0 +info: + title: ClaireData API + version: 1.0.0 + description: Spring Boot REST API for managing users and guilds + +tags: + - name: User Controller + description: Endpoints for user management + - name: Guild Controller + description: Endpoints for guild management + +paths: + /api/v1/user: + get: + tags: + - User Controller + operationId: getAllUsers + summary: Get all users + responses: + '200': + description: List of all users retrieved successfully + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/User' + + post: + tags: + - User Controller + operationId: addUser + summary: Create new user + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/User' + responses: + '200': + description: User created successfully + + /api/v1/user/{userID}: + get: + tags: + - User Controller + operationId: getUserById + summary: Get user by ID + parameters: + - name: userID + in: path + required: true + schema: + type: string + responses: + '200': + description: User found + content: + application/json: + schema: + $ref: '#/components/schemas/User' + '404': + description: User not found + + put: + tags: + - User Controller + operationId: updateUser + summary: Update user + parameters: + - name: userID + in: path + required: true + schema: + type: string + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/User' + responses: + '200': + description: User updated successfully + + delete: + tags: + - User Controller + operationId: deleteUserById + summary: Delete user + parameters: + - name: userID + in: path + required: true + schema: + type: string + responses: + '200': + description: User deleted successfully + + /api/v1/guild: + get: + tags: + - Guild Controller + operationId: getAllGuilds + summary: Get all guilds + responses: + '200': + description: List of all guilds retrieved successfully + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Guild' + + post: + tags: + - Guild Controller + operationId: addGuild + summary: Create new guild + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/Guild' + responses: + '200': + description: Guild created successfully + + /api/v1/guild/{guildID}: + get: + tags: + - Guild Controller + operationId: getGuildById + summary: Get guild by ID + parameters: + - name: guildID + in: path + required: true + schema: + type: string + responses: + '200': + description: Guild found + content: + application/json: + schema: + $ref: '#/components/schemas/Guild' + '404': + description: Guild not found + + put: + tags: + - Guild Controller + operationId: updateGuild + summary: Update guild + parameters: + - name: guildID + in: path + required: true + schema: + type: string + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/Guild' + responses: + '200': + description: Guild updated successfully + + delete: + tags: + - Guild Controller + operationId: deleteGuildById + summary: Delete guild + parameters: + - name: guildID + in: path + required: true + schema: + type: string + responses: + '200': + description: Guild deleted successfully + +components: + schemas: + User: + type: object + required: + - userID + properties: + userID: + type: string + accentColour: + type: string + description: Hex color code with '#' prefix + example: "#FF0000" + language: + type: string + description: ISO 639-3 language code + example: "eng" + pointsGuildID: + type: array + items: + type: string + pointsMessages: + type: array + items: + type: integer + deprecated: true + pointsVoiceChat: + type: array + items: + type: integer + deprecated: true + + Guild: + type: object + required: + - guildID + properties: + guildID: + type: string + requestsChannelID: + type: string + description: Discord channel ID for requests + moderatorMessagesChannelID: + type: string + description: Discord channel ID for moderator messages + enforceServerLanguage: + type: boolean + description: Whether to enforce server language settings + + securitySchemes: + BasicAuth: + type: http + scheme: basic + +security: + - BasicAuth: [] diff --git a/Writerside/topics/overview.md b/Writerside/topics/overview.md new file mode 100644 index 0000000..3d759d5 --- /dev/null +++ b/Writerside/topics/overview.md @@ -0,0 +1,70 @@ +# Overview + +> This documentation site is currently a work in progress. While efforts have been made to ensure accuracy, some +> details may be incomplete or subject to change. Critical information should be verified independently. Once the +> initial version is finalized, the documentation will be open-sourced and available at +> [Sidpatchy/ClaireBot-Docs](https://github.com/Sidpatchy/ClaireBot-Docs). +{style="warning"} + +Documentation for ClaireBot, ClaireData, and ClaireBot Docker all in one place, authored using Jetbrains Writerside. + +To contribute to these docs, or ClaireBot in general, please see: [Contributing](Contributing-guide.md) + +## What is ClaireBot? + +ClaireBot is a Discord bot written in Java with a focus on ease-of-use and beauty. + +### Background +ClaireBot 1 and 2 were initially built for a group of friends to use, and it was only used on a handful of servers. + +As time went on, I grew more and more interested in using ClaireBot elsewhere, in my public servers, so I rewrote bits +and pieces of her to work in different servers, and, eventually had rewritten nearly everything. This was ClaireBot 2. + +Over time, I wanted more, and more. But ClaireBot 2 was still using an architecture very similar to the original. It was +a pain to continue developing new features as I'd find myself tripping over technical debt nearly constantly, but I +persisted. It wasn't worth taking the time to rewrite ALL of ClaireBot, at least that's what I had told my self. + +Eventually, Discord.py was discontinued, this forced my hand. If I wanted to keep developing ClaireBot, I would have +to rewrite it from the ground up. Thus, ClaireBot 3 was born. + +It took me over a year and a half to finally finish the port, because procrastination is strong. That leads us to today. + +### Present Day + +ClaireBot is currently developed primarily in Java, some components have been and are in Kotlin, but this is currently +a very minor portion of the codebase. + +ClaireBot uses the Javacord library for interacting with Discord. Without getting sidetracked too much, this is subject +to change as Javacord's development has greatly slowed (see: [Beyond Javacord](Beyond-Javacord.md)). + +At the time of writing ClaireBot (v3.3.2) has the following commands: + +| Command | Description | Documentation | +|--------------|-------------------------------------------------------------------------------------------------|------------------------| +| /8ball | Exactly what it sounds like, though it's worth noting that ClaireBot only speaks in truth. | [Docs](8ball.md) | +| /avatar | Gets a user's profile image. | [Docs](avatar.md) | +| /help | How use ClaireBot??? | [Docs](help.md) | +| /info | Displays various nuggets of information (uptime, version, support info, etc.). | [Docs](info.md) | +| /leaderboard | Provides details on ClaireBot's (currently rudimentary) implementation of a Leaderboard system. | [Docs](leaderboard.md) | +| /level | Displays your ClaireBot level (tied together with the leaderboard system). | [Docs](level.md) | +| /poll | Creates a poll. Leave arguments empty for an interactive popup. | [Docs](poll.md) | +| /quote | Picks a random message from the channel the command is executed in and displays it. | [Docs](quote.md) | +| /request | Same system as /poll, but directs it to the server's requests channel. | [Docs](poll.md) | +| /server | Reports various bits of info about the (optionally specified) server / guild. | [Docs](server.md) | +| /user | Reports various bits of info about the specified user. | [Docs](user.md) | +| /config | Allows for modifying user or server preferences. | [Docs](config.md) | +| /santa | Tool for organizing secret santa gift exchanges. | [Docs](santa.md) | + +## Glossary + +ClaireBot +: ClaireBot refers to both the ClaireBot project as a whole, and the Discord bot component. +

The Discord bot component is the key piece of software that users interact with. It is what communicates with +the Discord API and responds to user calls. + +ClaireData +: ClaireData is the piece of software used to store long-term, persistent data. It serves a REST API that ClaireBot +interfaces with. ClaireData uses Postgres as a database. + +ClaireBot Docker +: A series of Docker containers, and a docker-compose.yml for quickly and easily setting up a working copy of ClaireBot. \ No newline at end of file diff --git a/Writerside/topics/poll.md b/Writerside/topics/poll.md new file mode 100644 index 0000000..d01c506 --- /dev/null +++ b/Writerside/topics/poll.md @@ -0,0 +1,42 @@ +# /poll & /request + +Allows for creating interactive, inline polls and requests. + +Polls and requests are effectively the same system. In fact, they use identical backend code. The key difference is that +Polls are inserted into the channel where the command is run, and requests are sent to the server's requests channel. + +For more info on how the requests channel is selected, please see: [_Requests Channel_](Requests-Channel.md) + +## Command + +### Syntax + +```shell +/poll [question] [optional: allow-multiple-choices] [optional: queries] +/poll +``` +```shell +/request [question] [optional: allow-multiple-choices] [optional: queries] +/request +``` + +### Options + +question +: The main question of your poll. + +allow-multiple-choices (optional) +: Determines whether Clairebot will allow users to vote for multiple options. + +queries (optional) +: Allows for specifying multiple choice answers. You can have up to 9 choices. + +When run with no options +: If none of the previous options are specified, ClaireBot will send your Discord client a popup window allowing you to +create a poll / request in a more graphical manner. +

This is better for situations where you have a lot to type out, or when you want to avoid fiddling +with the various options. + + + + \ No newline at end of file diff --git a/Writerside/topics/quote.md b/Writerside/topics/quote.md new file mode 100644 index 0000000..85e920f --- /dev/null +++ b/Writerside/topics/quote.md @@ -0,0 +1,36 @@ +# /quote + +Picks a random message from the channel the command is executed in and displays it. + +On Discord desktop, you can click the author's name to jump to the original message. + +On desktop and mobile, you can select the "View Original" button to jump to the original message. +ClaireBot will send an ephemeral message which contains a link to the original. + +### Known Issues + +/quote is ungodly levels of inefficient reference [ClaireBot #4](https://github.com/Sidpatchy/ClaireBot/issues/4) to +track this issue. + +Functions by pulling down (up-to) 50,000 of the most recently posted messages in the channel the +command was executed in. After this, the bot will attempt to pick a random message from the selected user. + +All this is to say it is very slow. Be patient. + +## Command + +### Syntax + +```shell +/quote [optional: user] +``` + +### Options + +user (optional) +: The user you wish to quote. +
If left blank, ClaireBot will quote whoever executed the command. + + + + \ No newline at end of file diff --git a/Writerside/topics/santa.md b/Writerside/topics/santa.md new file mode 100644 index 0000000..12d5eb6 --- /dev/null +++ b/Writerside/topics/santa.md @@ -0,0 +1,37 @@ +# /santa + +SecretClaire is *the* all-in-one solution for facilitating secret santa-style gift exchanges on Discord. +Use `/help santa` or `/santa` to get started! + +SecretClaire which was originally a standalone project that got ported into ClaireBot +(see: [Sidpatchy/SecretClaire](https://github.com/Sidpatchy/SecretClaire)). + +## Command + +### Syntax + +```shell +/santa [role] +``` + +### Options + +role +: In the case of SecretClaire the sole purpose of using a role is to allow for quickly and easily grouping together +users. + +## Example Organizer Messages +![](https://github.com/Sidpatchy/SecretClaire/blob/master/img/screenshot_1.png?raw=true) + +![](https://github.com/Sidpatchy/SecretClaire/blob/master/img/screenshot_6.png?raw=true) + +![](https://github.com/Sidpatchy/SecretClaire/blob/master/img/screenshot_7.png?raw=true) + +## Example gift-giver messages +![](https://github.com/Sidpatchy/SecretClaire/blob/master/img/screenshot_2.png?raw=true) + +![](https://github.com/Sidpatchy/SecretClaire/blob/master/img/screenshot_3.png?raw=true) + + + + \ No newline at end of file diff --git a/Writerside/topics/server.md b/Writerside/topics/server.md new file mode 100644 index 0000000..988573d --- /dev/null +++ b/Writerside/topics/server.md @@ -0,0 +1,28 @@ +# /server + +Reports various bits of info about the (optionally specified) server / guild, including: +- Owner +- Creation Date +- Number of Roles +- Number of Members +- Number of Channels + - Categories + - Text Channels + - Voice Channels + +## Command + +### Syntax + +```shell +/server [optional: guildID] +``` + +### Options + +guildID (optional) +: The ID of the guild you wish to gather info on. ClaireBot must be a member of the server. + + + + \ No newline at end of file diff --git a/Writerside/topics/user.md b/Writerside/topics/user.md new file mode 100644 index 0000000..0974e73 --- /dev/null +++ b/Writerside/topics/user.md @@ -0,0 +1,23 @@ +# /user + +Reports various bits of info about a user, including: +- Discord ID +- Account Creation Date + +## Command + +### Syntax + +```shell +/user [optional: user] +``` + +### Options + +user (optional) +: The user you wish to get the details of. +
If left blank, ClaireBot will report the details of whoever executed the command. + + + + \ No newline at end of file diff --git a/Writerside/v.list b/Writerside/v.list new file mode 100644 index 0000000..2d12cb3 --- /dev/null +++ b/Writerside/v.list @@ -0,0 +1,5 @@ + + + + + diff --git a/Writerside/writerside.cfg b/Writerside/writerside.cfg new file mode 100644 index 0000000..ff2222a --- /dev/null +++ b/Writerside/writerside.cfg @@ -0,0 +1,11 @@ + + + + + + + + + + \ No newline at end of file From 82e022338fb171725263d2c97698f4461844f4ef Mon Sep 17 00:00:00 2001 From: Sidpatchy Date: Mon, 13 Oct 2025 14:28:46 -0600 Subject: [PATCH 33/64] docs: pull docs into main repo from ClaireBot-docs edit to sign with correct key --- Writerside/c.list | 6 + Writerside/cd.tree | 57 ++++ Writerside/cfg/buildprofiles.xml | 13 + Writerside/cfg/glossary.xml | 7 + Writerside/redirection-rules.xml | 17 ++ Writerside/topics/8ball.md | 21 ++ Writerside/topics/Archived-Overview.md | 3 + Writerside/topics/Beyond-Javacord.md | 24 ++ Writerside/topics/Contributing-guide.md | 178 +++++++++++++ .../topics/Interface-Standardization-1.md | 23 ++ Writerside/topics/Requests-Channel.md | 27 ++ Writerside/topics/avatar.md | 29 ++ Writerside/topics/config-server.md | 31 +++ Writerside/topics/config-user.md | 27 ++ Writerside/topics/config.md | 26 ++ Writerside/topics/help.md | 20 ++ Writerside/topics/info.md | 21 ++ Writerside/topics/leaderboard.md | 23 ++ Writerside/topics/level.md | 22 ++ .../topics/openapi.yml/API_Reference.md | 17 ++ .../topics/openapi.yml/Create_new_guild.md | 3 + .../topics/openapi.yml/Create_new_user.md | 3 + Writerside/topics/openapi.yml/Delete_guild.md | 3 + Writerside/topics/openapi.yml/Delete_user.md | 3 + .../topics/openapi.yml/Get_all_guilds.md | 3 + .../topics/openapi.yml/Get_all_users.md | 3 + .../topics/openapi.yml/Get_guild_by_ID.md | 3 + .../topics/openapi.yml/Get_user_by_ID.md | 3 + Writerside/topics/openapi.yml/Update_guild.md | 3 + Writerside/topics/openapi.yml/Update_user.md | 3 + Writerside/topics/openapi.yml/openapi.yml | 247 ++++++++++++++++++ Writerside/topics/overview.md | 70 +++++ Writerside/topics/poll.md | 42 +++ Writerside/topics/quote.md | 36 +++ Writerside/topics/santa.md | 37 +++ Writerside/topics/server.md | 28 ++ Writerside/topics/user.md | 23 ++ Writerside/v.list | 5 + Writerside/writerside.cfg | 11 + 39 files changed, 1121 insertions(+) create mode 100644 Writerside/c.list create mode 100644 Writerside/cd.tree create mode 100644 Writerside/cfg/buildprofiles.xml create mode 100644 Writerside/cfg/glossary.xml create mode 100644 Writerside/redirection-rules.xml create mode 100644 Writerside/topics/8ball.md create mode 100644 Writerside/topics/Archived-Overview.md create mode 100644 Writerside/topics/Beyond-Javacord.md create mode 100644 Writerside/topics/Contributing-guide.md create mode 100644 Writerside/topics/Interface-Standardization-1.md create mode 100644 Writerside/topics/Requests-Channel.md create mode 100644 Writerside/topics/avatar.md create mode 100644 Writerside/topics/config-server.md create mode 100644 Writerside/topics/config-user.md create mode 100644 Writerside/topics/config.md create mode 100644 Writerside/topics/help.md create mode 100644 Writerside/topics/info.md create mode 100644 Writerside/topics/leaderboard.md create mode 100644 Writerside/topics/level.md create mode 100644 Writerside/topics/openapi.yml/API_Reference.md create mode 100644 Writerside/topics/openapi.yml/Create_new_guild.md create mode 100644 Writerside/topics/openapi.yml/Create_new_user.md create mode 100644 Writerside/topics/openapi.yml/Delete_guild.md create mode 100644 Writerside/topics/openapi.yml/Delete_user.md create mode 100644 Writerside/topics/openapi.yml/Get_all_guilds.md create mode 100644 Writerside/topics/openapi.yml/Get_all_users.md create mode 100644 Writerside/topics/openapi.yml/Get_guild_by_ID.md create mode 100644 Writerside/topics/openapi.yml/Get_user_by_ID.md create mode 100644 Writerside/topics/openapi.yml/Update_guild.md create mode 100644 Writerside/topics/openapi.yml/Update_user.md create mode 100644 Writerside/topics/openapi.yml/openapi.yml create mode 100644 Writerside/topics/overview.md create mode 100644 Writerside/topics/poll.md create mode 100644 Writerside/topics/quote.md create mode 100644 Writerside/topics/santa.md create mode 100644 Writerside/topics/server.md create mode 100644 Writerside/topics/user.md create mode 100644 Writerside/v.list create mode 100644 Writerside/writerside.cfg diff --git a/Writerside/c.list b/Writerside/c.list new file mode 100644 index 0000000..c4c77a2 --- /dev/null +++ b/Writerside/c.list @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/Writerside/cd.tree b/Writerside/cd.tree new file mode 100644 index 0000000..7c27a81 --- /dev/null +++ b/Writerside/cd.tree @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Writerside/cfg/buildprofiles.xml b/Writerside/cfg/buildprofiles.xml new file mode 100644 index 0000000..1edc2ec --- /dev/null +++ b/Writerside/cfg/buildprofiles.xml @@ -0,0 +1,13 @@ + + + + + + + + true + + + + diff --git a/Writerside/cfg/glossary.xml b/Writerside/cfg/glossary.xml new file mode 100644 index 0000000..22bec6b --- /dev/null +++ b/Writerside/cfg/glossary.xml @@ -0,0 +1,7 @@ + + + + + Description of what "foo" is. + + \ No newline at end of file diff --git a/Writerside/redirection-rules.xml b/Writerside/redirection-rules.xml new file mode 100644 index 0000000..6a7071a --- /dev/null +++ b/Writerside/redirection-rules.xml @@ -0,0 +1,17 @@ + + + + + + Created after removal of "wasd" from ClaireBot Docs + wasd.html + + + Created after removal of "Command" from ClaireBot Docs + Command.html + + \ No newline at end of file diff --git a/Writerside/topics/8ball.md b/Writerside/topics/8ball.md new file mode 100644 index 0000000..dff5e0e --- /dev/null +++ b/Writerside/topics/8ball.md @@ -0,0 +1,21 @@ +# /8ball + +8ball is exactly what it sounds like, though it's worth noting that ClaireBot only speaks in truth. Remember kids, +flesh betrays, ClaireBot will not. + +## Command + +### Syntax + +```shell +/8ball [query] +``` + +### Options + +query +: The question you wish to consult ClaireBot about. + + + + \ No newline at end of file diff --git a/Writerside/topics/Archived-Overview.md b/Writerside/topics/Archived-Overview.md new file mode 100644 index 0000000..4239386 --- /dev/null +++ b/Writerside/topics/Archived-Overview.md @@ -0,0 +1,3 @@ +# Archived + +This section contains various bits and bobs that are not particularly relevant anymore, but are retained for posterity. \ No newline at end of file diff --git a/Writerside/topics/Beyond-Javacord.md b/Writerside/topics/Beyond-Javacord.md new file mode 100644 index 0000000..49b83ce --- /dev/null +++ b/Writerside/topics/Beyond-Javacord.md @@ -0,0 +1,24 @@ +# Beyond Javacord + +Since day one, ClaireBot 3 has been based on Javacord due to it being the easiest Java-based Discord API wrapper +to work with. As far as I am concerned, that remains true today. On top of that, it has great documentation +and a lovely community. + +Despite how much I like Javacord, I recognize one major issue with it: +it's development has come to a complete standstill. + +**This leaves me with two realistic options**: +1. Contribute to Javacord and work to bring it up to date. +2. Migrate to a different API wrapper (JDA, Discord4J, etc.) + +## Option #1: Contributing to Javacord +In an ideal world, this is the path I'd choose, however, Javacord is quite out of date at this point, and I'd rather +put that effort into improving ClaireBot or working on one of my various other hobbies... which I have too many of. + +Javacord is currently using the latest Discord API version (v10, see: [Javacord.java](https://github.com/Javacord/Javacord/blob/aa5afd1dde791a8811ccdbc881b44d29cb629699/javacord-api/src/main/java/org/javacord/api/Javacord.java#L95) and [Discord Develpoer Portal](https://discord.com/developers/docs/reference)), +sooooo it wouldn't be completely out of the realm of reason to implement new features. + +## Option #2: +From a quick glance, Discord4J seems like the best option if ClaireBot were to switch libraries. It uses Spring's Mono +classes which _can_ be converted to CompletableFuture. This would hopefully eliminate the need to do major rewrites to +large parts of ClaireBot as structure could remain quite similar. \ No newline at end of file diff --git a/Writerside/topics/Contributing-guide.md b/Writerside/topics/Contributing-guide.md new file mode 100644 index 0000000..405d0af --- /dev/null +++ b/Writerside/topics/Contributing-guide.md @@ -0,0 +1,178 @@ +# Contributing + +We welcome pull requests! + + +# Contributing to ClaireBot + +First off, thanks for taking the time to contribute! ❤️ + +All types of contributions are encouraged and valued. See the [Table of Contents](#table-of-contents) for different ways to help and details about how this project handles them. Please make sure to read the relevant section before making your contribution. It will make it a lot easier for us maintainers and smooth out the experience for all involved. The community looks forward to your contributions. 🎉 + +> And if you like the project, but just don't have time to contribute, that's fine. There are other easy ways to support the project and show your appreciation, which we would also be very happy about: +> - Star the project +> - Tweet about it +> - Refer this project in your project's readme +> - Mention the project at local meetups and tell your friends/colleagues + + +## Table of Contents + +- [I Have a Question](#i-have-a-question) + - [I Want To Contribute](#i-want-to-contribute) + - [Reporting Bugs](#reporting-bugs) + - [Suggesting Enhancements](#suggesting-enhancements) + - [Your First Code Contribution](#your-first-code-contribution) + - [Improving The Documentation](#documentation) +- [Styleguide](#style-guides) + - [Commit Messages](#commit-messages) + - [Documentation](#documentation) +- [Join The Project Team](#join-the-project-team) + + + +## I Have a Question + +> If you want to ask a question, we assume that you have read the available [Documentation](https://docs.clairebot.net). + +Before you ask a question, it is best to search for existing [Issues](https://github.com/Sidpatchy/ClaireBot/issues) that might help you. In case you have found a suitable issue and still need clarification, you can write your question in this issue. It is also advisable to search the internet for answers first. + +If you then still feel the need to ask a question and need clarification, we recommend the following: + +- Open an [Issue](https://github.com/Sidpatchy/ClaireBot/issues/new). +- Provide as much context as you can about what you're running into. +- Provide project and platform versions (nodejs, npm, etc), depending on what seems relevant. + +We will then take care of the issue as soon as possible. + + + +## I Want To Contribute + +> ### Legal Notice +> When contributing to this project, you must agree that you have authored 100% of the content, that you have the necessary rights to the content and that the content you contribute may be provided under the project licence. + +### Reporting Bugs + + +#### Before Submitting a Bug Report + +A good bug report shouldn't leave others needing to chase you up for more information. Therefore, we ask you to investigate carefully, collect information and describe the issue in detail in your report. Please complete the following steps in advance to help us fix any potential bug as fast as possible. + +- Make sure that you are using the latest version. +- Determine if your bug is really a bug and not an error on your side e.g. using incompatible environment components/versions (Make sure that you have read the [documentation](https://docs.clairebot.net). If you are looking for support, you might want to check [this section](#i-have-a-question)). +- To see if other users have experienced (and potentially already solved) the same issue you are having, check if there is not already a bug report existing for your bug or error in the [bug tracker](https://github.com/Sidpatchy/ClaireBot/issues?q=label%3Abug). +- Also make sure to search the internet (including Stack Overflow) to see if users outside of the GitHub community have discussed the issue. +- Collect information about the bug: + - Stack trace (Traceback) + - OS, Platform and Version (Windows, Linux, macOS, x86, ARM) + - Version of the interpreter, compiler, SDK, runtime environment, package manager, depending on what seems relevant. + - Possibly your input and the output + - Can you reliably reproduce the issue? And can you also reproduce it with older versions? + + +#### How Do I Submit a Good Bug Report? + +> You must never report security related issues, vulnerabilities or bugs including sensitive information to the issue tracker, or elsewhere in public. Instead sensitive bugs must be sent by email to . + + +We use GitHub issues to track bugs and errors. If you run into an issue with the project: + +- Open an [Issue](https://github.com/Sidpatchy/ClaireBot/issues/new). (Since we can't be sure at this point whether it is a bug or not, we ask you not to talk about a bug yet and not to label the issue.) +- Explain the behavior you would expect and the actual behavior. +- Please provide as much context as possible and describe the *reproduction steps* that someone else can follow to recreate the issue on their own. This usually includes your code. For good bug reports you should isolate the problem and create a reduced test case. +- Provide the information you collected in the previous section. + +Once it's filed: + +- The project team will label the issue accordingly. +- A team member will try to reproduce the issue with your provided steps. If there are no reproduction steps or no obvious way to reproduce the issue, the team will ask you for those steps and mark the issue as `needs-repro`. Bugs with the `needs-repro` tag will not be addressed until they are reproduced. +- If the team is able to reproduce the issue, it will be marked `needs-fix`, as well as possibly other tags (such as `critical`), and the issue will be left to be [implemented by someone](#your-first-code-contribution). + + + + +### Suggesting Enhancements + +This section guides you through submitting an enhancement suggestion for ClaireBot, **including completely new features and minor improvements to existing functionality**. Following these guidelines will help maintainers and the community to understand your suggestion and find related suggestions. + + +#### Before Submitting an Enhancement + +- Make sure that you are using the latest version. +- Read the [documentation](https://docs.clairebot.net) carefully and find out if the functionality is already covered, maybe by an individual configuration. +- Perform a [search](https://github.com/Sidpatchy/ClaireBot/issues) to see if the enhancement has already been suggested. If it has, add a comment to the existing issue instead of opening a new one. +- Find out whether your idea fits with the scope and aims of the project. It's up to you to make a strong case to convince the project's developers of the merits of this feature. Keep in mind that we prefer features that will be useful to the majority of our users and not just a small subset. Features like these will be prioritized lower. + + +#### How Do I Submit a Good Enhancement Suggestion? + +Enhancement suggestions are tracked as [GitHub issues](https://github.com/Sidpatchy/ClaireBot/issues). + +- Use a **clear and descriptive title** for the issue to identify the suggestion. +- Provide a **step-by-step description of the suggested enhancement** in as many details as possible. +- **Describe the current behavior** and **explain which behavior you expected to see instead** and why. At this point you can also tell which alternatives do not work for you. +- You may want to **include screenshots or screen recordings** which help you demonstrate the steps or point out the part which the suggestion is related to. You can use [LICEcap](https://www.cockos.com/licecap/) to record GIFs on macOS and Windows, and the built-in [screen recorder in GNOME](https://help.gnome.org/users/gnome-help/stable/screen-shot-record.html.en) or [SimpleScreenRecorder](https://github.com/MaartenBaert/ssr) on Linux. +- **Explain why this enhancement would be useful** to most ClaireBot users. You may also want to point out the other projects that solved it better and which could serve as inspiration. + + + +### Your First Code Contribution + + +ClaireBot is primarily developed using IntelliJ IDEA. Other IDEs are acceptable, they are just not documented here due +to the maintainer's unfamiliarity with them. + +#### Setting Up a Working Copy of ClaireBot +You have two main options for setting up ClaireBot. + +##### ClaireBot Docker +The easiest way to set up ClaireBot/ClaireData is to use the Docker container provided at +[Sidpatchy/ClaireBot-Docker](https://github.com/Sidpatchy/ClaireBot-Docker). The repo includes documentation in the +README.md for getting started. It is written with Linux in mind, so if you're on Windows, you'll want to look into +WSL2 or Docker Desktop. + +##### Manually Setting Up ClaireBot + + +## Style Guides +### Commit Messages +We prefer [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/#summary) to keep changelogs tidy: +``` +feat: A new feature +fix: A bug fix +docs: Documentation changes +style: Code formatting or style adjustments (no functional changes) +refactor: Code restructuring without altering behavior +test: Adding or modifying tests +chore: Maintenance tasks or tooling updates +``` + +### Documentation +- Submit pull requests to [Sidpatchy/ClaireBot-docs](https://github.com/Sidpatchy/ClaireBot-Docs) +- Avoid using terms like 'simply', 'easy', and 'just' + - If someone's reading the docs, it's not 'simple' + - See: [https://justsimply.dev/](https://justsimply.dev/) + +## Join The Project Team + + + +## Attribution +This guide is based on the [contributing.md](https://contributing.md/generator)! \ No newline at end of file diff --git a/Writerside/topics/Interface-Standardization-1.md b/Writerside/topics/Interface-Standardization-1.md new file mode 100644 index 0000000..79237c1 --- /dev/null +++ b/Writerside/topics/Interface-Standardization-1.md @@ -0,0 +1,23 @@ +# Interface Standardization and Updates #1 +A place to document various interface changes that need to occur. + +## Issue #1 +ClaireBot currently uses a mix of CamelCase and kebab-case in user-facing resources. This should be standardized +to kebab-case. + +Internal naming will retain CamelCase (variable names, etc.). All user-facing elements need to be transitioned +to kebab-casing. + +## Issue #2 +/help command info should include a link to the relevant documentation webpage. This is blocked pending completion of +the documentation for all commands. + +Will either need to update Robin's implementation of the commands.yml standard, or just pull that implementation into +ClaireBot. + +## Issue #3 +What happens if there is no requests channel??? I'm pretty sure ClaireBot will just throw an error and to the user, +fail completely silently. This is very poor user experience. + +## Issue #4 +/user doesn't do very much right now. More fields should be considered for addition. \ No newline at end of file diff --git a/Writerside/topics/Requests-Channel.md b/Writerside/topics/Requests-Channel.md new file mode 100644 index 0000000..5edb1fe --- /dev/null +++ b/Writerside/topics/Requests-Channel.md @@ -0,0 +1,27 @@ +# Requests Channel + +The requests channel for a server is determined via the following steps: +
    +
  • Getting a Request Channel: +
      +
    • Step 1: Check ClaireData API +
        +
      • Success: Use the configured channel
      • +
      • Failure: Move to Step 2
      • +
      +
    • +
    • Step 2: Search for Default Channel +
        +
      • Look for channel named "requests" +
          +
        • Found: Use this channel
        • +
        • Not Found: Request fails
        • +
        +
      • +
      +
    • +
    +
  • +
+ +If no channel is found, the /request will fail. \ No newline at end of file diff --git a/Writerside/topics/avatar.md b/Writerside/topics/avatar.md new file mode 100644 index 0000000..f1d858d --- /dev/null +++ b/Writerside/topics/avatar.md @@ -0,0 +1,29 @@ +# /avatar + +Command to display a user's profile image. + +## Command + +### Syntax + +```shell +/avatar [optional: user] [optional: globalAvatar] +``` + +### Options + +user (optional) +: The user whose avatar you'd like to display. +
If left blank, ClaireBot will display the avatar of the user who issued the command. + + +globalAvatar (optional) +: If left blank, defaults to true
Options: +- **True**: ClaireBot will display the user's global avatar. +- **False**: ClaireBot will display the user's server-specific avatar. + + + + + + \ No newline at end of file diff --git a/Writerside/topics/config-server.md b/Writerside/topics/config-server.md new file mode 100644 index 0000000..032acfa --- /dev/null +++ b/Writerside/topics/config-server.md @@ -0,0 +1,31 @@ +# /config server + +Opens up the server settings page. + +## Command + +### Syntax + +```shell +/config server +``` + +### Options + +Requests Channel +: Allows server administrators to choose where ClaireBot should post users' requests. Lists out the first 25 channels +in the server's channel list. + +Moderator Messages Channel +: Allows server administrators to choose where ClaireBot should send moderator messages. Lists out the first 25 channels +in the server's channel list. + +Enforce Server Language +: Allows server administrators to force ClaireBot to use the same language as the server's region settings dictate. +

**Options**: +- **True**: ClaireBot will enforce the server's language. +- **False**: ClaireBot will respect the user's preferences and use their preferred language. + + + + \ No newline at end of file diff --git a/Writerside/topics/config-user.md b/Writerside/topics/config-user.md new file mode 100644 index 0000000..f797026 --- /dev/null +++ b/Writerside/topics/config-user.md @@ -0,0 +1,27 @@ +# /config user + +Opens up the user preferences page. + +## Command + +### Syntax + +```shell +/config user +``` + +### Options + +Accent Colour +: For those who hate the colour blue. Allows you to change the accent colour of embeds in ClaireBot's +responses. Options: +- **Select Common Colours**: Allows you to select a list of pre-configured colours. +- **Hexadecimal Entry**: Allows you to enter any hexadecimal (#000000) colour code. + +Language +: For those who are offended by English–or those who prefer a different language. +

This feature isn't yet implemented. It is currently planned for ClaireBot v3.4. + + + + \ No newline at end of file diff --git a/Writerside/topics/config.md b/Writerside/topics/config.md new file mode 100644 index 0000000..1953e96 --- /dev/null +++ b/Writerside/topics/config.md @@ -0,0 +1,26 @@ +# /config + +Interactive method for configuring ClaireBot. Allows for configuring both user and server settings. + +#### See also +- [/config user](config-user.md) +- [/config server](config-server.md) + +## Command + +### Syntax + +```shell +/config [optional: mode] +``` + +### Options + +mode (optional) +: The configuration section you want to ender. Options: +- User (default): Configuration options for users, see: [/config user](config-user.md) +- Server: Configuration options for servers, see: [/config server](config-server.md) + + + + \ No newline at end of file diff --git a/Writerside/topics/help.md b/Writerside/topics/help.md new file mode 100644 index 0000000..eb7ad97 --- /dev/null +++ b/Writerside/topics/help.md @@ -0,0 +1,20 @@ +# /help + +Displays info about a given command. + +## Command + +### Syntax + +```shell +/help [optional: command-name] +``` + +### Options + +command-name (optional) +: If specified ClaireBot will provide details on the specified command. + + + + \ No newline at end of file diff --git a/Writerside/topics/info.md b/Writerside/topics/info.md new file mode 100644 index 0000000..5a82b4e --- /dev/null +++ b/Writerside/topics/info.md @@ -0,0 +1,21 @@ +# /info + +The info command reports several details about ClaireBot, including: +- Where to find help +- How to add ClaireBot to a server +- A link to ClaireBot's source code +- How many servers ClaireBot is a member of +- ClaireBot's currently running version and release date. +- ClaireBot's uptime. + +## Command + +### Syntax + +```shell +/info +``` + + + + \ No newline at end of file diff --git a/Writerside/topics/leaderboard.md b/Writerside/topics/leaderboard.md new file mode 100644 index 0000000..34ecf92 --- /dev/null +++ b/Writerside/topics/leaderboard.md @@ -0,0 +1,23 @@ +# /leaderboard + +8ball is exactly what it sounds like, though it's worth noting that ClaireBot only speaks in truth. Remember kids, +flesh betrays, ClaireBot will not. + +## Command + +### Syntax + +```shell +/leaderboard [optional: global] +``` + +### Options + +global (optional) +: If left blank, defaults to False.
Options: +- **True**: Displays ClaireBot's global leaderboard. +- **False**: Displays ClaireBot's leaderboard for the server the command was executed in. + + + + \ No newline at end of file diff --git a/Writerside/topics/level.md b/Writerside/topics/level.md new file mode 100644 index 0000000..9535152 --- /dev/null +++ b/Writerside/topics/level.md @@ -0,0 +1,22 @@ +# /level + +Displays your ClaireBot level. This is the same thing that would be displayed on the [/leaderboard](leaderboard.md) if +you (or the queried user) are ranked high enough. + +## Command + +### Syntax + +```shell +/8ball [optional: user] +``` + +### Options + +user (optional) +: The user you wish to get the level of. +
If left blank, ClaireBot will report the level of whoever executed the command. + + + + \ No newline at end of file diff --git a/Writerside/topics/openapi.yml/API_Reference.md b/Writerside/topics/openapi.yml/API_Reference.md new file mode 100644 index 0000000..774437f --- /dev/null +++ b/Writerside/topics/openapi.yml/API_Reference.md @@ -0,0 +1,17 @@ +# API Reference + +| Endpoint | Method | Description | Documentation | +|-------------------------------|--------|---------------------|-----------------------------------------| +| ```/api/v1/user``` | GET | Retrieve all users | [Get all users](Get_all_users.md) | +| ```/api/v1/user``` | POST | Create a new user | [Create new user](Create_new_user.md) | +| ```/api/v1/user/{userID}``` | GET | Get user by ID | [Get used by ID](Get_user_by_ID.md) | +| ```/api/v1/user/{userID}``` | PUT | Update user | [Update user](Update_user.md) | +| ```/api/v1/user/{userID}``` | DELETE | Delete user | [Delete user](Delete_user.md) | +| ```/api/v1/guild``` | GET | Retrieve all guilds | [Get all guilds](Get_all_guilds.md) | +| ```/api/v1/guild``` | POST | Create a new guild | [Create new guild](Create_new_guild.md) | +| ```/api/v1/guild/{guildID}``` | GET | Get guild by ID | [Get guild by ID](Get_guild_by_ID.md) | +| ```/api/v1/guild/{guildID}``` | PUT | Update guild | [Update guild](Update_guild.md) | +| ```/api/v1/guild/{guildID}``` | DELETE | Delete guild | [Delete guild](Delete_guild.md) | + +All endpoints require Basic Authentication and accept/return JSON data. + diff --git a/Writerside/topics/openapi.yml/Create_new_guild.md b/Writerside/topics/openapi.yml/Create_new_guild.md new file mode 100644 index 0000000..866a35d --- /dev/null +++ b/Writerside/topics/openapi.yml/Create_new_guild.md @@ -0,0 +1,3 @@ +# Create new guild + + \ No newline at end of file diff --git a/Writerside/topics/openapi.yml/Create_new_user.md b/Writerside/topics/openapi.yml/Create_new_user.md new file mode 100644 index 0000000..7fd446f --- /dev/null +++ b/Writerside/topics/openapi.yml/Create_new_user.md @@ -0,0 +1,3 @@ +# Create new user + + \ No newline at end of file diff --git a/Writerside/topics/openapi.yml/Delete_guild.md b/Writerside/topics/openapi.yml/Delete_guild.md new file mode 100644 index 0000000..e8b4f23 --- /dev/null +++ b/Writerside/topics/openapi.yml/Delete_guild.md @@ -0,0 +1,3 @@ +# Delete guild + + \ No newline at end of file diff --git a/Writerside/topics/openapi.yml/Delete_user.md b/Writerside/topics/openapi.yml/Delete_user.md new file mode 100644 index 0000000..dd92836 --- /dev/null +++ b/Writerside/topics/openapi.yml/Delete_user.md @@ -0,0 +1,3 @@ +# Delete user + + \ No newline at end of file diff --git a/Writerside/topics/openapi.yml/Get_all_guilds.md b/Writerside/topics/openapi.yml/Get_all_guilds.md new file mode 100644 index 0000000..c5ef78c --- /dev/null +++ b/Writerside/topics/openapi.yml/Get_all_guilds.md @@ -0,0 +1,3 @@ +# Get all guilds + + \ No newline at end of file diff --git a/Writerside/topics/openapi.yml/Get_all_users.md b/Writerside/topics/openapi.yml/Get_all_users.md new file mode 100644 index 0000000..e00cbec --- /dev/null +++ b/Writerside/topics/openapi.yml/Get_all_users.md @@ -0,0 +1,3 @@ +# Get all users + + \ No newline at end of file diff --git a/Writerside/topics/openapi.yml/Get_guild_by_ID.md b/Writerside/topics/openapi.yml/Get_guild_by_ID.md new file mode 100644 index 0000000..bb556d6 --- /dev/null +++ b/Writerside/topics/openapi.yml/Get_guild_by_ID.md @@ -0,0 +1,3 @@ +# Get guild by ID + + \ No newline at end of file diff --git a/Writerside/topics/openapi.yml/Get_user_by_ID.md b/Writerside/topics/openapi.yml/Get_user_by_ID.md new file mode 100644 index 0000000..21d86aa --- /dev/null +++ b/Writerside/topics/openapi.yml/Get_user_by_ID.md @@ -0,0 +1,3 @@ +# Get user by ID + + \ No newline at end of file diff --git a/Writerside/topics/openapi.yml/Update_guild.md b/Writerside/topics/openapi.yml/Update_guild.md new file mode 100644 index 0000000..1e18ff7 --- /dev/null +++ b/Writerside/topics/openapi.yml/Update_guild.md @@ -0,0 +1,3 @@ +# Update guild + + \ No newline at end of file diff --git a/Writerside/topics/openapi.yml/Update_user.md b/Writerside/topics/openapi.yml/Update_user.md new file mode 100644 index 0000000..6123da2 --- /dev/null +++ b/Writerside/topics/openapi.yml/Update_user.md @@ -0,0 +1,3 @@ +# Update user + + \ No newline at end of file diff --git a/Writerside/topics/openapi.yml/openapi.yml b/Writerside/topics/openapi.yml/openapi.yml new file mode 100644 index 0000000..1b3bdc1 --- /dev/null +++ b/Writerside/topics/openapi.yml/openapi.yml @@ -0,0 +1,247 @@ +openapi: 3.0.0 +info: + title: ClaireData API + version: 1.0.0 + description: Spring Boot REST API for managing users and guilds + +tags: + - name: User Controller + description: Endpoints for user management + - name: Guild Controller + description: Endpoints for guild management + +paths: + /api/v1/user: + get: + tags: + - User Controller + operationId: getAllUsers + summary: Get all users + responses: + '200': + description: List of all users retrieved successfully + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/User' + + post: + tags: + - User Controller + operationId: addUser + summary: Create new user + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/User' + responses: + '200': + description: User created successfully + + /api/v1/user/{userID}: + get: + tags: + - User Controller + operationId: getUserById + summary: Get user by ID + parameters: + - name: userID + in: path + required: true + schema: + type: string + responses: + '200': + description: User found + content: + application/json: + schema: + $ref: '#/components/schemas/User' + '404': + description: User not found + + put: + tags: + - User Controller + operationId: updateUser + summary: Update user + parameters: + - name: userID + in: path + required: true + schema: + type: string + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/User' + responses: + '200': + description: User updated successfully + + delete: + tags: + - User Controller + operationId: deleteUserById + summary: Delete user + parameters: + - name: userID + in: path + required: true + schema: + type: string + responses: + '200': + description: User deleted successfully + + /api/v1/guild: + get: + tags: + - Guild Controller + operationId: getAllGuilds + summary: Get all guilds + responses: + '200': + description: List of all guilds retrieved successfully + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Guild' + + post: + tags: + - Guild Controller + operationId: addGuild + summary: Create new guild + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/Guild' + responses: + '200': + description: Guild created successfully + + /api/v1/guild/{guildID}: + get: + tags: + - Guild Controller + operationId: getGuildById + summary: Get guild by ID + parameters: + - name: guildID + in: path + required: true + schema: + type: string + responses: + '200': + description: Guild found + content: + application/json: + schema: + $ref: '#/components/schemas/Guild' + '404': + description: Guild not found + + put: + tags: + - Guild Controller + operationId: updateGuild + summary: Update guild + parameters: + - name: guildID + in: path + required: true + schema: + type: string + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/Guild' + responses: + '200': + description: Guild updated successfully + + delete: + tags: + - Guild Controller + operationId: deleteGuildById + summary: Delete guild + parameters: + - name: guildID + in: path + required: true + schema: + type: string + responses: + '200': + description: Guild deleted successfully + +components: + schemas: + User: + type: object + required: + - userID + properties: + userID: + type: string + accentColour: + type: string + description: Hex color code with '#' prefix + example: "#FF0000" + language: + type: string + description: ISO 639-3 language code + example: "eng" + pointsGuildID: + type: array + items: + type: string + pointsMessages: + type: array + items: + type: integer + deprecated: true + pointsVoiceChat: + type: array + items: + type: integer + deprecated: true + + Guild: + type: object + required: + - guildID + properties: + guildID: + type: string + requestsChannelID: + type: string + description: Discord channel ID for requests + moderatorMessagesChannelID: + type: string + description: Discord channel ID for moderator messages + enforceServerLanguage: + type: boolean + description: Whether to enforce server language settings + + securitySchemes: + BasicAuth: + type: http + scheme: basic + +security: + - BasicAuth: [] diff --git a/Writerside/topics/overview.md b/Writerside/topics/overview.md new file mode 100644 index 0000000..3d759d5 --- /dev/null +++ b/Writerside/topics/overview.md @@ -0,0 +1,70 @@ +# Overview + +> This documentation site is currently a work in progress. While efforts have been made to ensure accuracy, some +> details may be incomplete or subject to change. Critical information should be verified independently. Once the +> initial version is finalized, the documentation will be open-sourced and available at +> [Sidpatchy/ClaireBot-Docs](https://github.com/Sidpatchy/ClaireBot-Docs). +{style="warning"} + +Documentation for ClaireBot, ClaireData, and ClaireBot Docker all in one place, authored using Jetbrains Writerside. + +To contribute to these docs, or ClaireBot in general, please see: [Contributing](Contributing-guide.md) + +## What is ClaireBot? + +ClaireBot is a Discord bot written in Java with a focus on ease-of-use and beauty. + +### Background +ClaireBot 1 and 2 were initially built for a group of friends to use, and it was only used on a handful of servers. + +As time went on, I grew more and more interested in using ClaireBot elsewhere, in my public servers, so I rewrote bits +and pieces of her to work in different servers, and, eventually had rewritten nearly everything. This was ClaireBot 2. + +Over time, I wanted more, and more. But ClaireBot 2 was still using an architecture very similar to the original. It was +a pain to continue developing new features as I'd find myself tripping over technical debt nearly constantly, but I +persisted. It wasn't worth taking the time to rewrite ALL of ClaireBot, at least that's what I had told my self. + +Eventually, Discord.py was discontinued, this forced my hand. If I wanted to keep developing ClaireBot, I would have +to rewrite it from the ground up. Thus, ClaireBot 3 was born. + +It took me over a year and a half to finally finish the port, because procrastination is strong. That leads us to today. + +### Present Day + +ClaireBot is currently developed primarily in Java, some components have been and are in Kotlin, but this is currently +a very minor portion of the codebase. + +ClaireBot uses the Javacord library for interacting with Discord. Without getting sidetracked too much, this is subject +to change as Javacord's development has greatly slowed (see: [Beyond Javacord](Beyond-Javacord.md)). + +At the time of writing ClaireBot (v3.3.2) has the following commands: + +| Command | Description | Documentation | +|--------------|-------------------------------------------------------------------------------------------------|------------------------| +| /8ball | Exactly what it sounds like, though it's worth noting that ClaireBot only speaks in truth. | [Docs](8ball.md) | +| /avatar | Gets a user's profile image. | [Docs](avatar.md) | +| /help | How use ClaireBot??? | [Docs](help.md) | +| /info | Displays various nuggets of information (uptime, version, support info, etc.). | [Docs](info.md) | +| /leaderboard | Provides details on ClaireBot's (currently rudimentary) implementation of a Leaderboard system. | [Docs](leaderboard.md) | +| /level | Displays your ClaireBot level (tied together with the leaderboard system). | [Docs](level.md) | +| /poll | Creates a poll. Leave arguments empty for an interactive popup. | [Docs](poll.md) | +| /quote | Picks a random message from the channel the command is executed in and displays it. | [Docs](quote.md) | +| /request | Same system as /poll, but directs it to the server's requests channel. | [Docs](poll.md) | +| /server | Reports various bits of info about the (optionally specified) server / guild. | [Docs](server.md) | +| /user | Reports various bits of info about the specified user. | [Docs](user.md) | +| /config | Allows for modifying user or server preferences. | [Docs](config.md) | +| /santa | Tool for organizing secret santa gift exchanges. | [Docs](santa.md) | + +## Glossary + +ClaireBot +: ClaireBot refers to both the ClaireBot project as a whole, and the Discord bot component. +

The Discord bot component is the key piece of software that users interact with. It is what communicates with +the Discord API and responds to user calls. + +ClaireData +: ClaireData is the piece of software used to store long-term, persistent data. It serves a REST API that ClaireBot +interfaces with. ClaireData uses Postgres as a database. + +ClaireBot Docker +: A series of Docker containers, and a docker-compose.yml for quickly and easily setting up a working copy of ClaireBot. \ No newline at end of file diff --git a/Writerside/topics/poll.md b/Writerside/topics/poll.md new file mode 100644 index 0000000..d01c506 --- /dev/null +++ b/Writerside/topics/poll.md @@ -0,0 +1,42 @@ +# /poll & /request + +Allows for creating interactive, inline polls and requests. + +Polls and requests are effectively the same system. In fact, they use identical backend code. The key difference is that +Polls are inserted into the channel where the command is run, and requests are sent to the server's requests channel. + +For more info on how the requests channel is selected, please see: [_Requests Channel_](Requests-Channel.md) + +## Command + +### Syntax + +```shell +/poll [question] [optional: allow-multiple-choices] [optional: queries] +/poll +``` +```shell +/request [question] [optional: allow-multiple-choices] [optional: queries] +/request +``` + +### Options + +question +: The main question of your poll. + +allow-multiple-choices (optional) +: Determines whether Clairebot will allow users to vote for multiple options. + +queries (optional) +: Allows for specifying multiple choice answers. You can have up to 9 choices. + +When run with no options +: If none of the previous options are specified, ClaireBot will send your Discord client a popup window allowing you to +create a poll / request in a more graphical manner. +

This is better for situations where you have a lot to type out, or when you want to avoid fiddling +with the various options. + + + + \ No newline at end of file diff --git a/Writerside/topics/quote.md b/Writerside/topics/quote.md new file mode 100644 index 0000000..85e920f --- /dev/null +++ b/Writerside/topics/quote.md @@ -0,0 +1,36 @@ +# /quote + +Picks a random message from the channel the command is executed in and displays it. + +On Discord desktop, you can click the author's name to jump to the original message. + +On desktop and mobile, you can select the "View Original" button to jump to the original message. +ClaireBot will send an ephemeral message which contains a link to the original. + +### Known Issues + +/quote is ungodly levels of inefficient reference [ClaireBot #4](https://github.com/Sidpatchy/ClaireBot/issues/4) to +track this issue. + +Functions by pulling down (up-to) 50,000 of the most recently posted messages in the channel the +command was executed in. After this, the bot will attempt to pick a random message from the selected user. + +All this is to say it is very slow. Be patient. + +## Command + +### Syntax + +```shell +/quote [optional: user] +``` + +### Options + +user (optional) +: The user you wish to quote. +
If left blank, ClaireBot will quote whoever executed the command. + + + + \ No newline at end of file diff --git a/Writerside/topics/santa.md b/Writerside/topics/santa.md new file mode 100644 index 0000000..12d5eb6 --- /dev/null +++ b/Writerside/topics/santa.md @@ -0,0 +1,37 @@ +# /santa + +SecretClaire is *the* all-in-one solution for facilitating secret santa-style gift exchanges on Discord. +Use `/help santa` or `/santa` to get started! + +SecretClaire which was originally a standalone project that got ported into ClaireBot +(see: [Sidpatchy/SecretClaire](https://github.com/Sidpatchy/SecretClaire)). + +## Command + +### Syntax + +```shell +/santa [role] +``` + +### Options + +role +: In the case of SecretClaire the sole purpose of using a role is to allow for quickly and easily grouping together +users. + +## Example Organizer Messages +![](https://github.com/Sidpatchy/SecretClaire/blob/master/img/screenshot_1.png?raw=true) + +![](https://github.com/Sidpatchy/SecretClaire/blob/master/img/screenshot_6.png?raw=true) + +![](https://github.com/Sidpatchy/SecretClaire/blob/master/img/screenshot_7.png?raw=true) + +## Example gift-giver messages +![](https://github.com/Sidpatchy/SecretClaire/blob/master/img/screenshot_2.png?raw=true) + +![](https://github.com/Sidpatchy/SecretClaire/blob/master/img/screenshot_3.png?raw=true) + + + + \ No newline at end of file diff --git a/Writerside/topics/server.md b/Writerside/topics/server.md new file mode 100644 index 0000000..988573d --- /dev/null +++ b/Writerside/topics/server.md @@ -0,0 +1,28 @@ +# /server + +Reports various bits of info about the (optionally specified) server / guild, including: +- Owner +- Creation Date +- Number of Roles +- Number of Members +- Number of Channels + - Categories + - Text Channels + - Voice Channels + +## Command + +### Syntax + +```shell +/server [optional: guildID] +``` + +### Options + +guildID (optional) +: The ID of the guild you wish to gather info on. ClaireBot must be a member of the server. + + + + \ No newline at end of file diff --git a/Writerside/topics/user.md b/Writerside/topics/user.md new file mode 100644 index 0000000..0974e73 --- /dev/null +++ b/Writerside/topics/user.md @@ -0,0 +1,23 @@ +# /user + +Reports various bits of info about a user, including: +- Discord ID +- Account Creation Date + +## Command + +### Syntax + +```shell +/user [optional: user] +``` + +### Options + +user (optional) +: The user you wish to get the details of. +
If left blank, ClaireBot will report the details of whoever executed the command. + + + + \ No newline at end of file diff --git a/Writerside/v.list b/Writerside/v.list new file mode 100644 index 0000000..2d12cb3 --- /dev/null +++ b/Writerside/v.list @@ -0,0 +1,5 @@ + + + + + diff --git a/Writerside/writerside.cfg b/Writerside/writerside.cfg new file mode 100644 index 0000000..ff2222a --- /dev/null +++ b/Writerside/writerside.cfg @@ -0,0 +1,11 @@ + + + + + + + + + + \ No newline at end of file From 820a6673b8332b837f675ee42dedcadafb976d3e Mon Sep 17 00:00:00 2001 From: Sidpatchy Date: Tue, 14 Oct 2025 09:39:46 -0600 Subject: [PATCH 34/64] docs: fix openapi.yml path --- Writerside/topics/openapi.yml/Create_new_guild.md | 2 +- Writerside/topics/openapi.yml/Create_new_user.md | 2 +- Writerside/topics/openapi.yml/Delete_guild.md | 2 +- Writerside/topics/openapi.yml/Delete_user.md | 2 +- Writerside/topics/openapi.yml/Get_all_guilds.md | 2 +- Writerside/topics/openapi.yml/Get_guild_by_ID.md | 2 +- Writerside/topics/openapi.yml/Get_user_by_ID.md | 2 +- Writerside/topics/openapi.yml/Update_guild.md | 2 +- Writerside/topics/openapi.yml/Update_user.md | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Writerside/topics/openapi.yml/Create_new_guild.md b/Writerside/topics/openapi.yml/Create_new_guild.md index 866a35d..47b127f 100644 --- a/Writerside/topics/openapi.yml/Create_new_guild.md +++ b/Writerside/topics/openapi.yml/Create_new_guild.md @@ -1,3 +1,3 @@ # Create new guild - \ No newline at end of file + \ No newline at end of file diff --git a/Writerside/topics/openapi.yml/Create_new_user.md b/Writerside/topics/openapi.yml/Create_new_user.md index 7fd446f..b71aeb1 100644 --- a/Writerside/topics/openapi.yml/Create_new_user.md +++ b/Writerside/topics/openapi.yml/Create_new_user.md @@ -1,3 +1,3 @@ # Create new user - \ No newline at end of file + \ No newline at end of file diff --git a/Writerside/topics/openapi.yml/Delete_guild.md b/Writerside/topics/openapi.yml/Delete_guild.md index e8b4f23..55d3db6 100644 --- a/Writerside/topics/openapi.yml/Delete_guild.md +++ b/Writerside/topics/openapi.yml/Delete_guild.md @@ -1,3 +1,3 @@ # Delete guild - \ No newline at end of file + \ No newline at end of file diff --git a/Writerside/topics/openapi.yml/Delete_user.md b/Writerside/topics/openapi.yml/Delete_user.md index dd92836..f989284 100644 --- a/Writerside/topics/openapi.yml/Delete_user.md +++ b/Writerside/topics/openapi.yml/Delete_user.md @@ -1,3 +1,3 @@ # Delete user - \ No newline at end of file + \ No newline at end of file diff --git a/Writerside/topics/openapi.yml/Get_all_guilds.md b/Writerside/topics/openapi.yml/Get_all_guilds.md index c5ef78c..0ce3f7c 100644 --- a/Writerside/topics/openapi.yml/Get_all_guilds.md +++ b/Writerside/topics/openapi.yml/Get_all_guilds.md @@ -1,3 +1,3 @@ # Get all guilds - \ No newline at end of file + \ No newline at end of file diff --git a/Writerside/topics/openapi.yml/Get_guild_by_ID.md b/Writerside/topics/openapi.yml/Get_guild_by_ID.md index bb556d6..6c4e8ac 100644 --- a/Writerside/topics/openapi.yml/Get_guild_by_ID.md +++ b/Writerside/topics/openapi.yml/Get_guild_by_ID.md @@ -1,3 +1,3 @@ # Get guild by ID - \ No newline at end of file + \ No newline at end of file diff --git a/Writerside/topics/openapi.yml/Get_user_by_ID.md b/Writerside/topics/openapi.yml/Get_user_by_ID.md index 21d86aa..e855ce5 100644 --- a/Writerside/topics/openapi.yml/Get_user_by_ID.md +++ b/Writerside/topics/openapi.yml/Get_user_by_ID.md @@ -1,3 +1,3 @@ # Get user by ID - \ No newline at end of file + \ No newline at end of file diff --git a/Writerside/topics/openapi.yml/Update_guild.md b/Writerside/topics/openapi.yml/Update_guild.md index 1e18ff7..4d3e924 100644 --- a/Writerside/topics/openapi.yml/Update_guild.md +++ b/Writerside/topics/openapi.yml/Update_guild.md @@ -1,3 +1,3 @@ # Update guild - \ No newline at end of file + \ No newline at end of file diff --git a/Writerside/topics/openapi.yml/Update_user.md b/Writerside/topics/openapi.yml/Update_user.md index 6123da2..3a7e726 100644 --- a/Writerside/topics/openapi.yml/Update_user.md +++ b/Writerside/topics/openapi.yml/Update_user.md @@ -1,3 +1,3 @@ # Update user - \ No newline at end of file + \ No newline at end of file From aa87a8f1e05456b9a4679d4e494ce6b4a81e4d66 Mon Sep 17 00:00:00 2001 From: Sidpatchy Date: Tue, 14 Oct 2025 10:14:36 -0600 Subject: [PATCH 35/64] refactor: target JDK 24, bump deps, & replace SnakeYAML Not tested besides ensuring it builds. Since we're currently just using Kotlin for its data classes it may just be worth replacing it with a Java record class. --- build.gradle | 27 +++++++++--------- gradle/wrapper/gradle-wrapper.properties | 2 +- .../com/sidpatchy/clairebot/API/APIUser.java | 4 +-- .../Util/Leveling/LevelingTools.java | 28 +++++++++---------- 4 files changed, 29 insertions(+), 32 deletions(-) diff --git a/build.gradle b/build.gradle index b9aa486..8b2cbae 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,6 @@ plugins { - id 'com.github.johnrengelman.shadow' version '8.1.1' - id 'org.jetbrains.kotlin.jvm' version '1.9.22' + id 'com.gradleup.shadow' version '9.2.2' + id 'org.jetbrains.kotlin.jvm' version '2.2.20' id 'java' } @@ -24,7 +24,7 @@ processResources { } kotlin { - jvmToolchain(17) + jvmToolchain(24) } repositories { @@ -35,24 +35,23 @@ repositories { } dependencies { - testImplementation 'org.junit.jupiter:junit-jupiter-api:5.10.1' - testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.10.1' + testImplementation 'org.junit.jupiter:junit-jupiter-api:6.0.0' + testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:6.0.0' implementation 'com.github.Sidpatchy:Robin:2.2.4' implementation 'org.javacord:javacord:3.8.0' - implementation 'org.apache.logging.log4j:log4j-api:2.22.1' - implementation 'org.apache.logging.log4j:log4j-core:2.22.1' - implementation 'org.apache.logging.log4j:log4j-slf4j-impl:2.22.1' - implementation 'org.apache.commons:commons-lang3:3.14.0' + implementation 'org.apache.logging.log4j:log4j-api:2.25.2' + implementation 'org.apache.logging.log4j:log4j-core:2.25.2' + implementation 'org.apache.logging.log4j:log4j-slf4j-impl:2.25.2' + implementation 'org.apache.commons:commons-lang3:3.19.0' - implementation 'org.jetbrains.kotlin:kotlin-stdlib:1.9.22' + implementation 'org.jetbrains.kotlin:kotlin-stdlib:2.2.20' - implementation 'org.yaml:snakeyaml:2.0' - implementation 'org.json:json:20231013' - implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.18.0' - implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.18.0' + implementation 'org.json:json:20250517' + implementation 'tools.jackson.dataformat:jackson-dataformat-xml:3.0.0' + implementation 'tools.jackson.dataformat:jackson-dataformat-yaml:3.0.0' } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 48c0a02..d706aba 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/src/main/java/com/sidpatchy/clairebot/API/APIUser.java b/src/main/java/com/sidpatchy/clairebot/API/APIUser.java index 5331c7c..5193591 100644 --- a/src/main/java/com/sidpatchy/clairebot/API/APIUser.java +++ b/src/main/java/com/sidpatchy/clairebot/API/APIUser.java @@ -1,7 +1,7 @@ package com.sidpatchy.clairebot.API; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.node.ObjectNode; +import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.node.ObjectNode; import com.sidpatchy.Robin.File.RobinConfiguration; import com.sidpatchy.clairebot.Main; import com.sidpatchy.clairebot.Util.Leveling.LevelingTools; diff --git a/src/main/java/com/sidpatchy/clairebot/Util/Leveling/LevelingTools.java b/src/main/java/com/sidpatchy/clairebot/Util/Leveling/LevelingTools.java index d9586b4..8f82287 100644 --- a/src/main/java/com/sidpatchy/clairebot/Util/Leveling/LevelingTools.java +++ b/src/main/java/com/sidpatchy/clairebot/Util/Leveling/LevelingTools.java @@ -1,12 +1,12 @@ package com.sidpatchy.clairebot.Util.Leveling; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.node.ObjectNode; import com.sidpatchy.clairebot.API.APIUser; import com.sidpatchy.clairebot.Main; import org.apache.logging.log4j.Logger; -import org.yaml.snakeyaml.Yaml; +import tools.jackson.core.type.TypeReference; +import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.node.ObjectNode; +import tools.jackson.dataformat.yaml.YAMLMapper; import java.io.IOException; import java.util.ArrayList; @@ -21,8 +21,11 @@ public static HashMap rankUsers(String guildID) throws IOExcept APIUser apiUser = new APIUser(""); // Load the YAML data from an InputStream into a Java object - Yaml yaml = new Yaml(); - List> users = yaml.load(apiUser.getALLUsers()); + YAMLMapper yamlMapper = new YAMLMapper(); + List> users = yamlMapper.readValue( + apiUser.getALLUsers(), + new TypeReference>>() {} + ); // Iterate over each user and calculate their total points HashMap userPoints = new HashMap<>(); @@ -138,15 +141,10 @@ private static Map parseJsonArray2(List jsonArray) { result.put("global", 0); } else { // Otherwise, parse the JSON string and add it to the result list - try { - // Parse the JSON string into a Map - Map map = mapper.readValue(json, new TypeReference>() {}); - // Add all entries from the map to the result - result.putAll(map); - } catch (IOException e) { - // Handle the exception - e.printStackTrace(); - } + // Parse the JSON string into a Map + Map map = mapper.readValue(json, new TypeReference>() {}); + // Add all entries from the map to the result + result.putAll(map); } } return result; From a7ee79d3697a02c2f22cf91f0c620f03bebf328f Mon Sep 17 00:00:00 2001 From: Sidpatchy Date: Tue, 14 Oct 2025 10:25:18 -0600 Subject: [PATCH 36/64] refactor: move to JDK 25 & replace Kotlin data class with Java record Kotlin disabled until JDK 25 support is added, might just leave it removed in favour of pure Java. --- build.gradle | 4 +- .../clairebot/Lang/ContextManager.java | 61 +++++++++++++++++++ .../clairebot/Lang/ContextManager.kt | 45 -------------- .../clairebot/Lang/LanguageManager.java | 8 +-- .../clairebot/Lang/PlaceholderHandler.java | 30 ++++----- 5 files changed, 82 insertions(+), 66 deletions(-) create mode 100644 src/main/java/com/sidpatchy/clairebot/Lang/ContextManager.java delete mode 100644 src/main/java/com/sidpatchy/clairebot/Lang/ContextManager.kt diff --git a/build.gradle b/build.gradle index 8b2cbae..56342fb 100644 --- a/build.gradle +++ b/build.gradle @@ -24,7 +24,7 @@ processResources { } kotlin { - jvmToolchain(24) + jvmToolchain(25) } repositories { @@ -47,7 +47,7 @@ dependencies { implementation 'org.apache.logging.log4j:log4j-slf4j-impl:2.25.2' implementation 'org.apache.commons:commons-lang3:3.19.0' - implementation 'org.jetbrains.kotlin:kotlin-stdlib:2.2.20' +// implementation 'org.jetbrains.kotlin:kotlin-stdlib:2.2.20' TODO this can be considered for re-addition once JDK 25 support is added implementation 'org.json:json:20250517' implementation 'tools.jackson.dataformat:jackson-dataformat-xml:3.0.0' diff --git a/src/main/java/com/sidpatchy/clairebot/Lang/ContextManager.java b/src/main/java/com/sidpatchy/clairebot/Lang/ContextManager.java new file mode 100644 index 0000000..5f1acc4 --- /dev/null +++ b/src/main/java/com/sidpatchy/clairebot/Lang/ContextManager.java @@ -0,0 +1,61 @@ +package com.sidpatchy.clairebot.Lang; + +import org.javacord.api.entity.channel.TextChannel; +import org.javacord.api.entity.message.Message; +import org.javacord.api.entity.server.Server; +import org.javacord.api.entity.user.User; + +import java.util.HashMap; +import java.util.Map; + +public record ContextManager( + Server server, + TextChannel channel, + User author, + User user, + Message message, + Map dynamicData +) { + // Enum to define known context types + public enum ContextType { + POLL, + SANTA, + GENERIC + } + + // Compact constructor with default empty map + public ContextManager { + if (dynamicData == null) { + dynamicData = new HashMap<>(); + } + } + + // Alternative constructor without dynamicData parameter + public ContextManager(Server server, TextChannel channel, User author, + User user, Message message) { + this(server, channel, author, user, message, new HashMap<>()); + } + + // Add dynamic data with type safety + public void addData(ContextType type, String key, Object value) { + dynamicData.put(type.name().toLowerCase() + "." + key, value); + } + + // Get dynamic data with type safety + @SuppressWarnings("unchecked") + public T getData(ContextType type, String key) { + return (T) dynamicData.get(type.name().toLowerCase() + "." + key); + } + + // Helper functions for specific context types + public void addPollData(String pollId, String question) { + addData(ContextType.POLL, "id", pollId); + addData(ContextType.POLL, "question", question); + } + + public void addSantaData(User giftee, String theme, String rules) { + addData(ContextType.SANTA, "giftee", giftee); + addData(ContextType.SANTA, "theme", theme); + addData(ContextType.SANTA, "rules", rules); + } +} diff --git a/src/main/java/com/sidpatchy/clairebot/Lang/ContextManager.kt b/src/main/java/com/sidpatchy/clairebot/Lang/ContextManager.kt deleted file mode 100644 index a6fcd84..0000000 --- a/src/main/java/com/sidpatchy/clairebot/Lang/ContextManager.kt +++ /dev/null @@ -1,45 +0,0 @@ -package com.sidpatchy.clairebot.Lang - -import org.javacord.api.entity.channel.TextChannel -import org.javacord.api.entity.message.Message -import org.javacord.api.entity.server.Server -import org.javacord.api.entity.user.User - -data class ContextManager( - val server: Server?, - val channel: TextChannel?, - val author: User?, - val user: User?, - val message: Message?, - private val dynamicData: MutableMap = mutableMapOf() -) { - // Enum to define known context types - enum class ContextType { - POLL, - SANTA, - GENERIC - } - - // Add dynamic data with type safety - fun addData(type: ContextType, key: String, value: Any) { - dynamicData["${type.name.lowercase()}.$key"] = value - } - - // Get dynamic data with type safety - @Suppress("UNCHECKED_CAST") - fun getData(type: ContextType, key: String): T? { - return dynamicData["${type.name.lowercase()}.$key"] as? T - } - - // Helper functions for specific context types - fun addPollData(pollId: String, question: String) { - addData(ContextType.POLL, "id", pollId) - addData(ContextType.POLL, "question", question) - } - - fun addSantaData(giftee: User, theme: String, rules: String) { - addData(ContextType.SANTA, "giftee", giftee) - addData(ContextType.SANTA, "theme", theme) - addData(ContextType.SANTA, "rules", rules) - } -} \ No newline at end of file diff --git a/src/main/java/com/sidpatchy/clairebot/Lang/LanguageManager.java b/src/main/java/com/sidpatchy/clairebot/Lang/LanguageManager.java index 62eb9a8..b9a26c9 100644 --- a/src/main/java/com/sidpatchy/clairebot/Lang/LanguageManager.java +++ b/src/main/java/com/sidpatchy/clairebot/Lang/LanguageManager.java @@ -40,8 +40,8 @@ public LanguageManager(String pathToLanguageFiles, this.context = context; this.fallbackLocale = fallbackLocale; - this.server = context.getServer(); - this.user = context.getUser(); + this.server = context.server(); + this.user = context.user(); this.placeholderHandler = new PlaceholderHandler(context); } @@ -62,8 +62,8 @@ public LanguageManager(Locale fallbackLocale, ContextManager context) { this.context = context; this.fallbackLocale = fallbackLocale; - this.server = context.getServer(); - this.user = context.getUser(); + this.server = context.server(); + this.user = context.user(); this.placeholderHandler = new PlaceholderHandler(context); } diff --git a/src/main/java/com/sidpatchy/clairebot/Lang/PlaceholderHandler.java b/src/main/java/com/sidpatchy/clairebot/Lang/PlaceholderHandler.java index a6828dd..ccd3c9c 100644 --- a/src/main/java/com/sidpatchy/clairebot/Lang/PlaceholderHandler.java +++ b/src/main/java/com/sidpatchy/clairebot/Lang/PlaceholderHandler.java @@ -53,27 +53,27 @@ private Map initializePlaceholders() { // Server placeholders entry("cb.server.name", () -> - context.getServer() != null ? context.getServer().getName() : ""), + context.server() != null ? context.server().getName() : ""), entry("cb.server.id", () -> - context.getServer() != null ? context.getServer().getIdAsString() : ""), + context.server() != null ? context.server().getIdAsString() : ""), // User placeholders entry("cb.user.name", () -> - context.getUser() != null ? context.getUser().getName() : ""), + context.user() != null ? context.user().getName() : ""), entry("cb.user.id", () -> - context.getUser() != null ? context.getUser().getIdAsString() : ""), + context.user() != null ? context.user().getIdAsString() : ""), entry("cb.user.id.mentiontag", () -> - context.getUser() != null ? "<@" + context.getUser().getIdAsString() + ">" : ""), + context.user() != null ? "<@" + context.user().getIdAsString() + ">" : ""), entry("cb.user.id.accentcolour", () -> - String.valueOf(Main.getColor(Objects.requireNonNull(context.getUser()).getIdAsString()))), + String.valueOf(Main.getColor(Objects.requireNonNull(context.user()).getIdAsString()))), entry("cb.user.id.displayname.server", () -> { - org.javacord.api.entity.server.Server server = context.getServer(); - org.javacord.api.entity.user.User author = context.getAuthor(); + org.javacord.api.entity.server.Server server = context.server(); + org.javacord.api.entity.user.User author = context.author(); if (author != null) { return (server != null) ? author.getDisplayName(server) : author.getName(); } - org.javacord.api.entity.user.User user = context.getUser(); + org.javacord.api.entity.user.User user = context.user(); if (user != null) { return (server != null) ? user.getDisplayName(server) : user.getName(); } @@ -83,26 +83,26 @@ private Map initializePlaceholders() { // Author placeholders entry("cb.author.name", () -> - context.getAuthor() != null ? context.getAuthor().getName() : ""), + context.author() != null ? context.author().getName() : ""), entry("cb.author.id", () -> - context.getAuthor() != null ? context.getAuthor().getIdAsString() : ""), + context.author() != null ? context.author().getIdAsString() : ""), // Channel placeholders entry("cb.channel.name", () -> - Optional.ofNullable(context.getChannel()) + Optional.ofNullable(context.channel()) .flatMap(Channel::asServerChannel) .map(ServerChannel::getName) .orElse("NOT FOUND")), entry("cb.channel.id", () -> - context.getChannel() != null ? context.getChannel().getIdAsString() : ""), + context.channel() != null ? context.channel().getIdAsString() : ""), entry("cb.channel.id.mentiontag", () -> - context.getChannel() != null ? "<#" + context.getChannel().getIdAsString() + ">" : ""), + context.channel() != null ? "<#" + context.channel().getIdAsString() + ">" : ""), // Command placeholders entry("cb.commandname", () -> String.valueOf(Objects.requireNonNull(context.getData(ContextManager.ContextType.GENERIC, "commandname")))), entry("cb.user.id.username", () -> - Optional.ofNullable(context.getAuthor()) + Optional.ofNullable(context.author()) .map(org.javacord.api.entity.user.User::getDiscriminatedName) .orElseGet(() -> { Object v = context.getData(ContextManager.ContextType.GENERIC, "user.id.username"); From 0d90a0226f7486dba34e1477d5ffa47eed9d08c8 Mon Sep 17 00:00:00 2001 From: Sidpatchy Date: Tue, 14 Oct 2025 11:02:15 -0600 Subject: [PATCH 37/64] refactor: update build.gradle to remove warnings about gradle 10 --- build.gradle | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/build.gradle b/build.gradle index 56342fb..0148d42 100644 --- a/build.gradle +++ b/build.gradle @@ -5,14 +5,16 @@ plugins { } jar { - manifest.attributes( - 'Main-Class': 'com.sidpatchy.clairebot.Main', - 'Multi-Release': 'true' - ) + manifest { + attributes( + 'Main-Class': 'com.sidpatchy.clairebot.Main', + 'Multi-Release': 'true' + ) + } } -group 'com.sidpatchy' -version '3.4.0-SNAPSHOT' +group = 'com.sidpatchy' +version = '3.4.0-SNAPSHOT' processResources { filesMatching('**/build.properties') { @@ -30,8 +32,8 @@ kotlin { repositories { mavenLocal() mavenCentral() - maven { url 'https://m2.dv8tion.net/releases' } - maven { url 'https://jitpack.io' } + maven { url = 'https://m2.dv8tion.net/releases' } + maven { url = 'https://jitpack.io' } } dependencies { From 3243ff6a29967388b66aff6e31d28643ec963a5f Mon Sep 17 00:00:00 2001 From: Sidpatchy Date: Tue, 14 Oct 2025 13:46:03 -0600 Subject: [PATCH 38/64] chore(release): 3.4.0-alpha.1 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 0148d42..3108581 100644 --- a/build.gradle +++ b/build.gradle @@ -14,7 +14,7 @@ jar { } group = 'com.sidpatchy' -version = '3.4.0-SNAPSHOT' +version = '3.4.0-alpha.1' processResources { filesMatching('**/build.properties') { From d07309797b5f5fe2377bb3b845fb59978013782d Mon Sep 17 00:00:00 2001 From: Sidpatchy Date: Tue, 14 Oct 2025 15:41:28 -0600 Subject: [PATCH 39/64] fix: update robin & log4j2 config Updates to Robin 2.2.5 which does not use SnakeYAML. Bot should be runnable now. --- build.gradle | 2 +- src/main/java/com/sidpatchy/clairebot/Main.java | 2 +- src/main/resources/log4j2.xml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/build.gradle b/build.gradle index 3108581..5b9ef0c 100644 --- a/build.gradle +++ b/build.gradle @@ -40,7 +40,7 @@ dependencies { testImplementation 'org.junit.jupiter:junit-jupiter-api:6.0.0' testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:6.0.0' - implementation 'com.github.Sidpatchy:Robin:2.2.4' + implementation 'com.github.Sidpatchy:Robin:2.2.5' implementation 'org.javacord:javacord:3.8.0' diff --git a/src/main/java/com/sidpatchy/clairebot/Main.java b/src/main/java/com/sidpatchy/clairebot/Main.java index 5f54473..f8e7874 100644 --- a/src/main/java/com/sidpatchy/clairebot/Main.java +++ b/src/main/java/com/sidpatchy/clairebot/Main.java @@ -195,7 +195,7 @@ public static void extractParametersFromConfig(boolean updateOutdatedConfigs) { color = config.getString("color"); errorColor = config.getString("errorColor"); errorGifs = config.getList("error_gifs", String.class); - fallbackLocale = Locale.forLanguageTag(config.getString("fallback_locale")); + fallbackLocale = Locale.forLanguageTag(config.getString("fallback_language")); zerfas = config.getList("zerfas", String.class); zerfasEmojiServerID = String.valueOf(config.getLong("zerfas_emoji_server_id")); zerfasEmojiID = String.valueOf(config.getLong("zerfas_emoji_id")); diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml index a6a1f8e..0a1e19f 100644 --- a/src/main/resources/log4j2.xml +++ b/src/main/resources/log4j2.xml @@ -1,5 +1,5 @@ - + logs clairebot From 2f52b19bcba33e2503a27110a4983e21de4c5a62 Mon Sep 17 00:00:00 2001 From: Sidpatchy Date: Tue, 14 Oct 2025 15:41:41 -0600 Subject: [PATCH 40/64] chore(release): 3.4.0-alpha.2 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 5b9ef0c..74cf249 100644 --- a/build.gradle +++ b/build.gradle @@ -14,7 +14,7 @@ jar { } group = 'com.sidpatchy' -version = '3.4.0-alpha.1' +version = '3.4.0-alpha.2' processResources { filesMatching('**/build.properties') { From 9e44b9704a35cb33e34c2a485a17a171329c2bc8 Mon Sep 17 00:00:00 2001 From: Sidpatchy Date: Tue, 14 Oct 2025 16:43:13 -0600 Subject: [PATCH 41/64] fix: remove trailing slashes from API endpoints --- src/main/java/com/sidpatchy/clairebot/API/APIUser.java | 10 +++++----- src/main/java/com/sidpatchy/clairebot/API/Guild.java | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/sidpatchy/clairebot/API/APIUser.java b/src/main/java/com/sidpatchy/clairebot/API/APIUser.java index 5193591..c57af79 100644 --- a/src/main/java/com/sidpatchy/clairebot/API/APIUser.java +++ b/src/main/java/com/sidpatchy/clairebot/API/APIUser.java @@ -34,7 +34,7 @@ public APIUser(String userID) { */ public void getUser() throws IOException { try { - user.loadFromURL(Main.getApiUser(), Main.getApiPassword(), Main.getApiPath() + "api/v1/user/" + userID); + user.loadFromURL(Main.getApiUser(), Main.getApiPassword(), Main.getApiPath() + "api/v1/user" + userID); } catch (Exception e) { if (createNewWithDefaults) { @@ -94,7 +94,7 @@ public void createUser(String accentColour, List pointsMessages, List pointsVoiceChat) throws IOException { POST post = new POST(); - post.postToURL(Main.getApiPath() + "api/v1/user/", userConstructor(accentColour, language, pointsGuildID, pointsMessages, pointsVoiceChat)); + post.postToURL(Main.getApiPath() + "api/v1/user", userConstructor(accentColour, language, pointsGuildID, pointsMessages, pointsVoiceChat)); } public void createUserWithDefaults() { @@ -126,7 +126,7 @@ public void updateUser(String accentColour, } PUT put = new PUT(); - put.putToURL(Main.getApiPath() + "api/v1/user/" + userID, + put.putToURL(Main.getApiPath() + "api/v1/user" + userID, userConstructor(accentColour, language, pointsGuildID, pointsMessages, pointsVoiceChat)); } @@ -171,7 +171,7 @@ public void updateUserPointsGuildID(List pointsGuildID) throws IOExcepti public void deleteUser() throws IOException { DELETE delete = new DELETE(); - delete.deleteToURL(Main.getApiPath() + "api/v1/user/" + userID); + delete.deleteToURL(Main.getApiPath() + "api/v1/user" + userID); } /** @@ -210,7 +210,7 @@ public InputStreamReader getALLUsers() throws IOException { URL url; InputStreamReader reader; - String link = Main.getApiPath() + "api/v1/user/"; + String link = Main.getApiPath() + "api/v1/user"; try { url = new URL(link); URLConnection uc = url.openConnection(); diff --git a/src/main/java/com/sidpatchy/clairebot/API/Guild.java b/src/main/java/com/sidpatchy/clairebot/API/Guild.java index 1a20042..51180b2 100644 --- a/src/main/java/com/sidpatchy/clairebot/API/Guild.java +++ b/src/main/java/com/sidpatchy/clairebot/API/Guild.java @@ -29,7 +29,7 @@ public Guild(String guildID) { */ public void getGuild() throws IOException { try { - guild.loadFromURL(Main.getApiUser(), Main.getApiPassword(), Main.getApiPath() + "api/v1/guild/" + guildID); + guild.loadFromURL(Main.getApiUser(), Main.getApiPassword(), Main.getApiPath() + "api/v1/guild" + guildID); } catch (Exception e) { if (createNewWithDefaults) { @@ -61,7 +61,7 @@ public void createGuild(String requestsChannelID, String moderatorMessagesChannelID, boolean enforceServerLanguage) throws IOException { POST post = new POST(); - post.postToURL(Main.getApiPath() + "api/v1/guild/", guildConstructor( + post.postToURL(Main.getApiPath() + "api/v1/guild", guildConstructor( requestsChannelID, moderatorMessagesChannelID, enforceServerLanguage @@ -86,7 +86,7 @@ public void updateGuild(String requestsChannelID, String moderatorMessagesChannelID, boolean enforceServerLanguage) throws IOException { PUT put = new PUT(); - put.putToURL(Main.getApiPath() + "api/v1/guild/" + guildID, guildConstructor( + put.putToURL(Main.getApiPath() + "api/v1/guild" + guildID, guildConstructor( requestsChannelID, moderatorMessagesChannelID, enforceServerLanguage @@ -119,7 +119,7 @@ public void updateEnforceServerLanguage(boolean enforceServerLanguage) throws IO public void deleteGuild() throws IOException { DELETE delete = new DELETE(); - delete.deleteToURL(Main.getApiPath() + "api/v1/guild/" + guildID); + delete.deleteToURL(Main.getApiPath() + "api/v1/guild" + guildID); } public String guildConstructor(String requestsChannelID, @@ -141,7 +141,7 @@ public InputStreamReader getALLGuilds() throws IOException { URL url; InputStreamReader reader; - String link = Main.getApiPath() + "api/v1/guild/"; + String link = Main.getApiPath() + "api/v1/guild"; try { url = new URL(link); URLConnection uc = url.openConnection(); From fd6f665bc17662f23d6815f9b36598e6b30ea698 Mon Sep 17 00:00:00 2001 From: Sidpatchy Date: Tue, 14 Oct 2025 16:43:37 -0600 Subject: [PATCH 42/64] chore(release): 3.4.0-alpha.3 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 74cf249..df70320 100644 --- a/build.gradle +++ b/build.gradle @@ -14,7 +14,7 @@ jar { } group = 'com.sidpatchy' -version = '3.4.0-alpha.2' +version = '3.4.0-alpha.3' processResources { filesMatching('**/build.properties') { From 694f3285e27e0ce0e218459b770fa48e252b9682 Mon Sep 17 00:00:00 2001 From: Sidpatchy Date: Tue, 14 Oct 2025 17:01:48 -0600 Subject: [PATCH 43/64] feat: urlbuilder for API classes Yes, I am testing in prod. --- .../com/sidpatchy/clairebot/API/APIUser.java | 13 ++++---- .../com/sidpatchy/clairebot/API/Guild.java | 11 ++++--- .../clairebot/Util/Network/UrlBuilder.java | 31 +++++++++++++++++++ src/main/resources/config.yml | 2 +- 4 files changed, 45 insertions(+), 12 deletions(-) create mode 100644 src/main/java/com/sidpatchy/clairebot/Util/Network/UrlBuilder.java diff --git a/src/main/java/com/sidpatchy/clairebot/API/APIUser.java b/src/main/java/com/sidpatchy/clairebot/API/APIUser.java index c57af79..d98492d 100644 --- a/src/main/java/com/sidpatchy/clairebot/API/APIUser.java +++ b/src/main/java/com/sidpatchy/clairebot/API/APIUser.java @@ -8,6 +8,7 @@ import com.sidpatchy.clairebot.Util.Network.DELETE; import com.sidpatchy.clairebot.Util.Network.POST; import com.sidpatchy.clairebot.Util.Network.PUT; +import com.sidpatchy.clairebot.Util.Network.UrlBuilder; import java.io.IOException; import java.io.InputStreamReader; @@ -34,7 +35,7 @@ public APIUser(String userID) { */ public void getUser() throws IOException { try { - user.loadFromURL(Main.getApiUser(), Main.getApiPassword(), Main.getApiPath() + "api/v1/user" + userID); + user.loadFromURL(Main.getApiUser(), Main.getApiPassword(), UrlBuilder.buildUrl(Main.getApiPath(), "api/v1/user", userID)); } catch (Exception e) { if (createNewWithDefaults) { @@ -94,7 +95,7 @@ public void createUser(String accentColour, List pointsMessages, List pointsVoiceChat) throws IOException { POST post = new POST(); - post.postToURL(Main.getApiPath() + "api/v1/user", userConstructor(accentColour, language, pointsGuildID, pointsMessages, pointsVoiceChat)); + post.postToURL(UrlBuilder.buildUrl(Main.getApiPath(), "api/v1/user"), userConstructor(accentColour, language, pointsGuildID, pointsMessages, pointsVoiceChat)); } public void createUserWithDefaults() { @@ -126,7 +127,7 @@ public void updateUser(String accentColour, } PUT put = new PUT(); - put.putToURL(Main.getApiPath() + "api/v1/user" + userID, + put.putToURL(UrlBuilder.buildUrl(Main.getApiPath(), "api/v1/user", userID), userConstructor(accentColour, language, pointsGuildID, pointsMessages, pointsVoiceChat)); } @@ -171,7 +172,7 @@ public void updateUserPointsGuildID(List pointsGuildID) throws IOExcepti public void deleteUser() throws IOException { DELETE delete = new DELETE(); - delete.deleteToURL(Main.getApiPath() + "api/v1/user" + userID); + delete.deleteToURL(UrlBuilder.buildUrl(Main.getApiPath(), "api/v1/user", userID)); } /** @@ -210,7 +211,7 @@ public InputStreamReader getALLUsers() throws IOException { URL url; InputStreamReader reader; - String link = Main.getApiPath() + "api/v1/user"; + String link = UrlBuilder.buildUrl(Main.getApiPath(), "api/v1/user"); try { url = new URL(link); URLConnection uc = url.openConnection(); @@ -234,4 +235,4 @@ private void fixUserPointsGuildID() throws IOException { updateUserPointsGuildID((ArrayList) defaults.get("pointsGuildID")); } } -} \ No newline at end of file +} diff --git a/src/main/java/com/sidpatchy/clairebot/API/Guild.java b/src/main/java/com/sidpatchy/clairebot/API/Guild.java index 51180b2..ead2fe2 100644 --- a/src/main/java/com/sidpatchy/clairebot/API/Guild.java +++ b/src/main/java/com/sidpatchy/clairebot/API/Guild.java @@ -5,6 +5,7 @@ import com.sidpatchy.clairebot.Util.Network.DELETE; import com.sidpatchy.clairebot.Util.Network.POST; import com.sidpatchy.clairebot.Util.Network.PUT; +import com.sidpatchy.clairebot.Util.Network.UrlBuilder; import java.io.IOException; import java.io.InputStreamReader; @@ -29,7 +30,7 @@ public Guild(String guildID) { */ public void getGuild() throws IOException { try { - guild.loadFromURL(Main.getApiUser(), Main.getApiPassword(), Main.getApiPath() + "api/v1/guild" + guildID); + guild.loadFromURL(Main.getApiUser(), Main.getApiPassword(), UrlBuilder.buildUrl(Main.getApiPath(), "api/v1/guild", guildID)); } catch (Exception e) { if (createNewWithDefaults) { @@ -61,7 +62,7 @@ public void createGuild(String requestsChannelID, String moderatorMessagesChannelID, boolean enforceServerLanguage) throws IOException { POST post = new POST(); - post.postToURL(Main.getApiPath() + "api/v1/guild", guildConstructor( + post.postToURL(UrlBuilder.buildUrl(Main.getApiPath(), "api/v1/guild"), guildConstructor( requestsChannelID, moderatorMessagesChannelID, enforceServerLanguage @@ -86,7 +87,7 @@ public void updateGuild(String requestsChannelID, String moderatorMessagesChannelID, boolean enforceServerLanguage) throws IOException { PUT put = new PUT(); - put.putToURL(Main.getApiPath() + "api/v1/guild" + guildID, guildConstructor( + put.putToURL(UrlBuilder.buildUrl(Main.getApiPath(), "api/v1/guild", guildID), guildConstructor( requestsChannelID, moderatorMessagesChannelID, enforceServerLanguage @@ -119,7 +120,7 @@ public void updateEnforceServerLanguage(boolean enforceServerLanguage) throws IO public void deleteGuild() throws IOException { DELETE delete = new DELETE(); - delete.deleteToURL(Main.getApiPath() + "api/v1/guild" + guildID); + delete.deleteToURL(UrlBuilder.buildUrl(Main.getApiPath(), "api/v1/guild", guildID)); } public String guildConstructor(String requestsChannelID, @@ -141,7 +142,7 @@ public InputStreamReader getALLGuilds() throws IOException { URL url; InputStreamReader reader; - String link = Main.getApiPath() + "api/v1/guild"; + String link = UrlBuilder.buildUrl(Main.getApiPath(), "api/v1/guild"); try { url = new URL(link); URLConnection uc = url.openConnection(); diff --git a/src/main/java/com/sidpatchy/clairebot/Util/Network/UrlBuilder.java b/src/main/java/com/sidpatchy/clairebot/Util/Network/UrlBuilder.java new file mode 100644 index 0000000..ceedcb2 --- /dev/null +++ b/src/main/java/com/sidpatchy/clairebot/Util/Network/UrlBuilder.java @@ -0,0 +1,31 @@ +package com.sidpatchy.clairebot.Util.Network; + +public class UrlBuilder { + /** + * Safely joins URL path segments, ensuring proper slash placement. + * Removes trailing slash from base and ensures single slash between segments. + * + * @param base the base URL (e.g., "http://api.example.com/") + * @param segments path segments to append + * @return properly formatted URL + */ + public static String buildUrl(String base, String... segments) { + if (base == null) { + throw new IllegalArgumentException("Base URL cannot be null"); + } + + // Remove trailing slash from base if present + String url = base.endsWith("/") ? base.substring(0, base.length() - 1) : base; + + // Append each segment with a leading slash + for (String segment : segments) { + if (segment != null && !segment.isEmpty()) { + // Remove leading slash from segment if present to avoid double slashes + String cleanSegment = segment.startsWith("/") ? segment.substring(1) : segment; + url += "/" + cleanSegment; + } + } + + return url; + } +} diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 39d4c73..38a541a 100755 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -51,7 +51,7 @@ total_shards: 1 # https://github.com/Sidpatchy/ClaireData # ClaireData's login information -apiPath: https://api.example.com/ +apiPath: http://clairedata:8080/ apiUser: clairedata apiPassword: examplePassword From d80fa28b13fa1e0d140a8c0322f0d2be28eb7d59 Mon Sep 17 00:00:00 2001 From: Sidpatchy Date: Tue, 14 Oct 2025 17:02:15 -0600 Subject: [PATCH 44/64] chore(release): 3.4.0-alpha.4 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index df70320..a8cba01 100644 --- a/build.gradle +++ b/build.gradle @@ -14,7 +14,7 @@ jar { } group = 'com.sidpatchy' -version = '3.4.0-alpha.3' +version = '3.4.0-alpha.4' processResources { filesMatching('**/build.properties') { From d2cd7776afeb6402255b55e1e20ee8925cbdb6de Mon Sep 17 00:00:00 2001 From: Sidpatchy Date: Wed, 15 Oct 2025 15:51:49 -0600 Subject: [PATCH 45/64] refactor: mark legacy error methods as deprecated --- src/main/java/com/sidpatchy/clairebot/Embed/ErrorEmbed.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/com/sidpatchy/clairebot/Embed/ErrorEmbed.java b/src/main/java/com/sidpatchy/clairebot/Embed/ErrorEmbed.java index 3b41ac2..2f4f468 100755 --- a/src/main/java/com/sidpatchy/clairebot/Embed/ErrorEmbed.java +++ b/src/main/java/com/sidpatchy/clairebot/Embed/ErrorEmbed.java @@ -83,6 +83,7 @@ public static EmbedBuilder getLackingPermissions(LanguageManager languageManager // TODO - remove these legacy methods + @Deprecated public static EmbedBuilder getError(String errorCode) { ArrayList errorGifs = (ArrayList) Main.getErrorGifs(); int rand = new Random().nextInt(errorGifs.size()); @@ -95,15 +96,18 @@ public static EmbedBuilder getError(String errorCode) { .setImage(errorGifs.get(rand)); } + @Deprecated public static EmbedBuilder getError(String errorCode, String customMessage) { return getError(errorCode).setDescription(customMessage + "\n\nPlease try running the command once more and if that doesn't work, join my [Discord server](https://support.clairebot.net/) and let us know about the issue." + "\n\nPlease include the following error code: " + errorCode); } + @Deprecated public static EmbedBuilder getCustomError(String errorCode, String message) { return getError(errorCode).setDescription(message); } + @Deprecated public static EmbedBuilder getLackingPermissions(String message) { // Fixed: remove accidental double getErrorCode call return getCustomError(Main.getErrorCode("noPerms"), message); From 1190b716e24d0216b7d1a3001a9498f8101ed90f Mon Sep 17 00:00:00 2001 From: Sidpatchy Date: Wed, 15 Oct 2025 16:00:40 -0600 Subject: [PATCH 46/64] fix: multiple regressions eliminated --- .../Commands/Regular/ServerInfoEmbed.java | 3 +- .../clairebot/Listener/MessageCreate.java | 3 +- .../clairebot/Listener/ModalSubmit.java | 19 ++++++++---- .../Listener/SlashCommandCreate.java | 30 ++++++++++++------- .../clairebot/Util/ChannelUtils.java | 3 +- 5 files changed, 38 insertions(+), 20 deletions(-) diff --git a/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/ServerInfoEmbed.java b/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/ServerInfoEmbed.java index 3a9cfc0..ce93689 100644 --- a/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/ServerInfoEmbed.java +++ b/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/ServerInfoEmbed.java @@ -11,7 +11,8 @@ public class ServerInfoEmbed { public static EmbedBuilder getServerInfo(LanguageManager languageManager, Server server, String userID) { Color color = Main.getColor(userID); String authorName = server.getName(); - String footerText = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Regular.ServerInfoEmbed.ServerID: " + server.getIdAsString()); + String footerLabel = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Regular.ServerInfoEmbed.ServerID"); + String footerText = footerLabel + ": " + server.getIdAsString(); String ownerLabel = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Regular.ServerInfoEmbed.Owner"); String creationDateLabel = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Regular.ServerInfoEmbed.CreationDate"); String roleCountLabel = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Regular.ServerInfoEmbed.RoleCount"); diff --git a/src/main/java/com/sidpatchy/clairebot/Listener/MessageCreate.java b/src/main/java/com/sidpatchy/clairebot/Listener/MessageCreate.java index 592fc65..0aac919 100644 --- a/src/main/java/com/sidpatchy/clairebot/Listener/MessageCreate.java +++ b/src/main/java/com/sidpatchy/clairebot/Listener/MessageCreate.java @@ -32,8 +32,7 @@ public void onMessageCreate(MessageCreateEvent event) { TextChannel textChannel = message.getChannel(); ContextManager context = new ContextManager(server, textChannel, user, user, message, new HashMap<>()); - // Todo replace reference to en-US with config file parameter - LanguageManager languageManager = new LanguageManager(Locale.forLanguageTag("en-US"), context); + LanguageManager languageManager = new LanguageManager(Main.getFallbackLocale(), context); // it seems as though the Javacord functions for this don't actually work, or I'm using them wrong if (messageAuthor.isBotUser() || messageAuthor.isYourself() || messageAuthor.getIdAsString().equalsIgnoreCase("704244031772950528") || messageAuthor.getIdAsString().equalsIgnoreCase("848024760789237810")) { diff --git a/src/main/java/com/sidpatchy/clairebot/Listener/ModalSubmit.java b/src/main/java/com/sidpatchy/clairebot/Listener/ModalSubmit.java index 8667cf6..197959e 100644 --- a/src/main/java/com/sidpatchy/clairebot/Listener/ModalSubmit.java +++ b/src/main/java/com/sidpatchy/clairebot/Listener/ModalSubmit.java @@ -105,12 +105,19 @@ else if (modalID.startsWith("santa-theme")) { if (voteType.equalsIgnoreCase("request")) { ServerTextChannel requestsChannel = ChannelUtils.getRequestsChannel(server); - modalInteraction.createImmediateResponder() - .addEmbed(VotingEmbed.getUserResponse(languageManager, author, requestsChannel.getMentionTag())) - .setFlags(MessageFlag.EPHEMERAL) - .respond(); - - requestsChannel.sendMessage(VotingEmbed.getPoll(languageManager, voteType, question, description, false, null, server, author, 0)); + if (requestsChannel == null) { + modalInteraction.createImmediateResponder() + .addEmbed(com.sidpatchy.clairebot.Embed.ErrorEmbed.getCustomError(com.sidpatchy.clairebot.Main.getErrorCode("requestsChannelMissing"), "A requests channel is not configured for this server. An admin can set one in /config server > Requests Channel.")) + .setFlags(MessageFlag.EPHEMERAL) + .respond(); + } else { + modalInteraction.createImmediateResponder() + .addEmbed(VotingEmbed.getUserResponse(languageManager, author, requestsChannel.getMentionTag())) + .setFlags(MessageFlag.EPHEMERAL) + .respond(); + + requestsChannel.sendMessage(VotingEmbed.getPoll(languageManager, voteType, question, description, false, null, server, author, 0)); + } } else if (voteType.equalsIgnoreCase("poll")) { modalInteraction.createImmediateResponder() diff --git a/src/main/java/com/sidpatchy/clairebot/Listener/SlashCommandCreate.java b/src/main/java/com/sidpatchy/clairebot/Listener/SlashCommandCreate.java index 16efc4e..a1effca 100644 --- a/src/main/java/com/sidpatchy/clairebot/Listener/SlashCommandCreate.java +++ b/src/main/java/com/sidpatchy/clairebot/Listener/SlashCommandCreate.java @@ -12,6 +12,7 @@ import com.sidpatchy.clairebot.Util.ChannelUtils; import org.apache.logging.log4j.Logger; import org.javacord.api.entity.channel.TextChannel; +import org.javacord.api.entity.channel.ServerTextChannel; import org.javacord.api.entity.message.MessageFlag; import org.javacord.api.entity.message.component.ActionRow; import org.javacord.api.entity.message.component.Button; @@ -255,16 +256,25 @@ else if (commandName.equalsIgnoreCase(commands.getRequest().getName())) { } } - slashCommandInteraction.createImmediateResponder() - .addEmbed(VotingEmbed.getUserResponse(languageManager, author, ChannelUtils.getRequestsChannel(server).getMentionTag())) - .setFlags(MessageFlag.EPHEMERAL) - .respond(); + // Resolve requests channel safely + ServerTextChannel requestsChannel = ChannelUtils.getRequestsChannel(server); + if (requestsChannel == null) { + slashCommandInteraction.createImmediateResponder() + .setFlags(MessageFlag.EPHEMERAL) + .addEmbed(ErrorEmbed.getCustomError(Main.getErrorCode("requestsChannelMissing"), "A requests channel is not configured for this server. An admin can set one in /config server > Requests Channel.")) + .respond(); + } else { + slashCommandInteraction.createImmediateResponder() + .addEmbed(VotingEmbed.getUserResponse(languageManager, author, requestsChannel.getMentionTag())) + .setFlags(MessageFlag.EPHEMERAL) + .respond(); - ChannelUtils.getRequestsChannel(server).sendMessage(VotingEmbed.getPoll(languageManager, "REQUEST", question, allowMultipleChoices, choices, server, author, numChoices)).thenAccept(message -> { - message.addReaction("\uD83D\uDC4D"); - message.addReaction("\uD83D\uDC4E"); - message.addReaction(":vote:706373563564949566"); - }); + requestsChannel.sendMessage(VotingEmbed.getPoll(languageManager, "REQUEST", question, allowMultipleChoices, choices, server, author, numChoices)).thenAccept(message -> { + message.addReaction("\uD83D\uDC4D"); + message.addReaction("\uD83D\uDC4E"); + message.addReaction(":vote:706373563564949566"); + }); + } } } else if (commandName.equalsIgnoreCase(commands.getServer().getName())) { @@ -293,7 +303,7 @@ else if (server != null) { .addEmbed(embed) .respond(); } - else if (commandName.equalsIgnoreCase(commands.getInfo().getName())) { + else if (commandName.equalsIgnoreCase(commands.getUser().getName())) { slashCommandInteraction.createImmediateResponder() .addEmbed(UserInfoEmbed.getUser(languageManager, user, author, server)) .respond(); diff --git a/src/main/java/com/sidpatchy/clairebot/Util/ChannelUtils.java b/src/main/java/com/sidpatchy/clairebot/Util/ChannelUtils.java index 5786534..4e4f99f 100755 --- a/src/main/java/com/sidpatchy/clairebot/Util/ChannelUtils.java +++ b/src/main/java/com/sidpatchy/clairebot/Util/ChannelUtils.java @@ -65,7 +65,8 @@ public static ServerTextChannel getRequestsChannel(Server server) { } if (apiFailed || channel == null) { - channel = server.getTextChannelsByName("requests").get(0); + var byName = server.getTextChannelsByName("requests"); + channel = byName.isEmpty() ? null : byName.get(0); } return channel; From 929c2264bb216e12a752b867fe0866e6dd074e1d Mon Sep 17 00:00:00 2001 From: Sidpatchy Date: Wed, 15 Oct 2025 16:01:22 -0600 Subject: [PATCH 47/64] chore(release): 3.4.0-alpha.5 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index a8cba01..718bc17 100644 --- a/build.gradle +++ b/build.gradle @@ -14,7 +14,7 @@ jar { } group = 'com.sidpatchy' -version = '3.4.0-alpha.4' +version = '3.4.0-alpha.5' processResources { filesMatching('**/build.properties') { From b0a8de13464c43a0310ad739edaec55125f1685c Mon Sep 17 00:00:00 2001 From: Sidpatchy Date: Wed, 15 Oct 2025 21:17:39 -0600 Subject: [PATCH 48/64] fix: cb.user.id.accentcolour placeholder Now returns hexadecimal value as intended. --- .../com/sidpatchy/clairebot/Lang/PlaceholderHandler.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/sidpatchy/clairebot/Lang/PlaceholderHandler.java b/src/main/java/com/sidpatchy/clairebot/Lang/PlaceholderHandler.java index ccd3c9c..97ed14d 100644 --- a/src/main/java/com/sidpatchy/clairebot/Lang/PlaceholderHandler.java +++ b/src/main/java/com/sidpatchy/clairebot/Lang/PlaceholderHandler.java @@ -5,6 +5,7 @@ import org.javacord.api.entity.channel.Channel; import org.javacord.api.entity.channel.ServerChannel; +import java.awt.*; import java.util.List; import java.util.Map; import java.util.Objects; @@ -64,8 +65,10 @@ private Map initializePlaceholders() { context.user() != null ? context.user().getIdAsString() : ""), entry("cb.user.id.mentiontag", () -> context.user() != null ? "<@" + context.user().getIdAsString() + ">" : ""), - entry("cb.user.id.accentcolour", () -> - String.valueOf(Main.getColor(Objects.requireNonNull(context.user()).getIdAsString()))), + entry("cb.user.id.accentcolour", () -> { + Color color = Main.getColor(Objects.requireNonNull(context.user()).getIdAsString()); + return String.format("#%02x%02x%02x", color.getRed(), color.getGreen(), color.getBlue()); + }), entry("cb.user.id.displayname.server", () -> { org.javacord.api.entity.server.Server server = context.server(); org.javacord.api.entity.user.User author = context.author(); From ed0d2c5be2d16cdfd46c1dfe0ab033f0c4659948 Mon Sep 17 00:00:00 2001 From: Sidpatchy Date: Wed, 15 Oct 2025 21:50:51 -0600 Subject: [PATCH 49/64] fix: ensure /config updates and displays channel changes correctly --- .../Regular/ServerPreferencesEmbed.java | 4 +- .../clairebot/Lang/PlaceholderHandler.java | 9 + .../clairebot/Listener/MessageCreate.java | 5 + .../clairebot/Listener/SelectMenuChoose.java | 188 ++++++++++-------- .../Regular/ServerPreferencesComponents.java | 7 +- .../resources/translations/lang_en-US.yml | 4 +- 6 files changed, 126 insertions(+), 91 deletions(-) diff --git a/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/ServerPreferencesEmbed.java b/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/ServerPreferencesEmbed.java index dcf50e1..3ffed4f 100644 --- a/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/ServerPreferencesEmbed.java +++ b/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/ServerPreferencesEmbed.java @@ -66,7 +66,7 @@ public static EmbedBuilder getAcknowledgeRequestsChannelChange(LanguageManager l // Temp/localized variables String title = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Regular.ServerPreferencesEmbed.AcknowledgeRequestsChannelChangeTitle"); String mention = "<#" + channel.getIdAsString() + ">"; - languageManager.addContext(ContextManager.ContextType.GENERIC, "channel.id.mentiontag", mention); + languageManager.addContext(ContextManager.ContextType.GENERIC, "cb.channel.requests.mentiontag", mention); String desc = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Regular.ServerPreferencesEmbed.AcknowledgeRequestsChannelChangeDescription"); return new EmbedBuilder() @@ -94,7 +94,7 @@ public static EmbedBuilder getAcknowledgeModeratorChannelChange(LanguageManager // Temp/localized variables String title = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Regular.ServerPreferencesEmbed.AcknowledgeModeratorChannelChangeTitle"); String mention = "<#" + channel.getIdAsString() + ">"; - languageManager.addContext(ContextManager.ContextType.GENERIC, "channel.id.mentiontag", mention); + languageManager.addContext(ContextManager.ContextType.GENERIC, "cb.channel.moderator.mentiontag", mention); String desc = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Regular.ServerPreferencesEmbed.AcknowledgeModeratorChannelChangeDescription"); return new EmbedBuilder() diff --git a/src/main/java/com/sidpatchy/clairebot/Lang/PlaceholderHandler.java b/src/main/java/com/sidpatchy/clairebot/Lang/PlaceholderHandler.java index 97ed14d..4e860c7 100644 --- a/src/main/java/com/sidpatchy/clairebot/Lang/PlaceholderHandler.java +++ b/src/main/java/com/sidpatchy/clairebot/Lang/PlaceholderHandler.java @@ -100,6 +100,15 @@ private Map initializePlaceholders() { context.channel() != null ? context.channel().getIdAsString() : ""), entry("cb.channel.id.mentiontag", () -> context.channel() != null ? "<#" + context.channel().getIdAsString() + ">" : ""), + // Specific placeholders used by acknowledgements when the selected channel differs from invoking channel + entry("cb.channel.requests.mentiontag", () -> { + Object v = context.getData(ContextManager.ContextType.GENERIC, "cb.channel.requests.mentiontag"); + return v != null ? v.toString() : ""; + }), + entry("cb.channel.moderator.mentiontag", () -> { + Object v = context.getData(ContextManager.ContextType.GENERIC, "cb.channel.moderator.mentiontag"); + return v != null ? v.toString() : ""; + }), // Command placeholders entry("cb.commandname", () -> diff --git a/src/main/java/com/sidpatchy/clairebot/Listener/MessageCreate.java b/src/main/java/com/sidpatchy/clairebot/Listener/MessageCreate.java index 0aac919..7e2738e 100644 --- a/src/main/java/com/sidpatchy/clairebot/Listener/MessageCreate.java +++ b/src/main/java/com/sidpatchy/clairebot/Listener/MessageCreate.java @@ -31,6 +31,11 @@ public void onMessageCreate(MessageCreateEvent event) { APIUser apiUser = new APIUser(messageAuthor.getIdAsString()); TextChannel textChannel = message.getChannel(); + // Some messages (e.g., webhooks/system) have no user; skip processing to avoid NPEs in localization + if (user == null) { + return; + } + ContextManager context = new ContextManager(server, textChannel, user, user, message, new HashMap<>()); LanguageManager languageManager = new LanguageManager(Main.getFallbackLocale(), context); diff --git a/src/main/java/com/sidpatchy/clairebot/Listener/SelectMenuChoose.java b/src/main/java/com/sidpatchy/clairebot/Listener/SelectMenuChoose.java index e33ab4e..2159034 100644 --- a/src/main/java/com/sidpatchy/clairebot/Listener/SelectMenuChoose.java +++ b/src/main/java/com/sidpatchy/clairebot/Listener/SelectMenuChoose.java @@ -12,7 +12,6 @@ import org.javacord.api.entity.channel.TextChannel; import org.javacord.api.entity.message.Message; import org.javacord.api.entity.message.MessageFlag; -import org.javacord.api.entity.message.embed.EmbedAuthor; import org.javacord.api.entity.server.Server; import org.javacord.api.entity.user.User; import org.javacord.api.event.interaction.SelectMenuChooseEvent; @@ -37,92 +36,54 @@ public void onSelectMenuChoose(SelectMenuChooseEvent event) { ContextManager context = new ContextManager(server, channel, user, user, null, new HashMap<>()); languageManager = new LanguageManager(Main.getFallbackLocale(), context); - // Not speaking of message author, rather, the header field - EmbedAuthor embedAuthor = message.getEmbeds().get(0).getAuthor().orElse(null); - if (embedAuthor != null && channel != null) { - String menuName = embedAuthor.getName(); - String label = selectMenuInteraction.getChosenOptions().get(0).getLabel(); - String id = selectMenuInteraction.getChosenOptions().get(0).getValue(); + // Route exclusively by customId and stable option values to avoid localization issues + String customId = selectMenuInteraction.getCustomId(); + String value = selectMenuInteraction.getChosenOptions().get(0).getValue(); - // User preferences menu - if (menuName.equalsIgnoreCase("User Preferences Editor")) { - selectMenuInteraction.acknowledge(); - - if (label.equalsIgnoreCase("Accent Colour")) { - selectMenuInteraction.createFollowupMessageBuilder() - .setFlags(MessageFlag.EPHEMERAL) - .addEmbed(UserPreferencesEmbed.getAccentColourMenu(languageManager, user)) - .addComponents(UserPreferencesComponents.getAccentColourMenu(languageManager)) - .send(); - } - else if (label.equalsIgnoreCase("Language")) { - selectMenuInteraction.createFollowupMessageBuilder() - .setFlags(MessageFlag.EPHEMERAL) - .addEmbed(UserPreferencesEmbed.getLanguageMenu(languageManager, user)) - .addComponents() - .send(); - } - } - else if (menuName.equalsIgnoreCase("Accent Colour Editor")) { - if (label.equalsIgnoreCase("Select Common Colours")) { - selectMenuInteraction.createFollowupMessageBuilder() - .setFlags(MessageFlag.EPHEMERAL) - .addEmbed(UserPreferencesEmbed.getAccentColourListMenu(languageManager, user)) - .addComponents(UserPreferencesComponents.getAccentColourList(languageManager)) - .send(); - } - else if (label.equalsIgnoreCase("Hexadecimal Entry")) { - message.delete(); - selectMenuInteraction.respondWithModal("hex-entry-modal", "Hex Colour Entry", UserPreferencesComponents.getAccentColourHexEntry(languageManager)); - } + // User preferences main menu (customId: "settings") + if ("settings".equalsIgnoreCase(customId)) { + // Values are stable English identifiers set in UserPreferencesComponents + if ("Accent Colour Editor".equalsIgnoreCase(value)) { selectMenuInteraction.acknowledge(); + selectMenuInteraction.createFollowupMessageBuilder() + .setFlags(MessageFlag.EPHEMERAL) + .addEmbed(UserPreferencesEmbed.getAccentColourMenu(languageManager, user)) + .addComponents(UserPreferencesComponents.getAccentColourMenu(languageManager)) + .send(); } - else if (menuName.equalsIgnoreCase("Accent Colour List")) { + else if ("Language Editor".equalsIgnoreCase(value)) { selectMenuInteraction.acknowledge(); - - String accentColour = selectMenuInteraction.getChosenOptions().get(0).getDescription().orElse(""); - - if (accentColour.isEmpty()) { - selectMenuInteraction.createFollowupMessageBuilder() - .setFlags(MessageFlag.EPHEMERAL) - .addEmbed(ErrorEmbed.getError(Main.getErrorCode("accentColourParse"))) - .send(); - } - else { - selectMenuInteraction.createFollowupMessageBuilder() - .setFlags(MessageFlag.EPHEMERAL) - .addEmbed(UserPreferencesEmbed.getAcknowledgeAccentColourChange(languageManager, user, accentColour)) - .addComponents() - .send(); - } + selectMenuInteraction.createFollowupMessageBuilder() + .setFlags(MessageFlag.EPHEMERAL) + .addEmbed(UserPreferencesEmbed.getLanguageMenu(languageManager, user)) + .addComponents() + .send(); } - - // Server configuration - else if (menuName.equalsIgnoreCase("Server Configuration Editor")) { + else if ("Requests Channel".equalsIgnoreCase(value) + || "Moderator Messages Channel".equalsIgnoreCase(value) + || "Enforce Server Language".equalsIgnoreCase(value)) { + // This block will only be hit if server settings menu mistakenly used the same customId. + // We keep it for backward compatibility; prefer using "server-settings" going forward. selectMenuInteraction.acknowledge(); - if (server == null) { selectMenuInteraction.createFollowupMessageBuilder() .setFlags(MessageFlag.EPHEMERAL) .addEmbed(ServerPreferencesEmbed.getNotServerMenu(languageManager)) .addComponents() .send(); - } - else if (label.equalsIgnoreCase("Requests Channel")) { + } else if ("Requests Channel".equalsIgnoreCase(value)) { selectMenuInteraction.createFollowupMessageBuilder() .setFlags(MessageFlag.EPHEMERAL) .addEmbed(ServerPreferencesEmbed.getRequestsChannelMenu(languageManager, user)) .addComponents(ServerPreferencesComponents.getRequestsChannelMenu(languageManager, server)) .send(); - } - else if (label.equalsIgnoreCase("Moderator Messages Channel")) { + } else if ("Moderator Messages Channel".equalsIgnoreCase(value)) { selectMenuInteraction.createFollowupMessageBuilder() .setFlags(MessageFlag.EPHEMERAL) .addEmbed(ServerPreferencesEmbed.getModeratorChannelMenu(languageManager, user)) .addComponents(ServerPreferencesComponents.getModeratorChannelMenu(languageManager, server)) .send(); - } - else if (label.equalsIgnoreCase("Enforce Server Language")) { + } else if ("Enforce Server Language".equalsIgnoreCase(value)) { selectMenuInteraction.createFollowupMessageBuilder() .setFlags(MessageFlag.EPHEMERAL) .addEmbed(ServerPreferencesEmbed.getEnforceServerLangMenu(languageManager, user)) @@ -130,39 +91,98 @@ else if (label.equalsIgnoreCase("Enforce Server Language")) { .send(); } } - else if (menuName.equalsIgnoreCase("Requests Channel")) { - String channelID = selectMenuInteraction.getChosenOptions().get(0).getValue(); - + } + // User preferences accent color submenu and list share customId "accent-color" + else if ("accent-color".equalsIgnoreCase(customId)) { + // Two possible values from the first submenu, otherwise treat as selecting a color from the list + if ("Select Common Colours".equalsIgnoreCase(value)) { selectMenuInteraction.acknowledge(); - selectMenuInteraction.createFollowupMessageBuilder() .setFlags(MessageFlag.EPHEMERAL) - .addEmbed(ServerPreferencesEmbed.getAcknowledgeRequestsChannelChange(languageManager, server, user, channelID)) - .addComponents() + .addEmbed(UserPreferencesEmbed.getAccentColourListMenu(languageManager, user)) + .addComponents(UserPreferencesComponents.getAccentColourList(languageManager)) .send(); } - else if (menuName.equalsIgnoreCase("Moderator Messages Channel")) { - String channelID = selectMenuInteraction.getChosenOptions().get(0).getValue(); - + else if ("Hexadecimal Entry".equalsIgnoreCase(value)) { + // Switch to modal input, delete the menu message to reduce clutter selectMenuInteraction.acknowledge(); - + message.delete(); + selectMenuInteraction.respondWithModal("hex-entry-modal", "Hex Colour Entry", UserPreferencesComponents.getAccentColourHexEntry(languageManager)); + } + else { + // Selecting a specific color from the list; hex is stored as the description + selectMenuInteraction.acknowledge(); + String accentColour = selectMenuInteraction.getChosenOptions().get(0).getDescription().orElse(""); + if (accentColour.isEmpty()) { + selectMenuInteraction.createFollowupMessageBuilder() + .setFlags(MessageFlag.EPHEMERAL) + .addEmbed(ErrorEmbed.getError(Main.getErrorCode("accentColourParse"))) + .send(); + } else { + selectMenuInteraction.createFollowupMessageBuilder() + .setFlags(MessageFlag.EPHEMERAL) + .addEmbed(UserPreferencesEmbed.getAcknowledgeAccentColourChange(languageManager, user, accentColour)) + .addComponents() + .send(); + } + } + } + // Server configuration main menu (new customId: "server-settings") + else if ("server-settings".equalsIgnoreCase(customId)) { + selectMenuInteraction.acknowledge(); + if (server == null) { selectMenuInteraction.createFollowupMessageBuilder() .setFlags(MessageFlag.EPHEMERAL) - .addEmbed(ServerPreferencesEmbed.getAcknowledgeModeratorChannelChange(languageManager, server, user, channelID)) + .addEmbed(ServerPreferencesEmbed.getNotServerMenu(languageManager)) .addComponents() .send(); - } - else if (menuName.equalsIgnoreCase("Enforce Server Language")) { - String bool = selectMenuInteraction.getChosenOptions().get(0).getValue(); - - selectMenuInteraction.acknowledge(); - + } else if ("Requests Channel".equalsIgnoreCase(value)) { selectMenuInteraction.createFollowupMessageBuilder() .setFlags(MessageFlag.EPHEMERAL) - .addEmbed(ServerPreferencesEmbed.getAcknowledgeEnforceServerLanguageUpdate(languageManager, server, user, bool)) - .addComponents() + .addEmbed(ServerPreferencesEmbed.getRequestsChannelMenu(languageManager, user)) + .addComponents(ServerPreferencesComponents.getRequestsChannelMenu(languageManager, server)) + .send(); + } else if ("Moderator Messages Channel".equalsIgnoreCase(value)) { + selectMenuInteraction.createFollowupMessageBuilder() + .setFlags(MessageFlag.EPHEMERAL) + .addEmbed(ServerPreferencesEmbed.getModeratorChannelMenu(languageManager, user)) + .addComponents(ServerPreferencesComponents.getModeratorChannelMenu(languageManager, server)) + .send(); + } else if ("Enforce Server Language".equalsIgnoreCase(value)) { + selectMenuInteraction.createFollowupMessageBuilder() + .setFlags(MessageFlag.EPHEMERAL) + .addEmbed(ServerPreferencesEmbed.getEnforceServerLangMenu(languageManager, user)) + .addComponents(ServerPreferencesComponents.getEnforceServerLanguageMenu(languageManager)) .send(); } } + // Server channel selection submenus + else if ("requestsChannel".equalsIgnoreCase(customId)) { + String channelID = value; // value holds the selected channel ID + selectMenuInteraction.acknowledge(); + selectMenuInteraction.createFollowupMessageBuilder() + .setFlags(MessageFlag.EPHEMERAL) + .addEmbed(ServerPreferencesEmbed.getAcknowledgeRequestsChannelChange(languageManager, server, user, channelID)) + .addComponents() + .send(); + } + else if ("moderatorChannel".equalsIgnoreCase(customId)) { + String channelID = value; // value holds the selected channel ID + selectMenuInteraction.acknowledge(); + selectMenuInteraction.createFollowupMessageBuilder() + .setFlags(MessageFlag.EPHEMERAL) + .addEmbed(ServerPreferencesEmbed.getAcknowledgeModeratorChannelChange(languageManager, server, user, channelID)) + .addComponents() + .send(); + } + else if ("enforceServerLanguage".equalsIgnoreCase(customId)) { + String bool = value; + selectMenuInteraction.acknowledge(); + selectMenuInteraction.createFollowupMessageBuilder() + .setFlags(MessageFlag.EPHEMERAL) + .addEmbed(ServerPreferencesEmbed.getAcknowledgeEnforceServerLanguageUpdate(languageManager, server, user, bool)) + .addComponents() + .send(); + } } } diff --git a/src/main/java/com/sidpatchy/clairebot/MessageComponents/Regular/ServerPreferencesComponents.java b/src/main/java/com/sidpatchy/clairebot/MessageComponents/Regular/ServerPreferencesComponents.java index bc421f6..a18afab 100644 --- a/src/main/java/com/sidpatchy/clairebot/MessageComponents/Regular/ServerPreferencesComponents.java +++ b/src/main/java/com/sidpatchy/clairebot/MessageComponents/Regular/ServerPreferencesComponents.java @@ -28,7 +28,7 @@ public static ActionRow getMainMenu(LanguageManager languageManager) { return new ActionRowBuilder() .addComponents( - SelectMenu.create("settings", placeholder, 1, 1, + SelectMenu.create("server-settings", placeholder, 1, 1, Arrays.asList( // Keep values stable for interaction handlers SelectMenuOption.create(requestsLabel, "Requests Channel", requestsDesc), @@ -55,8 +55,9 @@ public static ActionRow getEnforceServerLanguageMenu(LanguageManager languageMan .addComponents( SelectMenu.create("enforceServerLanguage", placeholder, 1, 1, Arrays.asList( - SelectMenuOption.create("True", trueText), - SelectMenuOption.create("False", falseText) + // Localized labels with stable boolean values + SelectMenuOption.create(trueText, "true"), + SelectMenuOption.create(falseText, "false") )) ).build(); } diff --git a/src/main/resources/translations/lang_en-US.yml b/src/main/resources/translations/lang_en-US.yml index 2a16bd8..1817755 100644 --- a/src/main/resources/translations/lang_en-US.yml +++ b/src/main/resources/translations/lang_en-US.yml @@ -226,9 +226,9 @@ ClaireLang: ModeratorChannelDescription: "Only lists the first 25 channels in the server." EnforceServerLanguageMenuName: "Enforce Server Language" AcknowledgeRequestsChannelChangeTitle: "Requests Channel Changed!" - AcknowledgeRequestsChannelChangeDescription: "Your requests channel has been changed to {cb.channel.id.mentiontag}" + AcknowledgeRequestsChannelChangeDescription: "Your requests channel has been changed to {cb.channel.requests.mentiontag}" AcknowledgeModeratorChannelChangeTitle: "Moderator Channel Changed!" - AcknowledgeModeratorChannelChangeDescription: "Your moderator messages channel has been changed to {cb.channel.id.mentiontag}" + AcknowledgeModeratorChannelChangeDescription: "Your moderator messages channel has been changed to {cb.channel.moderator.mentiontag}" AcknowledgeEnforceServerLanguageUpdateTitle: "Server Language Preferences Updated!" AcknowledgeEnforceServerLanguageUpdateEnforced: "I will now follow the server's language regardless of user preference." AcknowledgeEnforceServerLanguageUpdateNotEnforced: "I will follow each individual user's language preference." From 44ba9f13f436277bcc7865009ac99c362eb44fd1 Mon Sep 17 00:00:00 2001 From: Sidpatchy Date: Wed, 15 Oct 2025 21:53:04 -0600 Subject: [PATCH 50/64] fix: regression where LanguageManager attempts to access lang_und.yml --- .../clairebot/Lang/LanguageManager.java | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/sidpatchy/clairebot/Lang/LanguageManager.java b/src/main/java/com/sidpatchy/clairebot/Lang/LanguageManager.java index b9a26c9..7d8103d 100644 --- a/src/main/java/com/sidpatchy/clairebot/Lang/LanguageManager.java +++ b/src/main/java/com/sidpatchy/clairebot/Lang/LanguageManager.java @@ -126,7 +126,19 @@ private RobinConfiguration parseUserAndServerOptions(Server server, User user) { try { APIUser apiUser = new APIUser(user.getIdAsString()); apiUser.getUser(); - locale = Locale.forLanguageTag(apiUser.getLanguage()); + String rawLang = apiUser.getLanguage(); + + // Normalize ClaireData language strings (e.g., en_US -> en-US). If empty/null, use fallback. + if (rawLang == null || rawLang.isBlank()) { + locale = fallbackLocale; + } else { + String normalizedTag = rawLang.replace('_', '-'); + locale = Locale.forLanguageTag(normalizedTag); + // Guard against Locale.ROOT ("und") resulting from invalid tags + if (locale == null || locale.toLanguageTag().equals("und")) { + locale = fallbackLocale; + } + } // todo, pending ClaireData update: allow server admins to specify a custom language string. // todo ref https://trello.com/c/vkQTCTMG @@ -138,7 +150,10 @@ private RobinConfiguration parseUserAndServerOptions(Server server, User user) { // todo this should not be determined here, but will be until the ClaireData implementation is completed. // todo this should instead be determined when the Guild object is created in the database. // todo ClaireData update on hold while still designing the major ClaireBot update that follows this one. - locale = server.getPreferredLocale(); + Locale serverLocale = server.getPreferredLocale(); + if (serverLocale != null) { + locale = serverLocale; + } } } } catch (IOException e) { From f4d570c9e6fce095033a01abc5c7e4ef2181d76f Mon Sep 17 00:00:00 2001 From: Sidpatchy Date: Wed, 15 Oct 2025 21:54:48 -0600 Subject: [PATCH 51/64] chore(release): 3.4.0-alpha.6 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 718bc17..decbcc6 100644 --- a/build.gradle +++ b/build.gradle @@ -14,7 +14,7 @@ jar { } group = 'com.sidpatchy' -version = '3.4.0-alpha.5' +version = '3.4.0-alpha.6' processResources { filesMatching('**/build.properties') { From 33c314f96b317d1bbc94c86323802d11860a9419 Mon Sep 17 00:00:00 2001 From: Sidpatchy Date: Thu, 16 Oct 2025 09:37:49 -0600 Subject: [PATCH 52/64] feat: base64 encode poll and santa IDs to save characters --- .../sidpatchy/clairebot/Util/SantaUtils.java | 32 ++++++++++----- .../clairebot/Util/Voting/VotingUtils.java | 40 +++++++++++++------ 2 files changed, 50 insertions(+), 22 deletions(-) diff --git a/src/main/java/com/sidpatchy/clairebot/Util/SantaUtils.java b/src/main/java/com/sidpatchy/clairebot/Util/SantaUtils.java index 634bca7..4113cd4 100644 --- a/src/main/java/com/sidpatchy/clairebot/Util/SantaUtils.java +++ b/src/main/java/com/sidpatchy/clairebot/Util/SantaUtils.java @@ -7,10 +7,8 @@ import org.javacord.api.entity.message.embed.EmbedFooter; import org.javacord.api.entity.user.User; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; +import java.nio.ByteBuffer; +import java.util.*; public class SantaUtils { public static class ExtractionResult { @@ -60,16 +58,32 @@ public static ExtractionResult extractDataFromEmbed(Embed embed, EmbedFooter foo } public static String getSantaID(String serverID, String authorID, String roleID) { - return serverID + ":" + authorID + ":" + roleID; + long serverIdLong = Long.parseLong(serverID); + long authorIdLong = Long.parseLong(authorID); + long roleIdLong = Long.parseLong(roleID); + + // Pack into bytes: 8 bytes per ID = 24 bytes total + ByteBuffer buffer = ByteBuffer.allocate(24); + buffer.putLong(serverIdLong); + buffer.putLong(authorIdLong); + buffer.putLong(roleIdLong); + + // Base64 encode (URL-safe, no padding) + return Base64.getUrlEncoder().withoutPadding().encodeToString(buffer.array()); } public static HashMap parseSantaID(String id) { - List entries = Arrays.asList(StringUtils.splitPreserveAllTokens(id, ":")); + byte[] bytes = Base64.getUrlDecoder().decode(id); + ByteBuffer buffer = ByteBuffer.wrap(bytes); + + long serverId = buffer.getLong(); + long authorId = buffer.getLong(); + long roleId = buffer.getLong(); return new HashMap<>() {{ - put("serverID", entries.get(0)); - put("authorID", entries.get(1)); - put("roleID", entries.get(2)); + put("serverID", String.valueOf(serverId)); + put("authorID", String.valueOf(authorId)); + put("roleID", String.valueOf(roleId)); }}; } } diff --git a/src/main/java/com/sidpatchy/clairebot/Util/Voting/VotingUtils.java b/src/main/java/com/sidpatchy/clairebot/Util/Voting/VotingUtils.java index aeeca50..91bb69b 100755 --- a/src/main/java/com/sidpatchy/clairebot/Util/Voting/VotingUtils.java +++ b/src/main/java/com/sidpatchy/clairebot/Util/Voting/VotingUtils.java @@ -2,33 +2,47 @@ import org.apache.commons.lang3.StringUtils; +import java.nio.ByteBuffer; import java.util.Arrays; +import java.util.Base64; import java.util.HashMap; import java.util.List; public class VotingUtils { public static String getPollID(Boolean allowMultipleChoices, String authorID, String numChoices) { - StringBuilder pollID = new StringBuilder(System.currentTimeMillis() / 1000L + ":"); + long timestamp = System.currentTimeMillis() / 1000L; + long authorIdLong = Long.parseLong(authorID); + int numChoicesInt = Integer.parseInt(numChoices); - if (allowMultipleChoices) { pollID.append(1); } - else { pollID.append(0); } + // Pack into bytes: 4 bytes timestamp + 8 bytes authorID + 1 byte flags/numChoices + ByteBuffer buffer = ByteBuffer.allocate(13); + buffer.putInt((int) timestamp); // 4 bytes (will work until 2038) + buffer.putLong(authorIdLong); // 8 bytes - pollID.append(":"); - pollID.append(authorID); - pollID.append(":"); - pollID.append(numChoices); + // Pack allowMultipleChoices and numChoices into 1 byte + byte packed = (byte) ((allowMultipleChoices ? 0x80 : 0) | (numChoicesInt & 0x7F)); + buffer.put(packed); - return pollID.toString(); + // Base64 encode (URL-safe, no padding) + return Base64.getUrlEncoder().withoutPadding().encodeToString(buffer.array()); } public static HashMap parsePollID(String pollID) { - List entries = Arrays.asList(StringUtils.splitPreserveAllTokens(pollID, ":")); + byte[] bytes = Base64.getUrlDecoder().decode(pollID); + ByteBuffer buffer = ByteBuffer.wrap(bytes); + + long timestamp = Integer.toUnsignedLong(buffer.getInt()); + long authorId = buffer.getLong(); + byte packed = buffer.get(); + + boolean allowMultipleChoices = (packed & 0x80) != 0; + int numChoices = packed & 0x7F; return new HashMap<>() {{ - put("timestamp", entries.get(0)); - put("allowMultipleChoices", entries.get(1)); - put("authorID", entries.get(2)); - put("numChoices", entries.get(3)); + put("timestamp", String.valueOf(timestamp)); + put("allowMultipleChoices", allowMultipleChoices ? "1" : "0"); + put("authorID", String.valueOf(authorId)); + put("numChoices", String.valueOf(numChoices)); }}; } } \ No newline at end of file From 63cde32c0bd3296bbd3701d88899e087679dc7e1 Mon Sep 17 00:00:00 2001 From: Sidpatchy Date: Thu, 16 Oct 2025 11:00:06 -0600 Subject: [PATCH 53/64] refactor: santa embed generation and logic - Separates concerns in embed construction. - Adds methods for generating embeds from existing pairings. - Implements the ability to randomize pairings by editing the existing embed. - Fixes an issue where the server icon was not properly being set as a footer. - Handles cases where the user is null for language processing. --- .../Embed/Commands/Regular/SantaEmbed.java | 76 ++++++++++++++++++- .../clairebot/Lang/LanguageManager.java | 26 ++++--- .../clairebot/Listener/ButtonClick.java | 6 +- .../clairebot/Listener/ModalSubmit.java | 19 ++++- 4 files changed, 110 insertions(+), 17 deletions(-) diff --git a/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/SantaEmbed.java b/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/SantaEmbed.java index 51b2995..fe615c8 100644 --- a/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/SantaEmbed.java +++ b/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/SantaEmbed.java @@ -42,8 +42,14 @@ public static MessageBuilder getHostMessage(LanguageManager languageManager, Rol EmbedBuilder embed = new EmbedBuilder() .setColor(Main.getColor(author.getIdAsString())) - .setAuthor("SecretClaire", "", "https://github.com/Sidpatchy/ClaireBot/blob/main/img/ClaireBot-SantaHat.png?raw=true") - .setFooter(SantaUtils.getSantaID(server.getIdAsString(), author.getIdAsString(), role.getIdAsString()), server.getIcon().orElse(null)); + .setAuthor("SecretClaire", "", "https://github.com/Sidpatchy/ClaireBot/blob/main/img/ClaireBot-SantaHat.png?raw=true"); + + String footerText = SantaUtils.getSantaID(server.getIdAsString(), author.getIdAsString(), role.getIdAsString()); + if (server.getIcon().isPresent()) { + embed.setFooter(footerText, server.getIcon().get()); + } else { + embed.setFooter(footerText); + } if (!theme.isEmpty()) { embed.addField("Theme", theme, false); @@ -126,4 +132,70 @@ private static HashMap assignSecretSanta(Set participants) { return users; } + + public static EmbedBuilder buildHostEmbedFromPairs(LanguageManager languageManager, Role role, User author, String rules, String theme, java.util.List givers, java.util.List receivers) { + Server server = role.getServer(); + + EmbedBuilder embed = new EmbedBuilder() + .setColor(Main.getColor(author.getIdAsString())) + .setAuthor("SecretClaire", "", "https://github.com/Sidpatchy/ClaireBot/blob/main/img/ClaireBot-SantaHat.png?raw=true"); + + String footerText = SantaUtils.getSantaID(server.getIdAsString(), author.getIdAsString(), role.getIdAsString()); + if (server.getIcon().isPresent()) { + embed.setFooter(footerText, server.getIcon().get()); + } else { + embed.setFooter(footerText); + } + + if (!theme.isEmpty()) { + embed.addField("Theme", theme, false); + } + + if (!rules.isEmpty()) { + embed.addField("Rules", rules, false); + } + + for (int i = 0; i < Math.min(givers.size(), receivers.size()); i++) { + User giver = givers.get(i); + User receiver = receivers.get(i); + embed.addField(giver.getIdAsString(), giver.getNicknameMentionTag() + " → " + receiver.getNicknameMentionTag(), false); + } + + return embed; + } + + // Builds a fresh randomized host embed (re-rolls pairs) and preserves footer/format + public static EmbedBuilder buildHostEmbedRandomized(LanguageManager languageManager, Role role, User author, String rules, String theme) { + Server server = role.getServer(); + + EmbedBuilder embed = new EmbedBuilder() + .setColor(Main.getColor(author.getIdAsString())) + .setAuthor("SecretClaire", "", "https://github.com/Sidpatchy/ClaireBot/blob/main/img/ClaireBot-SantaHat.png?raw=true"); + + String footerText = SantaUtils.getSantaID(server.getIdAsString(), author.getIdAsString(), role.getIdAsString()); + if (server.getIcon().isPresent()) { + embed.setFooter(footerText, server.getIcon().get()); + } else { + embed.setFooter(footerText); + } + + if (!theme.isEmpty()) { + embed.addField("Theme", theme, false); + } + + if (!rules.isEmpty()) { + embed.addField("Rules", rules, false); + } + + // Re-randomize using the role's current users + Set users = role.getUsers(); + HashMap santaList = assignSecretSanta(users); + for (Map.Entry userPair : santaList.entrySet()) { + User giver = userPair.getKey(); + User receiver = userPair.getValue(); + embed.addField(giver.getIdAsString(), giver.getNicknameMentionTag() + " → " + receiver.getNicknameMentionTag(), false); + } + + return embed; + } } diff --git a/src/main/java/com/sidpatchy/clairebot/Lang/LanguageManager.java b/src/main/java/com/sidpatchy/clairebot/Lang/LanguageManager.java index 7d8103d..9611aff 100644 --- a/src/main/java/com/sidpatchy/clairebot/Lang/LanguageManager.java +++ b/src/main/java/com/sidpatchy/clairebot/Lang/LanguageManager.java @@ -124,19 +124,25 @@ public List getLocalizedList(String basePath, String key) { private RobinConfiguration parseUserAndServerOptions(Server server, User user) { Locale locale; try { - APIUser apiUser = new APIUser(user.getIdAsString()); - apiUser.getUser(); - String rawLang = apiUser.getLanguage(); - - // Normalize ClaireData language strings (e.g., en_US -> en-US). If empty/null, use fallback. - if (rawLang == null || rawLang.isBlank()) { + // If user is null (e.g., triggered by a system action or missing context), + // default to fallback locale and continue to evaluate server-level overrides. + if (user == null) { locale = fallbackLocale; } else { - String normalizedTag = rawLang.replace('_', '-'); - locale = Locale.forLanguageTag(normalizedTag); - // Guard against Locale.ROOT ("und") resulting from invalid tags - if (locale == null || locale.toLanguageTag().equals("und")) { + APIUser apiUser = new APIUser(user.getIdAsString()); + apiUser.getUser(); + String rawLang = apiUser.getLanguage(); + + // Normalize ClaireData language strings (e.g., en_US -> en-US). If empty/null, use fallback. + if (rawLang == null || rawLang.isBlank()) { locale = fallbackLocale; + } else { + String normalizedTag = rawLang.replace('_', '-'); + locale = Locale.forLanguageTag(normalizedTag); + // Guard against Locale.ROOT ("und") resulting from invalid tags + if (locale == null || locale.toLanguageTag().equals("und")) { + locale = fallbackLocale; + } } } diff --git a/src/main/java/com/sidpatchy/clairebot/Listener/ButtonClick.java b/src/main/java/com/sidpatchy/clairebot/Listener/ButtonClick.java index a87d995..741891f 100644 --- a/src/main/java/com/sidpatchy/clairebot/Listener/ButtonClick.java +++ b/src/main/java/com/sidpatchy/clairebot/Listener/ButtonClick.java @@ -75,8 +75,10 @@ public void onButtonClick(ButtonClickEvent event) { case "randomize": buttonInteraction.acknowledge(); Role role = Main.getApi().getRoleById(extractionResult.santaID.get("roleID")).orElse(null); - buttonInteraction.getMessage().delete(); - SantaEmbed.getHostMessage(languageManager, role, buttonAuthor, extractionResult.rules, extractionResult.theme).send(buttonAuthor); + // Edit the existing host message in place with a fresh randomized pairing + buttonInteraction.getMessage().edit( + SantaEmbed.buildHostEmbedRandomized(languageManager, role, author, extractionResult.rules, extractionResult.theme) + ); break; diff --git a/src/main/java/com/sidpatchy/clairebot/Listener/ModalSubmit.java b/src/main/java/com/sidpatchy/clairebot/Listener/ModalSubmit.java index 197959e..570e399 100644 --- a/src/main/java/com/sidpatchy/clairebot/Listener/ModalSubmit.java +++ b/src/main/java/com/sidpatchy/clairebot/Listener/ModalSubmit.java @@ -19,9 +19,11 @@ import org.javacord.api.entity.user.User; import org.javacord.api.event.interaction.ModalSubmitEvent; import org.javacord.api.interaction.ModalInteraction; +import org.javacord.api.interaction.callback.InteractionOriginalResponseUpdater; import org.javacord.api.listener.interaction.ModalSubmitListener; import java.util.HashMap; +import java.util.concurrent.CompletableFuture; public class ModalSubmit implements ModalSubmitListener { @@ -127,15 +129,26 @@ else if (voteType.equalsIgnoreCase("poll")) { break; case "santa-rules", "santa-theme": - modalInteraction.createImmediateResponder().respond(); + // Silently defer the modal response (no visible message), then update the existing host message in place + CompletableFuture deferred = modalInteraction.respondLater(); + extractionResult.rules = modalInteraction.getTextInputValueByCustomId("rules-row").orElse(extractionResult.rules); extractionResult.theme = modalInteraction.getTextInputValueByCustomId("theme-row").orElse(extractionResult.theme); Role role = Main.getApi().getRoleById(extractionResult.santaID.get("roleID")).orElse(null); assert role != null; - SantaEmbed.getHostMessage(languageManager, role, user, extractionResult.rules, extractionResult.theme).send(user); - santaMessage.delete(); + // Rebuild the host embed with the existing giver/receiver pairs and edit the original message + santaMessage.edit(SantaEmbed.buildHostEmbedFromPairs(languageManager, role, user, extractionResult.rules, extractionResult.theme, extractionResult.givers, extractionResult.receivers)); + // Finalize and immediately delete the deferred response to avoid leaving a "Bot is thinking…" stub + deferred.thenAccept(interactionOriginalResponseUpdater -> { + try { + interactionOriginalResponseUpdater.setContent("\u200B").update().thenAccept(msg -> { + try { msg.delete(); } catch (Exception ignored2) {} + }); + } catch (Exception ignored) {} + }); + break; } } } From 9ced633d800469bcbcf99f0c1322727f1c10dea5 Mon Sep 17 00:00:00 2001 From: Sidpatchy Date: Thu, 16 Oct 2025 11:23:17 -0600 Subject: [PATCH 54/64] fix: voting and translation issues Addresses potential null pointer exceptions by ensuring context data is properly handled during placeholder replacement. Improves code readability by using `isEmpty()` instead of `length() == 0` for checking empty string builders. Corrects the translation key for the requests channel mention tag. Fixes number emoji reactions for voting options. Improves poll ID parsing and reaction moderation. --- .../Embed/Commands/Regular/VotingEmbed.java | 4 +- .../clairebot/Lang/PlaceholderHandler.java | 8 +-- .../Listener/SlashCommandCreate.java | 31 +++++++-- .../Listener/Voting/ModerateReactions.java | 36 ++++++----- .../clairebot/Util/Voting/VotingUtils.java | 64 ++++++++++++++----- .../resources/translations/lang_en-US.yml | 2 +- 6 files changed, 98 insertions(+), 47 deletions(-) diff --git a/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/VotingEmbed.java b/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/VotingEmbed.java index 2e446f8..7903953 100755 --- a/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/VotingEmbed.java +++ b/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/VotingEmbed.java @@ -64,7 +64,7 @@ public static EmbedBuilder getPoll(LanguageManager languageManager, } } - if (choiceBuilder.length() == 0) { + if (choiceBuilder.isEmpty()) { allowMultipleChoices = false; } else { embed.addField(choicesLabel, choiceBuilder.toString()); @@ -115,7 +115,7 @@ public static EmbedBuilder getPoll(LanguageManager languageManager, public static EmbedBuilder getUserResponse(LanguageManager languageManager, User author, String requestsChannelMentionTag) { // Temp/localized variables block String title = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Regular.VotingEmbed.UserResponseTitle"); - languageManager.addContext(ContextManager.ContextType.GENERIC, "channel.id.mentiontag", requestsChannelMentionTag); + languageManager.addContext(ContextManager.ContextType.GENERIC, "cb.channel.requests.mentiontag", requestsChannelMentionTag); String desc = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Regular.VotingEmbed.UserResponseDescription"); return new EmbedBuilder() diff --git a/src/main/java/com/sidpatchy/clairebot/Lang/PlaceholderHandler.java b/src/main/java/com/sidpatchy/clairebot/Lang/PlaceholderHandler.java index 4e860c7..ba95532 100644 --- a/src/main/java/com/sidpatchy/clairebot/Lang/PlaceholderHandler.java +++ b/src/main/java/com/sidpatchy/clairebot/Lang/PlaceholderHandler.java @@ -112,7 +112,7 @@ private Map initializePlaceholders() { // Command placeholders entry("cb.commandname", () -> - String.valueOf(Objects.requireNonNull(context.getData(ContextManager.ContextType.GENERIC, "commandname")))), + String.valueOf(Objects.requireNonNull((Object) context.getData(ContextManager.ContextType.GENERIC, "commandname")))), entry("cb.user.id.username", () -> Optional.ofNullable(context.author()) .map(org.javacord.api.entity.user.User::getDiscriminatedName) @@ -122,13 +122,13 @@ private Map initializePlaceholders() { })), // Voting placeholders entry("cb.poll.id", () -> - String.valueOf(Objects.requireNonNull(context.getData(ContextManager.ContextType.GENERIC, "poll.id")))), + String.valueOf(Objects.requireNonNull((Object) context.getData(ContextManager.ContextType.GENERIC, "poll.id")))), entry("cb.voting.optionnumber", () -> - String.valueOf(Objects.requireNonNull(context.getData(ContextManager.ContextType.GENERIC, "voting.optionnumber")))), + String.valueOf(Objects.requireNonNull((Object) context.getData(ContextManager.ContextType.GENERIC, "voting.optionnumber")))), // Error code entry("cb.errorcode", () -> - String.valueOf(Objects.requireNonNull(context.getData(ContextManager.ContextType.GENERIC, "errorcode")))) + String.valueOf(Objects.requireNonNull((Object) context.getData(ContextManager.ContextType.GENERIC, "errorcode")))) ); } diff --git a/src/main/java/com/sidpatchy/clairebot/Listener/SlashCommandCreate.java b/src/main/java/com/sidpatchy/clairebot/Listener/SlashCommandCreate.java index a1effca..b2a1a09 100644 --- a/src/main/java/com/sidpatchy/clairebot/Listener/SlashCommandCreate.java +++ b/src/main/java/com/sidpatchy/clairebot/Listener/SlashCommandCreate.java @@ -184,9 +184,17 @@ else if (commandName.equalsIgnoreCase(commands.getPoll().getName())) { slashCommandInteraction.respondLater().thenAccept(interactionOriginalResponseUpdater -> { interactionOriginalResponseUpdater.addEmbed(VotingEmbed.getPoll(languageManager, "POLL", question, allowMultipleChoices, choices, server, author, finalNumChoices)) .update().thenAccept(message -> { - message.addReaction("\uD83D\uDC4D"); // 👍 emoji - message.addReaction("\uD83D\uDC4E"); // 👎 emoji - message.addReaction(":vote:706373563564949566"); // Custom emoji + if (finalNumChoices > 0) { + String[] numberEmojis = new String[]{"1️⃣","2️⃣","3️⃣","4️⃣","5️⃣","6️⃣","7️⃣","8️⃣","9️⃣","🔟"}; + int limit = Math.min(finalNumChoices, numberEmojis.length); + for (int i = 0; i < limit; i++) { + message.addReaction(numberEmojis[i]); + } + } else { + message.addReaction("\uD83D\uDC4D"); // 👍 emoji + message.addReaction("\uD83D\uDC4E"); // 👎 emoji + message.addReaction(":vote:706373563564949566"); // Custom emoji + } }); }); } @@ -269,10 +277,19 @@ else if (commandName.equalsIgnoreCase(commands.getRequest().getName())) { .setFlags(MessageFlag.EPHEMERAL) .respond(); - requestsChannel.sendMessage(VotingEmbed.getPoll(languageManager, "REQUEST", question, allowMultipleChoices, choices, server, author, numChoices)).thenAccept(message -> { - message.addReaction("\uD83D\uDC4D"); - message.addReaction("\uD83D\uDC4E"); - message.addReaction(":vote:706373563564949566"); + final int finalNumChoicesReq = numChoices; + requestsChannel.sendMessage(VotingEmbed.getPoll(languageManager, "REQUEST", question, allowMultipleChoices, choices, server, author, finalNumChoicesReq)).thenAccept(message -> { + if (finalNumChoicesReq > 0) { + String[] numberEmojis = new String[]{"1️⃣","2️⃣","3️⃣","4️⃣","5️⃣","6️⃣","7️⃣","8️⃣","9️⃣","🔟"}; + int limit = Math.min(finalNumChoicesReq, numberEmojis.length); + for (int i = 0; i < limit; i++) { + message.addReaction(numberEmojis[i]); + } + } else { + message.addReaction("\uD83D\uDC4D"); + message.addReaction("\uD83D\uDC4E"); + message.addReaction(":vote:706373563564949566"); + } }); } } diff --git a/src/main/java/com/sidpatchy/clairebot/Listener/Voting/ModerateReactions.java b/src/main/java/com/sidpatchy/clairebot/Listener/Voting/ModerateReactions.java index 163c6a0..8e0a816 100644 --- a/src/main/java/com/sidpatchy/clairebot/Listener/Voting/ModerateReactions.java +++ b/src/main/java/com/sidpatchy/clairebot/Listener/Voting/ModerateReactions.java @@ -32,25 +32,27 @@ public void onReactionAdd(ReactionAddEvent event) { assert footer != null; String footerText = footer.getText().orElse(null); - if (footerText != null && footerText.contains("Poll ID")) { - HashMap pollID = VotingUtils.parsePollID(footerText.replace("Poll ID: ", "")); - boolean allowMultipleChoices = pollID.get("allowMultipleChoices").equalsIgnoreCase("1"); - - if (!allowMultipleChoices) { - String emote = event.getEmoji().asUnicodeEmoji().orElse(null); - User author = event.getUser().orElse(null); - - assert author != null; - if (author.isYourself()) { - return; - } - - List reacts = message.getReactions(); + if (footerText != null) { + String encoded = VotingUtils.extractPollIdFromFooter(footerText); + if (!encoded.isEmpty()) { + HashMap pollID = VotingUtils.parsePollID(encoded); + boolean allowMultipleChoices = pollID.get("allowMultipleChoices").equalsIgnoreCase("1"); + + if (!allowMultipleChoices) { + String emote = event.getEmoji().asUnicodeEmoji().orElse(null); + User author = event.getUser().orElse(null); + + assert author != null; + if (author.isYourself()) { + return; + } - for (Reaction reaction : reacts) { + List reacts = message.getReactions(); - if (emote != null && !emote.equalsIgnoreCase(reaction.getEmoji().asUnicodeEmoji().orElse(null))) { - reaction.removeUser(author); + for (Reaction reaction : reacts) { + if (emote != null && !emote.equalsIgnoreCase(reaction.getEmoji().asUnicodeEmoji().orElse(null))) { + reaction.removeUser(author); + } } } } diff --git a/src/main/java/com/sidpatchy/clairebot/Util/Voting/VotingUtils.java b/src/main/java/com/sidpatchy/clairebot/Util/Voting/VotingUtils.java index 91bb69b..94d1fcc 100755 --- a/src/main/java/com/sidpatchy/clairebot/Util/Voting/VotingUtils.java +++ b/src/main/java/com/sidpatchy/clairebot/Util/Voting/VotingUtils.java @@ -27,22 +27,54 @@ public static String getPollID(Boolean allowMultipleChoices, String authorID, St return Base64.getUrlEncoder().withoutPadding().encodeToString(buffer.array()); } + /** + * Extracts the poll ID from a footer string in a locale-agnostic way. + * Strategy: take the substring after the last colon if present, then the last whitespace-delimited token. + */ + public static String extractPollIdFromFooter(String footerText) { + if (footerText == null) return ""; + String s = footerText; + int colon = s.lastIndexOf(':'); + if (colon >= 0 && colon + 1 < s.length()) { + s = s.substring(colon + 1); + } + s = s.trim(); + int space = s.lastIndexOf(' '); + if (space >= 0 && space + 1 < s.length()) { + s = s.substring(space + 1).trim(); + } + return s; + } + public static HashMap parsePollID(String pollID) { - byte[] bytes = Base64.getUrlDecoder().decode(pollID); - ByteBuffer buffer = ByteBuffer.wrap(bytes); - - long timestamp = Integer.toUnsignedLong(buffer.getInt()); - long authorId = buffer.getLong(); - byte packed = buffer.get(); - - boolean allowMultipleChoices = (packed & 0x80) != 0; - int numChoices = packed & 0x7F; - - return new HashMap<>() {{ - put("timestamp", String.valueOf(timestamp)); - put("allowMultipleChoices", allowMultipleChoices ? "1" : "0"); - put("authorID", String.valueOf(authorId)); - put("numChoices", String.valueOf(numChoices)); - }}; + try { + byte[] bytes = Base64.getUrlDecoder().decode(pollID); + if (bytes.length < 13) { + throw new IllegalArgumentException("Unexpected poll ID length: " + bytes.length); + } + ByteBuffer buffer = ByteBuffer.wrap(bytes); + + long timestamp = Integer.toUnsignedLong(buffer.getInt()); + long authorId = buffer.getLong(); + byte packed = buffer.get(); + + boolean allowMultipleChoices = (packed & 0x80) != 0; + int numChoices = packed & 0x7F; + + return new HashMap<>() {{ + put("timestamp", String.valueOf(timestamp)); + put("allowMultipleChoices", allowMultipleChoices ? "1" : "0"); + put("authorID", String.valueOf(authorId)); + put("numChoices", String.valueOf(numChoices)); + }}; + } catch (Exception e) { + // Graceful fallback: default to allowing multiple choices to avoid over-restricting users + return new HashMap<>() {{ + put("timestamp", "0"); + put("allowMultipleChoices", "1"); + put("authorID", "0"); + put("numChoices", "0"); + }}; + } } } \ No newline at end of file diff --git a/src/main/resources/translations/lang_en-US.yml b/src/main/resources/translations/lang_en-US.yml index 1817755..37d698b 100644 --- a/src/main/resources/translations/lang_en-US.yml +++ b/src/main/resources/translations/lang_en-US.yml @@ -253,7 +253,7 @@ ClaireLang: Choices: "Choices" PollID: "Poll ID: {cb.poll.id}" UserResponseTitle: "Your request has been created!" - UserResponseDescription: "Go check it out in {cb.channel.id.mentiontag}" + UserResponseDescription: "Go check it out in {cb.channel.requests.mentiontag}" ErrorEmbed: Error: "ERROR" GenericDescription: "It appears that I've encountered an error, oops! Please try running the command once more and if that doesn't work, join my [Discord server]({cb.supportserver) and let us know about the issue.\n\nPlease include the following error code: {cb.errorcode}" From ea4f3692c8716a036c779f5e5a18923be627849e Mon Sep 17 00:00:00 2001 From: Sidpatchy Date: Thu, 16 Oct 2025 11:25:16 -0600 Subject: [PATCH 55/64] chore(release): 3.4.0-alpha.7 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index decbcc6..500785c 100644 --- a/build.gradle +++ b/build.gradle @@ -14,7 +14,7 @@ jar { } group = 'com.sidpatchy' -version = '3.4.0-alpha.6' +version = '3.4.0-alpha.7' processResources { filesMatching('**/build.properties') { From a9939a9b0106de2263126b158b7be9bd304e6382 Mon Sep 17 00:00:00 2001 From: Sidpatchy Date: Thu, 16 Oct 2025 12:27:46 -0600 Subject: [PATCH 56/64] refactor: code cleanup --- .../com/sidpatchy/clairebot/API/APIUser.java | 4 +-- .../com/sidpatchy/clairebot/Clockwork.java | 1 - .../Embed/Commands/Regular/InfoEmbed.java | 2 -- .../Embed/Commands/Regular/QuoteEmbed.java | 3 +- .../Commands/Regular/ServerInfoEmbed.java | 3 +- .../Regular/UserPreferencesEmbed.java | 3 +- .../clairebot/Embed/WelcomeEmbed.java | 2 +- .../clairebot/Listener/MessageCreate.java | 5 ++- .../clairebot/Listener/ServerJoin.java | 1 - .../Listener/SlashCommandCreate.java | 3 +- .../java/com/sidpatchy/clairebot/Main.java | 33 ++++++++----------- .../MessageComponents/Regular/SantaModal.java | 1 - .../Regular/UserPreferencesComponents.java | 1 + .../sidpatchy/clairebot/Util/SantaUtils.java | 6 ++-- .../clairebot/Util/Voting/VotingUtils.java | 4 --- src/main/resources/config.yml | 3 -- 16 files changed, 30 insertions(+), 45 deletions(-) diff --git a/src/main/java/com/sidpatchy/clairebot/API/APIUser.java b/src/main/java/com/sidpatchy/clairebot/API/APIUser.java index d98492d..9f7415a 100644 --- a/src/main/java/com/sidpatchy/clairebot/API/APIUser.java +++ b/src/main/java/com/sidpatchy/clairebot/API/APIUser.java @@ -1,7 +1,5 @@ package com.sidpatchy.clairebot.API; -import tools.jackson.databind.ObjectMapper; -import tools.jackson.databind.node.ObjectNode; import com.sidpatchy.Robin.File.RobinConfiguration; import com.sidpatchy.clairebot.Main; import com.sidpatchy.clairebot.Util.Leveling.LevelingTools; @@ -9,6 +7,8 @@ import com.sidpatchy.clairebot.Util.Network.POST; import com.sidpatchy.clairebot.Util.Network.PUT; import com.sidpatchy.clairebot.Util.Network.UrlBuilder; +import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.node.ObjectNode; import java.io.IOException; import java.io.InputStreamReader; diff --git a/src/main/java/com/sidpatchy/clairebot/Clockwork.java b/src/main/java/com/sidpatchy/clairebot/Clockwork.java index d980c3f..ba2e84c 100644 --- a/src/main/java/com/sidpatchy/clairebot/Clockwork.java +++ b/src/main/java/com/sidpatchy/clairebot/Clockwork.java @@ -31,7 +31,6 @@ public static void initClockwork() { } -@SuppressWarnings("unchecked") class Helper extends TimerTask { RobinConfiguration config = new RobinConfiguration(); diff --git a/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/InfoEmbed.java b/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/InfoEmbed.java index bcc244b..0b9f7b2 100644 --- a/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/InfoEmbed.java +++ b/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/InfoEmbed.java @@ -1,9 +1,7 @@ package com.sidpatchy.clairebot.Embed.Commands.Regular; -import com.sidpatchy.clairebot.Lang.ContextManager; import com.sidpatchy.clairebot.Lang.LanguageManager; import com.sidpatchy.clairebot.Main; -import org.apache.commons.lang3.time.DurationFormatUtils; import org.javacord.api.entity.message.embed.EmbedBuilder; import org.javacord.api.entity.user.User; diff --git a/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/QuoteEmbed.java b/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/QuoteEmbed.java index c18d0ea..7dc1d72 100644 --- a/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/QuoteEmbed.java +++ b/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/QuoteEmbed.java @@ -1,10 +1,9 @@ package com.sidpatchy.clairebot.Embed.Commands.Regular; + import com.sidpatchy.clairebot.Embed.ErrorEmbed; import com.sidpatchy.clairebot.Main; -import org.javacord.api.entity.Icon; import org.javacord.api.entity.channel.TextChannel; import org.javacord.api.entity.message.Message; -import org.javacord.api.entity.message.MessageBuilder; import org.javacord.api.entity.message.embed.EmbedBuilder; import org.javacord.api.entity.message.embed.EmbedFooter; import org.javacord.api.entity.server.Server; diff --git a/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/ServerInfoEmbed.java b/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/ServerInfoEmbed.java index ce93689..b8b4284 100644 --- a/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/ServerInfoEmbed.java +++ b/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/ServerInfoEmbed.java @@ -4,7 +4,8 @@ import com.sidpatchy.clairebot.Main; import org.javacord.api.entity.message.embed.EmbedBuilder; import org.javacord.api.entity.server.Server; -import java.awt.Color; + +import java.awt.*; public class ServerInfoEmbed { diff --git a/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/UserPreferencesEmbed.java b/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/UserPreferencesEmbed.java index c097f9c..cbc8fd0 100644 --- a/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/UserPreferencesEmbed.java +++ b/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/UserPreferencesEmbed.java @@ -2,13 +2,12 @@ import com.sidpatchy.clairebot.API.APIUser; import com.sidpatchy.clairebot.Embed.ErrorEmbed; -import com.sidpatchy.clairebot.Lang.ContextManager; import com.sidpatchy.clairebot.Lang.LanguageManager; import com.sidpatchy.clairebot.Main; import org.javacord.api.entity.message.embed.EmbedBuilder; import org.javacord.api.entity.user.User; -import java.awt.Color; +import java.awt.*; public class UserPreferencesEmbed { diff --git a/src/main/java/com/sidpatchy/clairebot/Embed/WelcomeEmbed.java b/src/main/java/com/sidpatchy/clairebot/Embed/WelcomeEmbed.java index 969f29d..9caabb8 100644 --- a/src/main/java/com/sidpatchy/clairebot/Embed/WelcomeEmbed.java +++ b/src/main/java/com/sidpatchy/clairebot/Embed/WelcomeEmbed.java @@ -11,7 +11,7 @@ import java.io.IOException; public class WelcomeEmbed { - private static Logger logger = Main.getLogger(); + private static final Logger logger = Main.getLogger(); public static EmbedBuilder getWelcome(LanguageManager languageManager, Server server) { diff --git a/src/main/java/com/sidpatchy/clairebot/Listener/MessageCreate.java b/src/main/java/com/sidpatchy/clairebot/Listener/MessageCreate.java index 7e2738e..8511a9e 100644 --- a/src/main/java/com/sidpatchy/clairebot/Listener/MessageCreate.java +++ b/src/main/java/com/sidpatchy/clairebot/Listener/MessageCreate.java @@ -16,7 +16,10 @@ import org.javacord.api.listener.message.MessageCreateListener; import java.io.IOException; -import java.util.*; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Random; import java.util.concurrent.ThreadLocalRandom; import java.util.regex.Pattern; diff --git a/src/main/java/com/sidpatchy/clairebot/Listener/ServerJoin.java b/src/main/java/com/sidpatchy/clairebot/Listener/ServerJoin.java index c868942..db100c8 100644 --- a/src/main/java/com/sidpatchy/clairebot/Listener/ServerJoin.java +++ b/src/main/java/com/sidpatchy/clairebot/Listener/ServerJoin.java @@ -1,7 +1,6 @@ package com.sidpatchy.clairebot.Listener; import com.sidpatchy.clairebot.Embed.WelcomeEmbed; -import com.sidpatchy.clairebot.Lang.ContextManager; import com.sidpatchy.clairebot.Lang.LanguageManager; import com.sidpatchy.clairebot.Main; import com.sidpatchy.clairebot.Util.ChannelUtils; diff --git a/src/main/java/com/sidpatchy/clairebot/Listener/SlashCommandCreate.java b/src/main/java/com/sidpatchy/clairebot/Listener/SlashCommandCreate.java index b2a1a09..df2d731 100644 --- a/src/main/java/com/sidpatchy/clairebot/Listener/SlashCommandCreate.java +++ b/src/main/java/com/sidpatchy/clairebot/Listener/SlashCommandCreate.java @@ -11,8 +11,8 @@ import com.sidpatchy.clairebot.MessageComponents.Regular.VotingComponents; import com.sidpatchy.clairebot.Util.ChannelUtils; import org.apache.logging.log4j.Logger; -import org.javacord.api.entity.channel.TextChannel; import org.javacord.api.entity.channel.ServerTextChannel; +import org.javacord.api.entity.channel.TextChannel; import org.javacord.api.entity.message.MessageFlag; import org.javacord.api.entity.message.component.ActionRow; import org.javacord.api.entity.message.component.Button; @@ -29,7 +29,6 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; -import java.util.Locale; import java.util.concurrent.CompletableFuture; public class SlashCommandCreate implements SlashCommandCreateListener { diff --git a/src/main/java/com/sidpatchy/clairebot/Main.java b/src/main/java/com/sidpatchy/clairebot/Main.java index f8e7874..5655364 100644 --- a/src/main/java/com/sidpatchy/clairebot/Main.java +++ b/src/main/java/com/sidpatchy/clairebot/Main.java @@ -16,25 +16,27 @@ import java.awt.*; import java.io.IOException; import java.io.InputStream; -import java.util.*; import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Properties; /** * ClaireBot - Simply the best. - * Copyright (C) 2021 Sidpatchy - * + * Copyright (C) 2021 Sidpatchy + *

* 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; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + *

* You should have received a copy of the GNU General Public License - * along with this program. If not, see . + * along with this program. If not, see <...>. * * @since April 2020 * @version 3.4.0-SNAPSHOT @@ -57,7 +59,6 @@ public class Main { private static Map guildDefaults; // Various parameters extracted from config files - private static String botName; private static String color; private static String errorColor; private static List errorGifs; @@ -95,7 +96,7 @@ public class Main { private static String documentationWebsite; private static String inviteLink; - public static void main(String[] args) throws InvalidConfigurationException { + static void main(String[] args) throws InvalidConfigurationException { logger.info("ClaireBot loading..."); // Make sure required resources are loaded @@ -126,7 +127,7 @@ public static void main(String[] args) throws InvalidConfigurationException { System.exit(2); } else { - logger.info("Successfully connected to Discord on shard " + current_shard + " with a total shard count of " + total_shards); + logger.info("Successfully connected to Discord on shard {} with a total shard count of {}", current_shard, total_shards); } Clockwork.initClockwork(); @@ -186,7 +187,6 @@ public static void extractParametersFromConfig(boolean updateOutdatedConfigs) { logger.info("Loading configuration files..."); try { - botName = config.getString("botName"); apiPath = config.getString("apiPath"); apiUser = config.getString("apiUser"); apiPassword = config.getString("apiPassword"); @@ -214,8 +214,7 @@ public static void extractParametersFromConfig(boolean updateOutdatedConfigs) { inviteLink = config.getString("clairebot.inviteLink"); } catch (Exception e) { - e.printStackTrace(); - logger.error("There was an error while extracting parameters from the config. This isn't fatal but there's a good chance things will be very broken."); + logger.error("There was an error while extracting parameters from the config. This isn't fatal but there's a good chance things will be very broken.", e); } } @@ -255,9 +254,7 @@ public static void verifyDatabaseConnectivity() { APIUser api = new APIUser("12345"); api.getALLUsers(); } catch (IOException e) { - e.printStackTrace(); - logger.error("ClaireBot was unable to access the APIUser table. See previous errors for more details."); - logger.error("This isn't strictly fatal, but things WILL be very broken."); + logger.error("ClaireBot was unable to access the APIUser table.", e); } // test Guild connectivity @@ -265,9 +262,7 @@ public static void verifyDatabaseConnectivity() { Guild api = new Guild("12345"); api.getALLGuilds(); } catch (IOException e) { - e.printStackTrace(); - logger.error("ClaireBot was unable to access the Guild table. See previous errors for more details."); - logger.error("This isn't strictly fatal, but things WILL be very broken."); + logger.error("ClaireBot was unable to access the Guild table.", e); } } @@ -353,8 +348,6 @@ public static String getErrorCode(String descriptor) { public static DiscordApi getApi() { return api; } - public static List getVoteEmoji() { return Arrays.asList("1️⃣", "2️⃣", "3️⃣", "4️⃣", "5️⃣", "6️⃣", "7️⃣", "8️⃣", "9️⃣", "🔟", "\uD83D\uDC4D", "\uD83D\uDC4E"); } - public static long getStartMillis() { return startMillis; } public static String getTranslationsPath() { diff --git a/src/main/java/com/sidpatchy/clairebot/MessageComponents/Regular/SantaModal.java b/src/main/java/com/sidpatchy/clairebot/MessageComponents/Regular/SantaModal.java index 6ed312d..51cd253 100644 --- a/src/main/java/com/sidpatchy/clairebot/MessageComponents/Regular/SantaModal.java +++ b/src/main/java/com/sidpatchy/clairebot/MessageComponents/Regular/SantaModal.java @@ -4,7 +4,6 @@ import org.javacord.api.entity.message.component.ActionRow; import org.javacord.api.entity.message.component.TextInput; import org.javacord.api.entity.message.component.TextInputStyle; -import org.javacord.api.entity.user.User; public class SantaModal { diff --git a/src/main/java/com/sidpatchy/clairebot/MessageComponents/Regular/UserPreferencesComponents.java b/src/main/java/com/sidpatchy/clairebot/MessageComponents/Regular/UserPreferencesComponents.java index 223f7f2..cbaeef9 100644 --- a/src/main/java/com/sidpatchy/clairebot/MessageComponents/Regular/UserPreferencesComponents.java +++ b/src/main/java/com/sidpatchy/clairebot/MessageComponents/Regular/UserPreferencesComponents.java @@ -2,6 +2,7 @@ import com.sidpatchy.clairebot.Lang.LanguageManager; import org.javacord.api.entity.message.component.*; + import java.util.*; public class UserPreferencesComponents { diff --git a/src/main/java/com/sidpatchy/clairebot/Util/SantaUtils.java b/src/main/java/com/sidpatchy/clairebot/Util/SantaUtils.java index 4113cd4..75254ac 100644 --- a/src/main/java/com/sidpatchy/clairebot/Util/SantaUtils.java +++ b/src/main/java/com/sidpatchy/clairebot/Util/SantaUtils.java @@ -1,14 +1,16 @@ package com.sidpatchy.clairebot.Util; import com.sidpatchy.clairebot.Main; -import org.apache.commons.lang3.StringUtils; import org.javacord.api.entity.message.embed.Embed; import org.javacord.api.entity.message.embed.EmbedField; import org.javacord.api.entity.message.embed.EmbedFooter; import org.javacord.api.entity.user.User; import java.nio.ByteBuffer; -import java.util.*; +import java.util.ArrayList; +import java.util.Base64; +import java.util.HashMap; +import java.util.List; public class SantaUtils { public static class ExtractionResult { diff --git a/src/main/java/com/sidpatchy/clairebot/Util/Voting/VotingUtils.java b/src/main/java/com/sidpatchy/clairebot/Util/Voting/VotingUtils.java index 94d1fcc..0593368 100755 --- a/src/main/java/com/sidpatchy/clairebot/Util/Voting/VotingUtils.java +++ b/src/main/java/com/sidpatchy/clairebot/Util/Voting/VotingUtils.java @@ -1,12 +1,8 @@ package com.sidpatchy.clairebot.Util.Voting; -import org.apache.commons.lang3.StringUtils; - import java.nio.ByteBuffer; -import java.util.Arrays; import java.util.Base64; import java.util.HashMap; -import java.util.List; public class VotingUtils { public static String getPollID(Boolean allowMultipleChoices, String authorID, String numChoices) { diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 38a541a..995aa50 100755 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -9,9 +9,6 @@ # Discord bot token, get one at https://discord.com/developers/applications token: -# Name of bot to be used in logs and -botName: ClaireBot # TODO does this even do anything? - # URL of the YouTube video ClaireBot claims to be streaming video_url: https://www.youtube.com/watch?v=AeZRYhLDLeU From 1e0457e30add6fb44be9e52a9accd0b543ee5b39 Mon Sep 17 00:00:00 2001 From: Sidpatchy Date: Thu, 16 Oct 2025 18:55:04 -0600 Subject: [PATCH 57/64] refactor: remove deprecated ErrorEmbed methods --- .../Embed/Commands/Regular/HelpEmbed.java | 2 +- .../Commands/Regular/LeaderboardEmbed.java | 10 +++--- .../Embed/Commands/Regular/QuoteEmbed.java | 7 ++-- .../Regular/ServerPreferencesEmbed.java | 12 +++---- .../Embed/Commands/Regular/UserInfoEmbed.java | 2 +- .../Regular/UserPreferencesEmbed.java | 3 +- .../sidpatchy/clairebot/Embed/ErrorEmbed.java | 32 ------------------- .../clairebot/Listener/ModalSubmit.java | 6 ++-- .../clairebot/Listener/SelectMenuChoose.java | 2 +- .../Listener/SlashCommandCreate.java | 31 +++++++++--------- 10 files changed, 37 insertions(+), 70 deletions(-) diff --git a/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/HelpEmbed.java b/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/HelpEmbed.java index 92da24a..92d076f 100644 --- a/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/HelpEmbed.java +++ b/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/HelpEmbed.java @@ -63,7 +63,7 @@ private static EmbedBuilder buildCommandDetailEmbed(String commandName, String u languageManager.addContext(ContextManager.ContextType.GENERIC, "errorcode", errorCode); String errorLangString = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Regular.HelpEmbed.Error"); Main.getLogger().error(errorLangString); - return ErrorEmbed.getError(errorCode); + return ErrorEmbed.getError(languageManager, errorCode); } else { return new EmbedBuilder() .setColor(Main.getColor(userID)) diff --git a/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/LeaderboardEmbed.java b/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/LeaderboardEmbed.java index 5717208..7f1a11b 100644 --- a/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/LeaderboardEmbed.java +++ b/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/LeaderboardEmbed.java @@ -26,11 +26,10 @@ public static EmbedBuilder getLeaderboard(LanguageManager languageManager, Serve try { unsortedLevelMap = LevelingTools.rankUsers(serverID); } catch (IOException e) { - e.printStackTrace(); String errorCode = Main.getErrorCode("loclead"); - Main.getLogger().error("Failed to query database while generating leaderboard. Ref: " + errorCode); - return ErrorEmbed.getError(errorCode); + Main.getLogger().error("Failed to query database while generating leaderboard. Ref: {}", errorCode, e); + return ErrorEmbed.getError(languageManager, errorCode); } Map namedMap = convertUserIDstoNames(unsortedLevelMap); Map sortedLevelMap = sortMap(namedMap); @@ -48,11 +47,10 @@ public static EmbedBuilder getLeaderboard(LanguageManager languageManager, Strin try { unsortedLevelMap = LevelingTools.rankUsers(serverID); } catch (IOException e) { - e.printStackTrace(); String errorCode = Main.getErrorCode("globlead"); - Main.getLogger().error("Failed to query database while generating leaderboard. Ref: " + errorCode); - return ErrorEmbed.getError(errorCode); + Main.getLogger().error("Failed to query database while generating leaderboard. Ref: {}", errorCode, e); + return ErrorEmbed.getError(languageManager, errorCode); } Map namedMap = convertUserIDstoNames(unsortedLevelMap); Map sortedLevelMap = sortMap(namedMap); diff --git a/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/QuoteEmbed.java b/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/QuoteEmbed.java index 7dc1d72..032a708 100644 --- a/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/QuoteEmbed.java +++ b/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/QuoteEmbed.java @@ -1,6 +1,7 @@ package com.sidpatchy.clairebot.Embed.Commands.Regular; import com.sidpatchy.clairebot.Embed.ErrorEmbed; +import com.sidpatchy.clairebot.Lang.LanguageManager; import com.sidpatchy.clairebot.Main; import org.javacord.api.entity.channel.TextChannel; import org.javacord.api.entity.message.Message; @@ -23,7 +24,7 @@ public class QuoteEmbed { * @param channel the text channel where the messages are located * @return a CompletableFuture that resolves to an EmbedBuilder containing the quote */ - public static CompletableFuture getQuote(Server server, final User user, TextChannel channel) { + public static CompletableFuture getQuote(LanguageManager languageManager, Server server, final User user, TextChannel channel) { return channel.getMessages(50000).thenApply(messages -> { List userMessages = new java.util.ArrayList<>(messages.stream() @@ -32,7 +33,7 @@ public static CompletableFuture getQuote(Server server, final User if (userMessages.isEmpty()) { // user not sent messages - return ErrorEmbed.getError(Main.getErrorCode("UserNotInSet")); + return ErrorEmbed.getError(languageManager, Main.getErrorCode("UserNotInSet")); } Random random = new Random(); @@ -72,7 +73,7 @@ public static CompletableFuture getQuote(Server server, final User // Fallback for if all messages checked were invalid if (!messageSelected) { - embed = ErrorEmbed.getCustomError(Main.getErrorCode("invalidMessages"), + embed = ErrorEmbed.getCustomError(languageManager, Main.getErrorCode("invalidMessages"), "Looks like the messages I selected were invalid. Please try again later."); } diff --git a/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/ServerPreferencesEmbed.java b/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/ServerPreferencesEmbed.java index 3ffed4f..bb444b0 100644 --- a/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/ServerPreferencesEmbed.java +++ b/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/ServerPreferencesEmbed.java @@ -23,7 +23,7 @@ public static EmbedBuilder getNotServerMenu(LanguageManager languageManager) { // Temp/localized variables String notServerMsg = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Regular.ServerPreferencesEmbed.NotAServer"); - return ErrorEmbed.getCustomError(Main.getErrorCode("notaserver"), notServerMsg); + return ErrorEmbed.getCustomError(languageManager, Main.getErrorCode("notaserver"), notServerMsg); } public static EmbedBuilder getRequestsChannelMenu(LanguageManager languageManager, User author) { @@ -55,7 +55,7 @@ public static EmbedBuilder getAcknowledgeRequestsChannelChange(LanguageManager l // Resolve channel ServerTextChannel channel = Main.getApi().getServerTextChannelById(requestsChannelID).orElse(null); if (channel == null) { - return ErrorEmbed.getError(Main.getErrorCode("channelNotExists")); + return ErrorEmbed.getError(languageManager, Main.getErrorCode("channelNotExists")); } try { @@ -75,7 +75,7 @@ public static EmbedBuilder getAcknowledgeRequestsChannelChange(LanguageManager l .setDescription(desc); } catch (Exception e) { e.printStackTrace(); - return ErrorEmbed.getError(Main.getErrorCode("updateRequestsChannel")); + return ErrorEmbed.getError(languageManager, Main.getErrorCode("updateRequestsChannel")); } } @@ -83,7 +83,7 @@ public static EmbedBuilder getAcknowledgeModeratorChannelChange(LanguageManager // Resolve channel ServerTextChannel channel = Main.getApi().getServerTextChannelById(moderatorChannelID).orElse(null); if (channel == null) { - return ErrorEmbed.getError(Main.getErrorCode("channelNotExists")); + return ErrorEmbed.getError(languageManager, Main.getErrorCode("channelNotExists")); } try { @@ -103,7 +103,7 @@ public static EmbedBuilder getAcknowledgeModeratorChannelChange(LanguageManager .setDescription(desc); } catch (Exception e) { e.printStackTrace(); - return ErrorEmbed.getError(Main.getErrorCode("updateModeratorChannel")); + return ErrorEmbed.getError(languageManager, Main.getErrorCode("updateModeratorChannel")); } } @@ -128,7 +128,7 @@ public static EmbedBuilder getAcknowledgeEnforceServerLanguageUpdate(LanguageMan } catch (Exception e) { e.printStackTrace(); - return ErrorEmbed.getError(Main.getErrorCode("updateEnforceServerLang")); + return ErrorEmbed.getError(languageManager, Main.getErrorCode("updateEnforceServerLang")); } } diff --git a/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/UserInfoEmbed.java b/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/UserInfoEmbed.java index 5a55183..79adc93 100644 --- a/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/UserInfoEmbed.java +++ b/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/UserInfoEmbed.java @@ -35,7 +35,7 @@ public static EmbedBuilder getUser(LanguageManager languageManager, User user, U String err2 = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Regular.UserInfoEmbed.Error_2"); Main.getLogger().error(err1); Main.getLogger().error(err2); - return ErrorEmbed.getError(errorCode); + return ErrorEmbed.getError(languageManager, errorCode); } String footerText = author.getDiscriminatedName() + " (" + author.getIdAsString() + ")"; diff --git a/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/UserPreferencesEmbed.java b/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/UserPreferencesEmbed.java index cbc8fd0..d94fbb1 100644 --- a/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/UserPreferencesEmbed.java +++ b/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/UserPreferencesEmbed.java @@ -62,8 +62,7 @@ public static EmbedBuilder getAcknowledgeAccentColourChange(LanguageManager lang .setAuthor(title) .setDescription(desc); } catch (Exception e) { - e.printStackTrace(); - return ErrorEmbed.getError(Main.getErrorCode("updateAccentColour")); + return ErrorEmbed.getError(languageManager, Main.getErrorCode("updateAccentColour")); } } diff --git a/src/main/java/com/sidpatchy/clairebot/Embed/ErrorEmbed.java b/src/main/java/com/sidpatchy/clairebot/Embed/ErrorEmbed.java index 2f4f468..7cc0d18 100755 --- a/src/main/java/com/sidpatchy/clairebot/Embed/ErrorEmbed.java +++ b/src/main/java/com/sidpatchy/clairebot/Embed/ErrorEmbed.java @@ -80,36 +80,4 @@ public static EmbedBuilder getLackingPermissions(LanguageManager languageManager String errorCode = Main.getErrorCode("noPerms"); return getCustomError(languageManager, errorCode, message); } - - // TODO - remove these legacy methods - - @Deprecated - public static EmbedBuilder getError(String errorCode) { - ArrayList errorGifs = (ArrayList) Main.getErrorGifs(); - int rand = new Random().nextInt(errorGifs.size()); - - return new EmbedBuilder() - .setColor(Main.getErrorColor()) - .setAuthor("ERROR") - .setDescription("It appears that I've encountered an error, oops! Please try running the command once more and if that doesn't work, join my [Discord server](https://support.clairebot.net/) and let us know about the issue." - + "\n\nPlease include the following error code: " + errorCode) - .setImage(errorGifs.get(rand)); - } - - @Deprecated - public static EmbedBuilder getError(String errorCode, String customMessage) { - return getError(errorCode).setDescription(customMessage + "\n\nPlease try running the command once more and if that doesn't work, join my [Discord server](https://support.clairebot.net/) and let us know about the issue." - + "\n\nPlease include the following error code: " + errorCode); - } - - @Deprecated - public static EmbedBuilder getCustomError(String errorCode, String message) { - return getError(errorCode).setDescription(message); - } - - @Deprecated - public static EmbedBuilder getLackingPermissions(String message) { - // Fixed: remove accidental double getErrorCode call - return getCustomError(Main.getErrorCode("noPerms"), message); - } } diff --git a/src/main/java/com/sidpatchy/clairebot/Listener/ModalSubmit.java b/src/main/java/com/sidpatchy/clairebot/Listener/ModalSubmit.java index 570e399..9f78e43 100644 --- a/src/main/java/com/sidpatchy/clairebot/Listener/ModalSubmit.java +++ b/src/main/java/com/sidpatchy/clairebot/Listener/ModalSubmit.java @@ -3,6 +3,7 @@ import com.sidpatchy.clairebot.Embed.Commands.Regular.SantaEmbed; import com.sidpatchy.clairebot.Embed.Commands.Regular.UserPreferencesEmbed; import com.sidpatchy.clairebot.Embed.Commands.Regular.VotingEmbed; +import com.sidpatchy.clairebot.Embed.ErrorEmbed; import com.sidpatchy.clairebot.Lang.ContextManager; import com.sidpatchy.clairebot.Lang.LanguageManager; import com.sidpatchy.clairebot.Main; @@ -70,7 +71,7 @@ else if (modalID.startsWith("santa-theme")) { santaMessage = Main.getApi().getCachedMessageById(santaMessageID).orElse(null); assert santaMessage != null; - Embed embed = santaMessage.getEmbeds().get(0); + Embed embed = santaMessage.getEmbeds().getFirst(); EmbedFooter footer = embed.getFooter().orElse(null); extractionResult = SantaUtils.extractDataFromEmbed(embed, footer); @@ -109,7 +110,7 @@ else if (modalID.startsWith("santa-theme")) { ServerTextChannel requestsChannel = ChannelUtils.getRequestsChannel(server); if (requestsChannel == null) { modalInteraction.createImmediateResponder() - .addEmbed(com.sidpatchy.clairebot.Embed.ErrorEmbed.getCustomError(com.sidpatchy.clairebot.Main.getErrorCode("requestsChannelMissing"), "A requests channel is not configured for this server. An admin can set one in /config server > Requests Channel.")) + .addEmbed(ErrorEmbed.getCustomError(languageManager, Main.getErrorCode("requestsChannelMissing"), "A requests channel is not configured for this server. An admin can set one in /config server > Requests Channel.")) .setFlags(MessageFlag.EPHEMERAL) .respond(); } else { @@ -132,6 +133,7 @@ else if (voteType.equalsIgnoreCase("poll")) { // Silently defer the modal response (no visible message), then update the existing host message in place CompletableFuture deferred = modalInteraction.respondLater(); + assert extractionResult != null; extractionResult.rules = modalInteraction.getTextInputValueByCustomId("rules-row").orElse(extractionResult.rules); extractionResult.theme = modalInteraction.getTextInputValueByCustomId("theme-row").orElse(extractionResult.theme); diff --git a/src/main/java/com/sidpatchy/clairebot/Listener/SelectMenuChoose.java b/src/main/java/com/sidpatchy/clairebot/Listener/SelectMenuChoose.java index 2159034..b048612 100644 --- a/src/main/java/com/sidpatchy/clairebot/Listener/SelectMenuChoose.java +++ b/src/main/java/com/sidpatchy/clairebot/Listener/SelectMenuChoose.java @@ -116,7 +116,7 @@ else if ("Hexadecimal Entry".equalsIgnoreCase(value)) { if (accentColour.isEmpty()) { selectMenuInteraction.createFollowupMessageBuilder() .setFlags(MessageFlag.EPHEMERAL) - .addEmbed(ErrorEmbed.getError(Main.getErrorCode("accentColourParse"))) + .addEmbed(ErrorEmbed.getError(languageManager, Main.getErrorCode("accentColourParse"))) .send(); } else { selectMenuInteraction.createFollowupMessageBuilder() diff --git a/src/main/java/com/sidpatchy/clairebot/Listener/SlashCommandCreate.java b/src/main/java/com/sidpatchy/clairebot/Listener/SlashCommandCreate.java index df2d731..e8695b2 100644 --- a/src/main/java/com/sidpatchy/clairebot/Listener/SlashCommandCreate.java +++ b/src/main/java/com/sidpatchy/clairebot/Listener/SlashCommandCreate.java @@ -65,7 +65,7 @@ public void onSlashCommandCreate(SlashCommandCreateEvent event) { interactionResponse.addEmbed(EightBallEmbed.getEightBall(languageManager, query, author)); interactionResponse.update(); } catch (Exception e) { - e.printStackTrace(); + logger.error("Error while creating eightball embed: ", e); } }); } @@ -97,7 +97,7 @@ else if (mode.equalsIgnoreCase("server") && server != null) { else { slashCommandInteraction.createImmediateResponder() .setFlags(MessageFlag.EPHEMERAL) - .addEmbed(ErrorEmbed.getLackingPermissions("You do not have permission to run that command!")) + .addEmbed(ErrorEmbed.getLackingPermissions(languageManager, "You do not have permission to run that command!")) .respond(); } } @@ -156,12 +156,12 @@ else if (commandName.equalsIgnoreCase(commands.getPoll().getName())) { ); pollModal.exceptionally(e -> { - e.printStackTrace(); + logger.error("Error while creating poll modal: ", e); return null; }); } catch (Exception e) { - e.printStackTrace(); + logger.error("Error while creating poll modal: ", e); } } else { @@ -202,14 +202,14 @@ else if (commandName.equalsIgnoreCase(commands.getQuote().getName())) { TextChannel channel = slashCommandInteraction.getChannel().orElse(null); if (channel == null) { slashCommandInteraction.createImmediateResponder() - .addEmbed(ErrorEmbed.getError("NotInAChannel")) + .addEmbed(ErrorEmbed.getError(languageManager, "NotInAChannel")) .respond(); return; } // Construct response and update message slashCommandInteraction.respondLater().thenAccept(interactionOriginalResponseUpdater -> { - QuoteEmbed.getQuote(server, user, channel).thenAccept(embed -> { + QuoteEmbed.getQuote(languageManager, server, user, channel).thenAccept(embed -> { // Create an ActionRow with a button ActionRow actionRow = ActionRow.of( Button.primary("view_original", "View Original") @@ -225,7 +225,7 @@ else if (commandName.equalsIgnoreCase(commands.getQuote().getName())) { else if (commandName.equalsIgnoreCase(commands.getRequest().getName())) { if (server == null) { slashCommandInteraction.createImmediateResponder() - .addEmbed(ErrorEmbed.getCustomError(Main.getErrorCode("notaserver"), + .addEmbed(ErrorEmbed.getCustomError(languageManager, Main.getErrorCode("notaserver"), "You must run this command inside a server!")) .respond(); return; @@ -240,12 +240,12 @@ else if (commandName.equalsIgnoreCase(commands.getRequest().getName())) { ); pollModal.exceptionally(e -> { - e.printStackTrace(); + logger.error("Error while creating request modal: ", e); return null; }); } catch (Exception e) { - e.printStackTrace(); + logger.error("Error while creating request modal: ", e); } } else { @@ -268,7 +268,7 @@ else if (commandName.equalsIgnoreCase(commands.getRequest().getName())) { if (requestsChannel == null) { slashCommandInteraction.createImmediateResponder() .setFlags(MessageFlag.EPHEMERAL) - .addEmbed(ErrorEmbed.getCustomError(Main.getErrorCode("requestsChannelMissing"), "A requests channel is not configured for this server. An admin can set one in /config server > Requests Channel.")) + .addEmbed(ErrorEmbed.getCustomError(languageManager, Main.getErrorCode("requestsChannelMissing"), "A requests channel is not configured for this server. An admin can set one in /config server > Requests Channel.")) .respond(); } else { slashCommandInteraction.createImmediateResponder() @@ -280,8 +280,7 @@ else if (commandName.equalsIgnoreCase(commands.getRequest().getName())) { requestsChannel.sendMessage(VotingEmbed.getPoll(languageManager, "REQUEST", question, allowMultipleChoices, choices, server, author, finalNumChoicesReq)).thenAccept(message -> { if (finalNumChoicesReq > 0) { String[] numberEmojis = new String[]{"1️⃣","2️⃣","3️⃣","4️⃣","5️⃣","6️⃣","7️⃣","8️⃣","9️⃣","🔟"}; - int limit = Math.min(finalNumChoicesReq, numberEmojis.length); - for (int i = 0; i < limit; i++) { + for (int i = 0; i < finalNumChoicesReq; i++) { message.addReaction(numberEmojis[i]); } } else { @@ -299,7 +298,7 @@ else if (commandName.equalsIgnoreCase(commands.getServer().getName())) { String guildID = slashCommandInteraction.getArgumentStringValueByName("guildID").orElse(null); if (server == null && guildID == null) { - embed = ErrorEmbed.getCustomError(Main.getErrorCode("no-guild-present"), "A guild must be specified. Either run this command in a server or specify a guild ID."); + embed = ErrorEmbed.getCustomError(languageManager, Main.getErrorCode("no-guild-present"), "A guild must be specified. Either run this command in a server or specify a guild ID."); } if (guildID != null) { @@ -308,7 +307,7 @@ else if (commandName.equalsIgnoreCase(commands.getServer().getName())) { embed = ServerInfoEmbed.getServerInfo(languageManager, fromGuildID, user.getIdAsString()); } else { - embed = ErrorEmbed.getCustomError(Main.getErrorCode("guildID-invalid"), "Either that guild ID is invalid or I'm not a member of the server."); + embed = ErrorEmbed.getCustomError(languageManager, Main.getErrorCode("guildID-invalid"), "Either that guild ID is invalid or I'm not a member of the server."); } } else if (server != null) { @@ -329,14 +328,14 @@ else if (commandName.equalsIgnoreCase(commands.getSanta().getName())) { if (role == null) { slashCommandInteraction.createImmediateResponder().addEmbed( - ErrorEmbed.getError(Main.getErrorCode("RoleMissing")) + ErrorEmbed.getError(languageManager, Main.getErrorCode("RoleMissing")) ).respond(); return; } if (!author.canManageRole(role)) { slashCommandInteraction.createImmediateResponder() - .addEmbed(ErrorEmbed.getLackingPermissions("Sorry! You don't have the permission to run this " + + .addEmbed(ErrorEmbed.getLackingPermissions(languageManager, "Sorry! You don't have the permission to run this " + "command. You must be able to manage the role " + role.getMentionTag() + ".")) .respond(); return; From be70c0a5b573982a29767a8eb2793b79ea0e2a30 Mon Sep 17 00:00:00 2001 From: Sidpatchy Date: Thu, 16 Oct 2025 19:26:10 -0600 Subject: [PATCH 58/64] feat: add workflow for selecting user's preferred language --- .../Commands/Regular/EightBallEmbed.java | 2 +- .../Regular/UserPreferencesEmbed.java | 25 ++++ .../clairebot/Listener/SelectMenuChoose.java | 12 +- .../java/com/sidpatchy/clairebot/Main.java | 81 ++++++++++++- .../Regular/UserPreferencesComponents.java | 36 ++++++ .../resources/translations/lang_en-US.yml | 7 +- .../resources/translations/lang_es-ES.yml | 13 +- .../resources/translations/lang_ja-JP.yml | 15 ++- todo.md | 114 ++++++++---------- 9 files changed, 226 insertions(+), 79 deletions(-) diff --git a/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/EightBallEmbed.java b/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/EightBallEmbed.java index 932198f..4421e18 100644 --- a/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/EightBallEmbed.java +++ b/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/EightBallEmbed.java @@ -18,7 +18,7 @@ public static EmbedBuilder getEightBall(LanguageManager languageManager, String List onTopTriggers = languageManager.getLocalizedList("ClaireLang.Embed.Commands.Regular.EightBallEmbed.OnTopTriggers"); String ateBallLanguageString = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Regular.EightBallEmbed.8ball"); - Main.getLogger().error(onTopTriggers.toString()); + Main.getLogger().debug(onTopTriggers.toString()); Random random = new Random(); int rand = random.nextInt(eightBall.size()); diff --git a/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/UserPreferencesEmbed.java b/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/UserPreferencesEmbed.java index d94fbb1..8d83ae6 100644 --- a/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/UserPreferencesEmbed.java +++ b/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/UserPreferencesEmbed.java @@ -76,4 +76,29 @@ public static EmbedBuilder getLanguageMenu(LanguageManager languageManager, User .setAuthor(title) .setDescription(desc); } + + /** + * Response when a language has been selected. Updates language based on the selected locale tag. + * + * @param author User updating their language + * @param languageTag IETF BCP 47 tag (e.g., en-US) + * @return embed + */ + public static EmbedBuilder getAcknowledgeLanguageChange(LanguageManager languageManager, User author, String languageTag) { + try { + APIUser apiUser = new APIUser(author.getIdAsString()); + apiUser.getUser(); + apiUser.updateUserLanguage(languageTag); + + String title = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Regular.UserPreferencesEmbed.LanguageChanged"); + String desc = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Regular.UserPreferencesEmbed.LanguageChangedDesc"); + + return new EmbedBuilder() + .setColor(Main.getColor(author.getIdAsString())) + .setAuthor(title) + .setDescription(desc); + } catch (Exception e) { + return ErrorEmbed.getError(languageManager, Main.getErrorCode("updateLanguage")); + } + } } diff --git a/src/main/java/com/sidpatchy/clairebot/Listener/SelectMenuChoose.java b/src/main/java/com/sidpatchy/clairebot/Listener/SelectMenuChoose.java index b048612..2266a14 100644 --- a/src/main/java/com/sidpatchy/clairebot/Listener/SelectMenuChoose.java +++ b/src/main/java/com/sidpatchy/clairebot/Listener/SelectMenuChoose.java @@ -56,7 +56,7 @@ else if ("Language Editor".equalsIgnoreCase(value)) { selectMenuInteraction.createFollowupMessageBuilder() .setFlags(MessageFlag.EPHEMERAL) .addEmbed(UserPreferencesEmbed.getLanguageMenu(languageManager, user)) - .addComponents() + .addComponents(UserPreferencesComponents.getLanguageMenu(languageManager)) .send(); } else if ("Requests Channel".equalsIgnoreCase(value) @@ -184,5 +184,15 @@ else if ("enforceServerLanguage".equalsIgnoreCase(customId)) { .addComponents() .send(); } + else if ("user-language".equalsIgnoreCase(customId)) { + // Value is the IETF language tag (e.g., en-US) + String languageTag = value; + selectMenuInteraction.acknowledge(); + selectMenuInteraction.createFollowupMessageBuilder() + .setFlags(MessageFlag.EPHEMERAL) + .addEmbed(UserPreferencesEmbed.getAcknowledgeLanguageChange(languageManager, user, languageTag)) + .addComponents() + .send(); + } } } diff --git a/src/main/java/com/sidpatchy/clairebot/Main.java b/src/main/java/com/sidpatchy/clairebot/Main.java index 5655364..b5c06db 100644 --- a/src/main/java/com/sidpatchy/clairebot/Main.java +++ b/src/main/java/com/sidpatchy/clairebot/Main.java @@ -16,10 +16,18 @@ import java.awt.*; import java.io.IOException; import java.io.InputStream; +import java.io.File; +import java.net.URL; +import java.net.JarURLConnection; +import java.util.Enumeration; +import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Properties; +import java.util.Set; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; /** * ClaireBot - Simply the best. @@ -103,7 +111,7 @@ static void main(String[] args) throws InvalidConfigurationException { ResourceLoader loader = new ResourceLoader(); loader.saveResource(configFile, false); loader.saveResource(commandsFile, false); - loader.saveResource("translations/lang_en-US.yml", true); // TODO make this false, handle non en-US files. + loadAllBundledTranslations(loader, true); // TODO make this false once language files are stable // Init config handlers config = new RobinConfiguration("config/" + configFile); @@ -230,6 +238,75 @@ public static void loadCommandDefs() { } } + private static void loadAllBundledTranslations(ResourceLoader loader, boolean replace) { + String resourceDir = "translations"; + Set resourcePaths = new HashSet<>(); + try { + ClassLoader cl = Main.class.getClassLoader(); + Enumeration urls = cl.getResources(resourceDir); + while (urls.hasMoreElements()) { + URL url = urls.nextElement(); + String protocol = url.getProtocol(); + if ("file".equals(protocol)) { + try { + File dir = new File(url.toURI()); + File[] files = dir.listFiles((d, name) -> name.endsWith(".yml")); + if (files != null) { + for (File f : files) { + resourcePaths.add(resourceDir + "/" + f.getName()); + } + } + } catch (Exception e) { + logger.warn("Failed to enumerate file resources for translations: {}", e.getMessage()); + } + } else if ("jar".equals(protocol)) { + try { + JarURLConnection conn = (JarURLConnection) url.openConnection(); + try (JarFile jarFile = conn.getJarFile()) { + Enumeration entries = jarFile.entries(); + while (entries.hasMoreElements()) { + JarEntry entry = entries.nextElement(); + String name = entry.getName(); + if (!entry.isDirectory() && name.startsWith(resourceDir + "/") && name.endsWith(".yml")) { + resourcePaths.add(name); + } + } + } + } catch (Exception e) { + logger.warn("Failed to enumerate JAR resources for translations: {}", e.getMessage()); + } + } else { + logger.debug("Unsupported classpath URL protocol for translations: {}", protocol); + } + } + } catch (IOException e) { + logger.warn("Unable to list translation resources: {}", e.getMessage()); + } + + if (resourcePaths.isEmpty()) { + // Fallback: attempt known default file to ensure at least the template exists on first run + logger.warn("No translation resources discovered via classpath enumeration. Falling back to default copy of en-US and TEMPLATE if present."); + String[] fallbacks = new String[] {"translations/lang_en-US.yml", "translations/lang_TEMPLATE.yml"}; + for (String path : fallbacks) { + try { + loader.saveResource(path, replace); + } catch (Exception e) { + logger.debug("Fallback translation '{}' not present in resources: {}", path, e.getMessage()); + } + } + return; + } + + for (String path : resourcePaths) { + try { + loader.saveResource(path, replace); + logger.debug("Ensured translation resource available: {}", path); + } catch (Exception e) { + logger.warn("Failed to save translation resource '{}': {}", path, e.getMessage()); + } + } + } + // Handle the registry of slash commands and any errors associated. public static void registerSlashCommands() { try { @@ -385,4 +462,4 @@ public static String getInviteLink() { public static Locale getFallbackLocale() { return fallbackLocale; } -} +} \ No newline at end of file diff --git a/src/main/java/com/sidpatchy/clairebot/MessageComponents/Regular/UserPreferencesComponents.java b/src/main/java/com/sidpatchy/clairebot/MessageComponents/Regular/UserPreferencesComponents.java index cbaeef9..dbbff8a 100644 --- a/src/main/java/com/sidpatchy/clairebot/MessageComponents/Regular/UserPreferencesComponents.java +++ b/src/main/java/com/sidpatchy/clairebot/MessageComponents/Regular/UserPreferencesComponents.java @@ -1,8 +1,10 @@ package com.sidpatchy.clairebot.MessageComponents.Regular; import com.sidpatchy.clairebot.Lang.LanguageManager; +import com.sidpatchy.clairebot.Main; import org.javacord.api.entity.message.component.*; +import java.io.File; import java.util.*; public class UserPreferencesComponents { @@ -128,4 +130,38 @@ public static ActionRow getAccentColourHexEntry(LanguageManager languageManager) .addComponents(TextInput.create(TextInputStyle.SHORT, "hex-entry-field", label)) .build(); } + + public static ActionRow getLanguageMenu(LanguageManager languageManager) { + String placeholder = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Components.Regular.UserPreferences.LanguagePlaceholder"); + + // Discover available language files under config/translations + File dir = new File(Main.getTranslationsPath()); + List options = new ArrayList<>(); + if (dir.exists() && dir.isDirectory()) { + File[] files = dir.listFiles((d, name) -> name.startsWith("lang_") && name.endsWith(".yml")); + if (files != null) { + // Keep deterministic order + Arrays.sort(files, Comparator.comparing(File::getName)); + for (File f : files) { + String name = f.getName(); + // Example: lang_en-US.yml -> en-US + String tag = name.substring("lang_".length(), name.length() - ".yml".length()); + // Try to read friendly display name from file header or use tag as fallback + String label = tag; // Minimal; can be localized in future via file metadata + // Value must be stable and languageManager-independent; use tag + options.add(SelectMenuOption.create(label, tag)); + } + } + } + + if (options.isEmpty()) { + // Fallback to at least allow selecting fallback locale + String tag = Main.getFallbackLocale().toLanguageTag(); + options.add(SelectMenuOption.create(tag, tag)); + } + + return new ActionRowBuilder() + .addComponents(SelectMenu.create("user-language", placeholder, 1, 1, options)) + .build(); + } } diff --git a/src/main/resources/translations/lang_en-US.yml b/src/main/resources/translations/lang_en-US.yml index 37d698b..b86e14c 100644 --- a/src/main/resources/translations/lang_en-US.yml +++ b/src/main/resources/translations/lang_en-US.yml @@ -245,8 +245,10 @@ ClaireLang: AccentColourList: "Accent Colour List" AccentColourChanged: "Acccent Colour Changed!" AccentColourChangedDesc: "Your accent colour has been changed to {cb.user.id.accentcolour}" - LanguageMenuTitle: "NYI" - LanguageMenuDesc: "Sorry, this feature is not yet implemented." + LanguageMenuTitle: "Language Preferences" + LanguageMenuDesc: "Choose the language ClaireBot should use when interacting with you." + LanguageChanged: "Language Updated!" + LanguageChangedDesc: "Your preferred language has been updated." VotingEmbed: PollRequest: "{cb.user.id.displayname.server} requests:" PollAsk: "{cb.user.id.displayname.server} asks:" @@ -287,6 +289,7 @@ ClaireLang: AccentColourDescription: "For those who hate the colour blue" Language: "Language" LanguageDescription: "If you're offended by english" + LanguagePlaceholder: "Click to select a language" AccentColourPlaceholder: "Click to choose option" SelectCommonColours: "Select Common Colours" SelectCommonColoursDescription: "Select from a list of common colours" diff --git a/src/main/resources/translations/lang_es-ES.yml b/src/main/resources/translations/lang_es-ES.yml index a254d85..97c5cf7 100644 --- a/src/main/resources/translations/lang_es-ES.yml +++ b/src/main/resources/translations/lang_es-ES.yml @@ -228,9 +228,9 @@ ClaireLang: ModeratorChannelDescription: "Solo muestra los primeros 25 canales del servidor." EnforceServerLanguageMenuName: "Forzar Idioma del Servidor" AcknowledgeRequestsChannelChangeTitle: "¡Canal de Solicitudes Cambiado!" - AcknowledgeRequestsChannelChangeDescription: "Tu canal de solicitudes ha sido cambiado a {cb.channel.id.mentiontag}" + AcknowledgeRequestsChannelChangeDescription: "Tu canal de solicitudes ha sido cambiado a {cb.channel.requests.mentiontag}" AcknowledgeModeratorChannelChangeTitle: "¡Canal de Moderadores Cambiado!" - AcknowledgeModeratorChannelChangeDescription: "Tu canal de mensajes para moderadores ha sido cambiado a {cb.channel.id.mentiontag}" + AcknowledgeModeratorChannelChangeDescription: "Tu canal de mensajes para moderadores ha sido cambiado a {cb.channel.moderator.mentiontag}" AcknowledgeEnforceServerLanguageUpdateTitle: "¡Preferencias de Idioma del Servidor Actualizadas!" AcknowledgeEnforceServerLanguageUpdateEnforced: "Ahora seguiré el idioma del servidor independientemente de la preferencia del usuario." AcknowledgeEnforceServerLanguageUpdateNotEnforced: "Seguiré la preferencia de idioma de cada usuario individual." @@ -247,15 +247,17 @@ ClaireLang: AccentColourList: "Lista de Colores de Acento" AccentColourChanged: "¡Color de Acento Cambiado!" AccentColourChangedDesc: "Tu color de acento ha sido cambiado a {cb.user.id.accentcolour}" - LanguageMenuTitle: "Aún no implementado" - LanguageMenuDesc: "Lo sentimos, esta función aún no está implementada." + LanguageMenuTitle: "Preferencias de Idioma" + LanguageMenuDesc: "Elige el idioma que ClaireBot debe usar al interactuar contigo." + LanguageChanged: "¡Idioma actualizado!" + LanguageChangedDesc: "Tu idioma preferido ha sido actualizado." VotingEmbed: PollRequest: "{cb.user.id.displayname.server} solicita:" PollAsk: "{cb.user.id.displayname.server} pregunta:" Choices: "Opciones" PollID: "ID de Encuesta: {cb.poll.id}" UserResponseTitle: "¡Tu solicitud ha sido creada!" - UserResponseDescription: "Ve a verla en {cb.channel.id.mentiontag}" + UserResponseDescription: "Ve a verla en {cb.channel.requests.mentiontag}" ErrorEmbed: Error: "ERROR" GenericDescription: "Parece que he encontrado un error, ¡ups! Por favor, intenta ejecutar el comando una vez más y si eso no funciona, únete a mi [servidor de Discord]({cb.supportserver}) y cuéntanos sobre el problema.\n\nPor favor, incluye el siguiente código de error: {cb.errorcode}" @@ -289,6 +291,7 @@ ClaireLang: AccentColourDescription: "Para aquellos que odian el color azul" Language: "Idioma" LanguageDescription: "Si te ofende el inglés" + LanguagePlaceholder: "Haz clic para seleccionar un idioma" AccentColourPlaceholder: "Haz clic para elegir una opción" SelectCommonColours: "Seleccionar Colores Comunes" SelectCommonColoursDescription: "Selecciona de una lista de colores comunes" diff --git a/src/main/resources/translations/lang_ja-JP.yml b/src/main/resources/translations/lang_ja-JP.yml index e39c612..e4b3760 100644 --- a/src/main/resources/translations/lang_ja-JP.yml +++ b/src/main/resources/translations/lang_ja-JP.yml @@ -232,9 +232,9 @@ ClaireLang: ModeratorChannelDescription: "サーバー内の最初の25チャンネルのみをリスト表示します。" EnforceServerLanguageMenuName: "サーバー言語を強制" AcknowledgeRequestsChannelChangeTitle: "リクエストチャンネルが変更されました!" - AcknowledgeRequestsChannelChangeDescription: "リクエストチャンネルが {cb.channel.id.mentiontag} に変更されました。" + AcknowledgeRequestsChannelChangeDescription: "リクエストチャンネルが {cb.channel.requests.mentiontag} に変更されました。" AcknowledgeModeratorChannelChangeTitle: "モデレーターチャンネルが変更されました!" - AcknowledgeModeratorChannelChangeDescription: "モデレーターメッセージチャンネルが {cb.channel.id.mentiontag} に変更されました。" + AcknowledgeModeratorChannelChangeDescription: "モデレーターメッセージチャンネルが {cb.channel.moderator.mentiontag} に変更されました。" AcknowledgeEnforceServerLanguageUpdateTitle: "サーバー言語設定が更新されました!" AcknowledgeEnforceServerLanguageUpdateEnforced: "これからは、ユーザー設定に関わらずサーバーの言語に従います。" AcknowledgeEnforceServerLanguageUpdateNotEnforced: "これからは、個々のユーザーの言語設定に従います。" @@ -251,15 +251,17 @@ ClaireLang: AccentColourList: "アクセントカラーリスト" AccentColourChanged: "アクセントカラーが変更されました!" AccentColourChangedDesc: "あなたのアクセントカラーが {cb.user.id.accentcolour} に変更されました。" - LanguageMenuTitle: "未実装" - LanguageMenuDesc: "申し訳ありませんが、この機能はまだ実装されていません。" + LanguageMenuTitle: "言語設定" + LanguageMenuDesc: "ClaireBotがあなたとやり取りするときに使用する言語を選択してください。" + LanguageChanged: "言語が更新されました!" + LanguageChangedDesc: "あなたの優先言語が更新されました。" VotingEmbed: PollRequest: "{cb.user.id.displayname.server} からのリクエスト:" PollAsk: "{cb.user.id.displayname.server} からの質問:" Choices: "選択肢" PollID: "投票ID: {cb.poll.id}" UserResponseTitle: "リクエストが作成されました!" - UserResponseDescription: "{cb.channel.id.mentiontag} で確認してください。" + UserResponseDescription: "{cb.channel.requests.mentiontag} で確認してください。" ErrorEmbed: Error: "エラー" GenericDescription: "おっと、エラーが発生したようです!もう一度コマンドを実行してみてください。それでもうまくいかない場合は、私の[Discordサーバー]({cb.supportserver})に参加して、問題についてお知らせください。\n\n以下のエラーコードを含めてください: {cb.errorcode}" @@ -293,6 +295,7 @@ ClaireLang: AccentColourDescription: "青色が嫌いなあなたへ" Language: "言語" LanguageDescription: "英語に不快感を覚えるなら" + LanguagePlaceholder: "クリックして言語を選択" AccentColourPlaceholder: "クリックしてオプションを選択" SelectCommonColours: "一般的な色を選択" SelectCommonColoursDescription: "一般的な色のリストから選択します" @@ -309,4 +312,4 @@ ClaireLang: AllowMultipleChoicesPlaceholder: "クリックしてオプションを選択" AllowMultipleChoicesYesDescription: "回答者が複数の選択肢を選べるようにします" AllowMultipleChoicesNoDescription: "回答者が一つの選択肢しか選べないようにします" - OptionLabelTemplate: "選択肢 #{cb.voting.optionnumber}" \ No newline at end of file + OptionLabelTemplate: "選択肢 #{cb.voting.optionnumber}" diff --git a/todo.md b/todo.md index 875456b..5fd65be 100644 --- a/todo.md +++ b/todo.md @@ -1,67 +1,57 @@ -# MOVED TO +# ClaireBot TODOs — Updated 2025-10-16 18:43 -# Known Issues -1) Avatar command not sending 4096x4096 image - FIXED -2) Help command fucking dies whenever a command is added / name changed. Caused by using a seperate commands list from -the one used by everything else. Recommendation: Delete ClaireMusic - DELETED CLAIREMUSIC -3) Race condition issue with the leveling system - see https://github.com/Sidpatchy/ClaireBot/issues/3 +This file replaces an outdated TODO from several release cycles ago. It consolidates current, de-duplicated, prioritized action items discovered across the codebase and docs. -# Commands -1) 8ball - Done but needs optimizations -2) Avatar - Done, see known issues #1 -3) Config - - Server preferences - DONE - - Messaging Channels - DONE - - Language - DONE - - User preferences - - Colour - - Common colours - DONE - - Hex entry - DONE - - Language - NYI (see #14) - - Need to create a system for multi-lang. - - For plans see Specs/ClaireLang, ClaireConfig, ClairePAPI -4) Help - DONE, see known issues #2 DONE, see #16 -5) Info - NYI, literally just copy RomeBot's info command. -- DONE -6) Leaderboard - - DB implementation complete. Finalize API implementation. - - API implementation complete. Need to create the command. - - Command framework is completed. Need to prevent querying, but more importantly, displaying users who are not in a guild. - - Race condition issue https://github.com/Sidpatchy/ClaireBot/issues/3 -7) Level - NYI - - API prepared for command creation. - - Command created - - Listeners partially implemented - - Race condition issue https://github.com/Sidpatchy/ClaireBot/issues/3 -8) Poll - DONE -9) Request - DONE -10) Server - DONE -11) User - DONE -12) ClaireBot on top! - DONE -13) Zerfas -14) ClaireBot Language System (ClaireLang) - - Language Manager - - Collect EVERY language string and add it to a YAML file. - - Rewrite EVERY command to make use of the language manager. - - 8ball - - avatar - - help - - info - - leaderboard - - level - - poll - - request - - server - - user - - config -15) Probably gonna want to do something with the points system you added to the API... - Sorta kinda started - - Need to add system of gaining points. - - Mostly done, could probably make gaining points way more robust. - - Fix race condition issues: https://github.com/Sidpatchy/ClaireBot/issues/3 -16) Rewrite the help command to be actually informative. There's literally no additional value added with the current system. The command spec already supports a more advanced help string for each command. MAKE USE OF IT. The architecture of the command is great, it just needs to be more useful. The main screen could be a little more useful, point to starters like /config, and explain breifly what it does. +## 1) High‑impact, low‑effort fixes (quick wins) +- Main.java: Do not force-copy default language file each startup. + - Change saveResource("translations/lang_en-US.yml", true) to false and only copy when missing. Ensure non-en-US locales are handled on first run via lazy copy or locale detection. Priority: P1. +- SlashCommandCreate.java: Remove hardcoded "en-US". + - Read Main.fallbackLocale or a config param and pass language context appropriately. Priority: P1. +- Translations (lang_en-US.yml, lang_ja-JP.yml, lang_es-ES.yml, lang_TEMPLATE.yml): Replace “# todo insert wiki page”. + - Insert the correct documentation URL(s) to the wiki/help pages. Priority: P1. +- config.yml: “TODO either automatically add new config file params or remove this.” + - Decide policy. Prefer auto-merge of new keys at startup; otherwise remove the comment and document manual update. Priority: P1. +- ErrorEmbed.java: “remove these legacy methods.” + - Identify unused legacy methods, remove or deprecate with @Deprecated and migrate call sites. Priority: P1–P2. +## 2) Internationalization and language flow +- LanguageManager.java + - Allow server admins to specify a custom language via ClaireData. Extend Guild model + API endpoints to store server language; honor enforceServerLanguage and read from DB (not Discord preferredLocale). Priority: P2. + - Move locale resolution out of parseUserAndServerOptions into Guild creation or a dedicated locale service; LanguageManager should consume it. Priority: P2. + - Dependency: ClaireData update (Trello vkQTCTMG). Status: Blocked. Priority: P2 (blocked). -# General Features -## ClaireWeb -A website needs to be designed. In addition, ClaireBot needs to have an API endpoint (in addition to ClaireData) for it to query from. +## 3) Command system and registration +- RegisterSlashCommands.java + - Register commands per-server so server admins may use different languages. Implement per-guild registration with Javacord, mapping guild ID to localized command variants, or use localization hooks if supported. Priority: P2. +- QuoteEmbed.java + - "validate that this won’t nuke the bot" — add guardrails and error handling; bound rate/size; add a small test or dry-run. Priority: P2. -## \ No newline at end of file +## 4) Configuration handling +- Main.java + - “stop using Robin for this. Switch to standard Java classes.” Replace RobinConfiguration for config.yml with Jackson YAML or SnakeYAML + POJO; keep migration path; add validation and defaults. Priority: P3. +- build.gradle + - Kotlin stdlib re-add when JDK 25 support lands. Track Kotlin/JDK compatibility; once supported, re-enable or rely on transitive stdlib via Kotlin DSL if applicable. Priority: P3 (blocked by external support). + +## 5) Documentation +- Writerside/topics/Contributing-guide.md + - Fill TODO sections: branching model, code style, commit messages, PR checks. Priority: P2. +- Translations wiki links + - Insert live URLs across all locales (see section 1). Priority: P1. + +## 6) Backlog (still relevant) +- Leaderboard + - Exclude users not in guild; finish final display logic. Monitor race condition issue (#3). Priority: P2. +- Level command + - API prepared; complete command and listeners; verify race conditions (#3). Priority: P2. +- Points system + - Make gaining points more robust; define events, caps, anti-abuse. Priority: P3. +- Help command rewrite + - Use rich, informative help with extended per-command descriptions; point to starters like /config. Priority: P2. +- ClaireLang rollout + - Collect all language strings into YAML; ensure every command uses LanguageManager; audit placeholders and add tests. Priority: P2. +- ClaireWeb + - Design website and add ClaireBot API endpoints (in addition to ClaireData) for web queries. Priority: P3. + +## Notes and dependencies +- Several i18n items are blocked by ClaireData schema/API changes (see Trello card). Start with unblocked quick wins and documentation. +- Spanish translation file contains natural-language “todo” occurrences (meaning “all/every”); only comment lines at the top were actionable. \ No newline at end of file From ce7772b019974766b18e61a9c7f497ad9678d5f3 Mon Sep 17 00:00:00 2001 From: Sidpatchy Date: Thu, 16 Oct 2025 20:21:13 -0600 Subject: [PATCH 59/64] feat: server language selection + enforced guild locale with fallback This commit REQUIRES ClaireData v1.1.0 due to making use of the locale field in the Guilds table --- .../com/sidpatchy/clairebot/API/Guild.java | 47 ++++++-- .../Regular/ServerPreferencesEmbed.java | 25 ++++ .../clairebot/Lang/LanguageManager.java | 107 +++++++++++++++--- .../clairebot/Listener/SelectMenuChoose.java | 15 +++ .../Regular/ServerPreferencesComponents.java | 33 +++++- .../resources/translations/lang_TEMPLATE.yml | 10 ++ .../resources/translations/lang_en-US.yml | 7 ++ .../resources/translations/lang_es-ES.yml | 7 ++ .../resources/translations/lang_ja-JP.yml | 7 ++ 9 files changed, 230 insertions(+), 28 deletions(-) diff --git a/src/main/java/com/sidpatchy/clairebot/API/Guild.java b/src/main/java/com/sidpatchy/clairebot/API/Guild.java index ead2fe2..d68b943 100644 --- a/src/main/java/com/sidpatchy/clairebot/API/Guild.java +++ b/src/main/java/com/sidpatchy/clairebot/API/Guild.java @@ -58,14 +58,20 @@ public boolean isEnforceSeverLanguage() { return (boolean) guild.getObj("enforceServerLanguage"); } + public String getLocale() { + return guild.getString("locale"); + } + public void createGuild(String requestsChannelID, String moderatorMessagesChannelID, - boolean enforceServerLanguage) throws IOException { + boolean enforceServerLanguage, + String locale) throws IOException { POST post = new POST(); post.postToURL(UrlBuilder.buildUrl(Main.getApiPath(), "api/v1/guild"), guildConstructor( requestsChannelID, moderatorMessagesChannelID, - enforceServerLanguage + enforceServerLanguage, + locale )); } @@ -75,22 +81,25 @@ public void createGuildWithDefaults() { try { createGuild((String) defaults.get("requestsChannelID"), (String) defaults.get("moderatorMessagesChannelID"), - (boolean) defaults.get("enforceServerLanguage")); + (boolean) defaults.get("enforceServerLanguage"), + (String) defaults.get("locale")); } // top 10 bad ideas #1 - catch (Exception ignored) { - Main.getLogger().error("Unable to create user with defaults."); + catch (Exception e) { + Main.getLogger().error("Unable to create user with defaults.", e); } } public void updateGuild(String requestsChannelID, String moderatorMessagesChannelID, - boolean enforceServerLanguage) throws IOException { + boolean enforceServerLanguage, + String locale) throws IOException { PUT put = new PUT(); put.putToURL(UrlBuilder.buildUrl(Main.getApiPath(), "api/v1/guild", guildID), guildConstructor( requestsChannelID, moderatorMessagesChannelID, - enforceServerLanguage + enforceServerLanguage, + locale )); } @@ -98,7 +107,8 @@ public void updateRequestsChannelID(String requestsChannelID) throws IOException updateGuild( requestsChannelID, getModeratorMessagesChannelID(), - isEnforceSeverLanguage() + isEnforceSeverLanguage(), + getLocale() ); } @@ -106,7 +116,8 @@ public void updateModeratorMessagesChannelID(String moderatorMessagesChannelID) updateGuild( getRequestsChannelID(), moderatorMessagesChannelID, - isEnforceSeverLanguage() + isEnforceSeverLanguage(), + getLocale() ); } @@ -114,7 +125,17 @@ public void updateEnforceServerLanguage(boolean enforceServerLanguage) throws IO updateGuild( getRequestsChannelID(), getModeratorMessagesChannelID(), - enforceServerLanguage + enforceServerLanguage, + getLocale() + ); + } + + public void updateLocale(String locale) throws IOException { + updateGuild( + getRequestsChannelID(), + getModeratorMessagesChannelID(), + isEnforceSeverLanguage(), + locale ); } @@ -125,12 +146,14 @@ public void deleteGuild() throws IOException { public String guildConstructor(String requestsChannelID, String moderatorMessagesChannelID, - boolean enforceServerLanguage) { + boolean enforceServerLanguage, + String locale) { return "{" + "\"guildID\":\"" + guildID + "\"," + "\"requestsChannelID\":\""+ requestsChannelID + "\"," + "\"moderatorMessagesChannelID\":\"" + moderatorMessagesChannelID + "\"," + - "\"enforceServerLanguage\":\"" + enforceServerLanguage + "\"" + + "\"enforceServerLanguage\":\"" + enforceServerLanguage + "\"," + + "\"locale\":\"" + locale + "\"" + "}"; } diff --git a/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/ServerPreferencesEmbed.java b/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/ServerPreferencesEmbed.java index bb444b0..c7d75d7 100644 --- a/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/ServerPreferencesEmbed.java +++ b/src/main/java/com/sidpatchy/clairebot/Embed/Commands/Regular/ServerPreferencesEmbed.java @@ -51,6 +51,12 @@ public static EmbedBuilder getEnforceServerLangMenu(LanguageManager languageMana return createGenericMenuEmbed(author, menuName); } + public static EmbedBuilder getServerLanguageMenu(LanguageManager languageManager, User author) { + String title = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Regular.ServerPreferencesEmbed.ServerLanguageMenuTitle"); + String desc = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Regular.ServerPreferencesEmbed.ServerLanguageMenuDesc"); + return createGenericMenuEmbed(author, title).setDescription(desc); + } + public static EmbedBuilder getAcknowledgeRequestsChannelChange(LanguageManager languageManager, Server server, User author, String requestsChannelID) { // Resolve channel ServerTextChannel channel = Main.getApi().getServerTextChannelById(requestsChannelID).orElse(null); @@ -132,6 +138,25 @@ public static EmbedBuilder getAcknowledgeEnforceServerLanguageUpdate(LanguageMan } } + public static EmbedBuilder getAcknowledgeServerLanguageChange(LanguageManager languageManager, Server server, User author, String languageTag) { + try { + Guild guild = new Guild(server.getIdAsString()); + guild.getGuild(); + guild.updateLocale(languageTag); + + String title = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Regular.ServerPreferencesEmbed.ServerLanguageChanged"); + String desc = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Regular.ServerPreferencesEmbed.ServerLanguageChangedDesc"); + + return new EmbedBuilder() + .setColor(Main.getColor(author.getIdAsString())) + .setAuthor(title) + .setDescription(desc); + } catch (Exception e) { + e.printStackTrace(); + return ErrorEmbed.getError(languageManager, Main.getErrorCode("updateServerLocale")); + } + } + /** * Create a simple embed for settings menus. * diff --git a/src/main/java/com/sidpatchy/clairebot/Lang/LanguageManager.java b/src/main/java/com/sidpatchy/clairebot/Lang/LanguageManager.java index 9611aff..e66e0fe 100644 --- a/src/main/java/com/sidpatchy/clairebot/Lang/LanguageManager.java +++ b/src/main/java/com/sidpatchy/clairebot/Lang/LanguageManager.java @@ -75,12 +75,20 @@ public LanguageManager(Locale fallbackLocale, ContextManager context) { * @throws IOException if an I/O error occurs while retrieving the localized string */ public String getLocalizedString(String key) { - RobinConfiguration languageFile = parseUserAndServerOptions(server, user); - String localizedString = languageFile.getString(key); - logger.debug(localizedString); - String rawLanguageString = localizedString != null ? localizedString : key; + Locale locale = resolveEffectiveLocale(server, user); + // Load primary and fallback separately to support per-key fallback + RobinConfiguration primary = tryLoadConfig(new File(pathToLanguageFiles, "lang_" + locale.toLanguageTag() + ".yml")); + RobinConfiguration fallback = tryLoadConfig(new File(pathToLanguageFiles, "lang_" + fallbackLocale.toLanguageTag() + ".yml")); - return placeholderHandler.process(rawLanguageString); + String localized = null; + if (primary != null) { + localized = primary.getString(key); + } + if (localized == null && fallback != null) { + localized = fallback.getString(key); + } + String raw = localized != null ? localized : key; + return placeholderHandler.process(raw); } /** @@ -102,11 +110,18 @@ public String getLocalizedString(String basePath, String key) { * @throws IOException if an I/O error occurs while retrieving the localized string */ public List getLocalizedList(String key) { - RobinConfiguration languageFile = parseUserAndServerOptions(server, user); - List localizedList = languageFile.getList(key, String.class); - logger.debug(localizedList); - List rawLanguageString = localizedList != null ? localizedList : List.of(key); + Locale locale = resolveEffectiveLocale(server, user); + RobinConfiguration primary = tryLoadConfig(new File(pathToLanguageFiles, "lang_" + locale.toLanguageTag() + ".yml")); + RobinConfiguration fallback = tryLoadConfig(new File(pathToLanguageFiles, "lang_" + fallbackLocale.toLanguageTag() + ".yml")); + List localizedList = null; + if (primary != null) { + localizedList = primary.getList(key, String.class); + } + if (localizedList == null && fallback != null) { + localizedList = fallback.getList(key, String.class); + } + List rawLanguageString = localizedList != null ? localizedList : List.of(key); return placeholderHandler.process(rawLanguageString); } @@ -121,6 +136,57 @@ public List getLocalizedList(String basePath, String key) { return getLocalizedList(basePath + "." + key); } + private Locale resolveEffectiveLocale(Server server, User user) { + Locale locale; + try { + if (user == null) { + locale = fallbackLocale; + } else { + APIUser apiUser = new APIUser(user.getIdAsString()); + apiUser.getUser(); + String rawLang = apiUser.getLanguage(); + + if (rawLang == null || rawLang.isBlank()) { + locale = fallbackLocale; + } else { + String normalizedTag = rawLang.replace('_', '-'); + locale = Locale.forLanguageTag(normalizedTag); + if (locale == null || locale.toLanguageTag().equals("und")) { + locale = fallbackLocale; + } + } + } + + if (server != null) { + Guild guild = new Guild(server.getIdAsString()); + guild.getGuild(); + + if (guild.isEnforceSeverLanguage()) { + String guildLocaleTag = guild.getLocale(); + if (guildLocaleTag != null && !guildLocaleTag.isBlank()) { + String normalized = guildLocaleTag.replace('_', '-'); + Locale parsed = Locale.forLanguageTag(normalized); + if (parsed != null && !"und".equals(parsed.toLanguageTag())) { + locale = parsed; + } else if (server.getPreferredLocale() != null) { + locale = server.getPreferredLocale(); + } else { + locale = fallbackLocale; + } + } else if (server.getPreferredLocale() != null) { + locale = server.getPreferredLocale(); + } else { + locale = fallbackLocale; + } + } + } + } catch (IOException e) { + logger.error("ClaireData failed to return a response for Locale information. Are we cooked?"); + locale = fallbackLocale; + } + return locale; + } + private RobinConfiguration parseUserAndServerOptions(Server server, User user) { Locale locale; try { @@ -153,12 +219,23 @@ private RobinConfiguration parseUserAndServerOptions(Server server, User user) { guild.getGuild(); if (guild.isEnforceSeverLanguage()) { - // todo this should not be determined here, but will be until the ClaireData implementation is completed. - // todo this should instead be determined when the Guild object is created in the database. - // todo ClaireData update on hold while still designing the major ClaireBot update that follows this one. - Locale serverLocale = server.getPreferredLocale(); - if (serverLocale != null) { - locale = serverLocale; + // Respect stored guild locale when enforcement is enabled. + String guildLocaleTag = guild.getLocale(); + if (guildLocaleTag != null && !guildLocaleTag.isBlank()) { + String normalized = guildLocaleTag.replace('_', '-'); + Locale parsed = Locale.forLanguageTag(normalized); + if (parsed != null && !"und".equals(parsed.toLanguageTag())) { + locale = parsed; + } else if (server.getPreferredLocale() != null) { + locale = server.getPreferredLocale(); + } else { + locale = fallbackLocale; + } + } else if (server.getPreferredLocale() != null) { + // Fallback to Discord server preferred locale if guild setting is absent + locale = server.getPreferredLocale(); + } else { + locale = fallbackLocale; } } } diff --git a/src/main/java/com/sidpatchy/clairebot/Listener/SelectMenuChoose.java b/src/main/java/com/sidpatchy/clairebot/Listener/SelectMenuChoose.java index 2266a14..97524f8 100644 --- a/src/main/java/com/sidpatchy/clairebot/Listener/SelectMenuChoose.java +++ b/src/main/java/com/sidpatchy/clairebot/Listener/SelectMenuChoose.java @@ -154,6 +154,12 @@ else if ("server-settings".equalsIgnoreCase(customId)) { .addEmbed(ServerPreferencesEmbed.getEnforceServerLangMenu(languageManager, user)) .addComponents(ServerPreferencesComponents.getEnforceServerLanguageMenu(languageManager)) .send(); + } else if ("Server Language".equalsIgnoreCase(value)) { + selectMenuInteraction.createFollowupMessageBuilder() + .setFlags(MessageFlag.EPHEMERAL) + .addEmbed(ServerPreferencesEmbed.getServerLanguageMenu(languageManager, user)) + .addComponents(ServerPreferencesComponents.getServerLanguageMenu(languageManager)) + .send(); } } // Server channel selection submenus @@ -184,6 +190,15 @@ else if ("enforceServerLanguage".equalsIgnoreCase(customId)) { .addComponents() .send(); } + else if ("server-language".equalsIgnoreCase(customId)) { + String languageTag = value; + selectMenuInteraction.acknowledge(); + selectMenuInteraction.createFollowupMessageBuilder() + .setFlags(MessageFlag.EPHEMERAL) + .addEmbed(ServerPreferencesEmbed.getAcknowledgeServerLanguageChange(languageManager, server, user, languageTag)) + .addComponents() + .send(); + } else if ("user-language".equalsIgnoreCase(customId)) { // Value is the IETF language tag (e.g., en-US) String languageTag = value; diff --git a/src/main/java/com/sidpatchy/clairebot/MessageComponents/Regular/ServerPreferencesComponents.java b/src/main/java/com/sidpatchy/clairebot/MessageComponents/Regular/ServerPreferencesComponents.java index a18afab..e1753a2 100644 --- a/src/main/java/com/sidpatchy/clairebot/MessageComponents/Regular/ServerPreferencesComponents.java +++ b/src/main/java/com/sidpatchy/clairebot/MessageComponents/Regular/ServerPreferencesComponents.java @@ -26,6 +26,9 @@ public static ActionRow getMainMenu(LanguageManager languageManager) { String enforceLabel = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Components.Regular.ServerPreferences.EnforceServerLanguage"); String enforceDesc = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Components.Regular.ServerPreferences.EnforceServerLanguageDescription"); + String serverLangLabel = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Components.Regular.ServerPreferences.ServerLanguage"); + String serverLangDesc = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Components.Regular.ServerPreferences.ServerLanguageDescription"); + return new ActionRowBuilder() .addComponents( SelectMenu.create("server-settings", placeholder, 1, 1, @@ -33,7 +36,8 @@ public static ActionRow getMainMenu(LanguageManager languageManager) { // Keep values stable for interaction handlers SelectMenuOption.create(requestsLabel, "Requests Channel", requestsDesc), SelectMenuOption.create(modLabel, "Moderator Messages Channel", modDesc), - SelectMenuOption.create(enforceLabel, "Enforce Server Language", enforceDesc) + SelectMenuOption.create(enforceLabel, "Enforce Server Language", enforceDesc), + SelectMenuOption.create(serverLangLabel, "Server Language", serverLangDesc) )) ).build(); } @@ -62,6 +66,33 @@ public static ActionRow getEnforceServerLanguageMenu(LanguageManager languageMan ).build(); } + public static ActionRow getServerLanguageMenu(LanguageManager languageManager) { + // Server language placeholder + String placeholder = languageManager.getLocalizedString("ClaireLang.Embed.Commands.Components.Regular.ServerPreferences.ServerLanguagePlaceholder"); + + java.io.File dir = new java.io.File(com.sidpatchy.clairebot.Main.getTranslationsPath()); + List options = new ArrayList<>(); + if (dir.exists() && dir.isDirectory()) { + java.io.File[] files = dir.listFiles((d, name) -> name.startsWith("lang_") && name.endsWith(".yml")); + if (files != null) { + Arrays.sort(files, java.util.Comparator.comparing(java.io.File::getName)); + for (java.io.File f : files) { + String name = f.getName(); + String tag = name.substring("lang_".length(), name.length() - ".yml".length()); + String label = tag; + options.add(SelectMenuOption.create(label, tag)); + } + } + } + if (options.isEmpty()) { + String tag = com.sidpatchy.clairebot.Main.getFallbackLocale().toLanguageTag(); + options.add(SelectMenuOption.create(tag, tag)); + } + return new ActionRowBuilder() + .addComponents(SelectMenu.create("server-language", placeholder, 1, 1, options)) + .build(); + } + /** * Create a select menu with a list of the server's channels. * diff --git a/src/main/resources/translations/lang_TEMPLATE.yml b/src/main/resources/translations/lang_TEMPLATE.yml index a4984d4..1c24297 100644 --- a/src/main/resources/translations/lang_TEMPLATE.yml +++ b/src/main/resources/translations/lang_TEMPLATE.yml @@ -157,6 +157,10 @@ ClaireLang: AcknowledgeEnforceServerLanguageUpdateTitle: "" AcknowledgeEnforceServerLanguageUpdateEnforced: "" AcknowledgeEnforceServerLanguageUpdateNotEnforced: "" + ServerLanguageMenuTitle: "" + ServerLanguageMenuDesc: "" + ServerLanguageChanged: "" + ServerLanguageChangedDesc: "" UserInfoEmbed: Error_1: "" Error_2: "" @@ -172,6 +176,8 @@ ClaireLang: AccentColourChangedDesc: "" LanguageMenuTitle: "" LanguageMenuDesc: "" + LanguageChanged: "" + LanguageChangedDesc: "" VotingEmbed: PollRequest: "" PollAsk: "" @@ -206,12 +212,16 @@ ClaireLang: EnforceServerLanguageDescription: "" EnforceServerLanguagePlaceholder: "" SelectAChannelPlaceholder: "" + ServerLanguage: "" + ServerLanguageDescription: "" + ServerLanguagePlaceholder: "" UserPreferences: Settings: "" AccentColour: "" AccentColourDescription: "" Language: "" LanguageDescription: "" + LanguagePlaceholder: "" AccentColourPlaceholder: "" SelectCommonColours: "" SelectCommonColoursDescription: "" diff --git a/src/main/resources/translations/lang_en-US.yml b/src/main/resources/translations/lang_en-US.yml index b86e14c..c09dde5 100644 --- a/src/main/resources/translations/lang_en-US.yml +++ b/src/main/resources/translations/lang_en-US.yml @@ -232,6 +232,10 @@ ClaireLang: AcknowledgeEnforceServerLanguageUpdateTitle: "Server Language Preferences Updated!" AcknowledgeEnforceServerLanguageUpdateEnforced: "I will now follow the server's language regardless of user preference." AcknowledgeEnforceServerLanguageUpdateNotEnforced: "I will follow each individual user's language preference." + ServerLanguageMenuTitle: "Server Language" + ServerLanguageMenuDesc: "Choose the default language ClaireBot should use for this server when enforcement is enabled." + ServerLanguageChanged: "Server Language Updated!" + ServerLanguageChangedDesc: "The server's language preference has been updated." UserInfoEmbed: Error_1: "The value for author was null when passed into UserInfo Embed. Error code: {cb.errorcode}" Error_2: "Author: {cb.user.name}" @@ -283,6 +287,9 @@ ClaireLang: EnforceServerLanguageDescription: "Force ClaireBot to use the same language as the server regardless of user preference." EnforceServerLanguagePlaceholder: "Click to select an option" SelectAChannelPlaceholder: "Click to select a channel" + ServerLanguage: "Server Language" + ServerLanguageDescription: "Choose the default language ClaireBot should use in this server." + ServerLanguagePlaceholder: "Click to select a server language" UserPreferences: Settings: "Settings" AccentColour: "Accent Colour" diff --git a/src/main/resources/translations/lang_es-ES.yml b/src/main/resources/translations/lang_es-ES.yml index 97c5cf7..0b44d3c 100644 --- a/src/main/resources/translations/lang_es-ES.yml +++ b/src/main/resources/translations/lang_es-ES.yml @@ -234,6 +234,10 @@ ClaireLang: AcknowledgeEnforceServerLanguageUpdateTitle: "¡Preferencias de Idioma del Servidor Actualizadas!" AcknowledgeEnforceServerLanguageUpdateEnforced: "Ahora seguiré el idioma del servidor independientemente de la preferencia del usuario." AcknowledgeEnforceServerLanguageUpdateNotEnforced: "Seguiré la preferencia de idioma de cada usuario individual." + ServerLanguageMenuTitle: "Idioma del servidor" + ServerLanguageMenuDesc: "Elige el idioma por defecto que ClaireBot debe usar en este servidor cuando la imposición esté activada." + ServerLanguageChanged: "¡Idioma del servidor actualizado!" + ServerLanguageChangedDesc: "La preferencia de idioma del servidor ha sido actualizada." UserInfoEmbed: Error_1: "El valor para 'author' era nulo cuando se pasó al Embed de UserInfo. Código de error: {cb.errorcode}" Error_2: "Autor: {cb.user.name}" @@ -285,6 +289,9 @@ ClaireLang: EnforceServerLanguageDescription: "Forzar a ClaireBot a usar el mismo idioma que el servidor, independientemente de la preferencia del usuario." EnforceServerLanguagePlaceholder: "Haz clic para seleccionar una opción" SelectAChannelPlaceholder: "Haz clic para seleccionar un canal" + ServerLanguage: "Idioma del servidor" + ServerLanguageDescription: "Elige el idioma por defecto que ClaireBot debe usar en este servidor." + ServerLanguagePlaceholder: "Haz clic para seleccionar un idioma para el servidor" UserPreferences: Settings: "Configuración" AccentColour: "Color de Acento" diff --git a/src/main/resources/translations/lang_ja-JP.yml b/src/main/resources/translations/lang_ja-JP.yml index e4b3760..675f302 100644 --- a/src/main/resources/translations/lang_ja-JP.yml +++ b/src/main/resources/translations/lang_ja-JP.yml @@ -238,6 +238,10 @@ ClaireLang: AcknowledgeEnforceServerLanguageUpdateTitle: "サーバー言語設定が更新されました!" AcknowledgeEnforceServerLanguageUpdateEnforced: "これからは、ユーザー設定に関わらずサーバーの言語に従います。" AcknowledgeEnforceServerLanguageUpdateNotEnforced: "これからは、個々のユーザーの言語設定に従います。" + ServerLanguageMenuTitle: "サーバー言語" + ServerLanguageMenuDesc: "強制が有効な場合、ClaireBotがこのサーバーで使用するデフォルト言語を選択してください。" + ServerLanguageChanged: "サーバー言語が更新されました!" + ServerLanguageChangedDesc: "サーバーの言語設定が更新されました。" UserInfoEmbed: Error_1: "UserInfo Embedに渡されたauthorの値がnullでした。エラーコード: {cb.errorcode}" Error_2: "作成者: {cb.user.name}" @@ -289,6 +293,9 @@ ClaireLang: EnforceServerLanguageDescription: "ユーザー設定に関わらず、ClaireBotにサーバーと同じ言語を使用させます。" EnforceServerLanguagePlaceholder: "クリックしてオプションを選択" SelectAChannelPlaceholder: "クリックしてチャンネルを選択" + ServerLanguage: "サーバーの言語" + ServerLanguageDescription: "このサーバーでClaireBotが使用するデフォルト言語を選択してください。" + ServerLanguagePlaceholder: "クリックしてサーバーの言語を選択" UserPreferences: Settings: "設定" AccentColour: "アクセントカラー" From 224ed85b0bb1c2cb1b58ac907e96799eb5b155d7 Mon Sep 17 00:00:00 2001 From: Sidpatchy Date: Thu, 16 Oct 2025 20:24:39 -0600 Subject: [PATCH 60/64] chore(release): 3.4.0-beta.1 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 500785c..c9be67c 100644 --- a/build.gradle +++ b/build.gradle @@ -14,7 +14,7 @@ jar { } group = 'com.sidpatchy' -version = '3.4.0-alpha.7' +version = '3.4.0-beta.1' processResources { filesMatching('**/build.properties') { From 4da8efeb6e8c45405fe39a25936c55656a39af09 Mon Sep 17 00:00:00 2001 From: Sidpatchy Date: Fri, 17 Oct 2025 10:41:00 -0600 Subject: [PATCH 61/64] feat: add en_GB --- .../resources/translations/lang_en-GB-yml | 321 ++++++++++++++++++ 1 file changed, 321 insertions(+) create mode 100644 src/main/resources/translations/lang_en-GB-yml diff --git a/src/main/resources/translations/lang_en-GB-yml b/src/main/resources/translations/lang_en-GB-yml new file mode 100644 index 0000000..3e77d73 --- /dev/null +++ b/src/main/resources/translations/lang_en-GB-yml @@ -0,0 +1,321 @@ +# ---------------------------------------------- +# | ClaireBot v3.4.0 | +# | Proper Bri'ish language file | +# ---------------------------------------------- +# +# Please reference the below wiki page to learn more about contributing: +# todo insert wiki page +# Thank you for any and all contributions! + +# A list of people who have contributed to the translations for this language. +# +# Please include your name if you contribute to this file. It will be displayed in the credits section for a language +# whenever it is selected. You may include a link to your GitHub or any other social media platform. +contributors: + - ["Sidpatchy", "https://github.com/Sidpatchy/"] + +# Any notes that translators feel the need to express, written in the target language. +# At your discretion, you may leave this field blank if you feel that it is not needed. If updating the language file, +# feel free to modify or remove this (make it blank) if it no longer applies. +# +# If you are contributing, please provide the ClaireBot maintainer(s) with a translated version/explanation of this. +# The explanation doesn't have to go into crazy detail, just let us know why it is present. +translationNotes: "The officially supported language for ClaireBot, innit." + +# The ClaireLang version that applies to this file. +# +# If you are translating ClaireBot, you do not need to worry about understanding this, the project maintainer(s) will +# happily assist you in determining this. +# +# The above being said, you are more than welcome to update this value to match the +# language revision you are translating to. +# +# This number should be changed in accordance with the following: +# +# ClaireLang version numbers are incremented every time new parameter(s) are added. If the main language file (en-US) +# has its wording changed, a .1, .2, .3 decimal value will be added. +# +# ClaireLang version numbers are used by the bot to assist in determining whether parameters are missing or not +# yet translated. +# +# See the below wiki page for a changelog: +# todo insert wiki page +version: 1 + +# Language Keys +# -------------------------------------------------------------------------------------------------------------------- # + +ClaireLang: + # Generic Strings which are likely ot be used in multiple places. + Generic: + True: "Proper True" + False: "Bollocks" + Yes: "Yeah mate" + No: "Nah bruv" + ClickToDisplaySettings: "Give it a click to display settings, yeah?" + + Colors: + ClaireBotBlue: "ClaireBot Blue (like the Queen's blood)" + Red: "Red (like a postbox)" + Pink: "Pink (bit cheeky)" + Purple: "Purple (right posh)" + DeepPurple: "Deep Purple (proper fancy)" + Indigo: "Indigo (innit)" + Blue: "Blue (like a Tory)" + LightBlue: "Light Blue (like the sky over Manchester)" + Cyan: "Cyan (bit modern that)" + Teal: "Teal (lovely cuppa colour)" + Green: "Green (like the countryside)" + Olive: "Olive (bit Mediterranean for my taste)" + LightGreen: "Light Green (spring-like)" + Lime: "Lime (bit zesty)" + Yellow: "Yellow (like custard)" + Amber: "Amber (traffic light vibes)" + NRAXOrange: "NRAX Orange (bit loud)" + DeepOrange: "Deep Orange (proper vibrant)" + White: "White (like cliffs of Dover)" + Grey: "Grey (like London weather)" + Black: "Black (like a proper cuppa)" + + PlsBan: + PlsBanTriggers: + - "pls ban" + - "please ban" + - "ban the muppet" + - "get rid of this tosser" + - "no loicense" + PlsBanResponses: + - "Right you are guv" + - "No worries mate!" + - "Sorted. Cheerio! :)" + - "Consider it done, innit." + - "Done and dusted. Absolute muppet that one" + - "*L + Ratio + No tea for you -ClaireBot*" + - "I'll tell 'em to sod off, I reckon ¯\\_(ツ)_/¯" + - "Sent 'em packing proper" + - "Cry harder, you melt." + - "Cheers" + - "Good riddance to bad rubbish" + - "Oi, you got a loicense for that behaviour?" + - "No TV loicense? Straight to ban, mate." + Embed: + Commands: + Regular: + AvatarEmbed: "" # Unused + EightBallEmbed: + 8ball: "Magic 8 Ball (British Edition)" + 8bResponses: + - "*Blimey, yes!*" + - "*Bloody hell no*" + - "*Perhaps, mate*" + - "*Yeah*" + - "*Nah*" + - "*Not a chance, bruv*" + - "*Could be, innit.*" + - "*Not on your nelly.*" + - "*I proper doubt it.*" + - "*Absolutely chuffed to say yes!*" + - "*Bit tricky to say.*" + - "*Quite likely!*" + - "*Not bloody likely.*" + - "*Only if you've got a loicense for it*" + 8bRiggedResponses: + - "*Too right!*" + - "*BLOODY BRILLIANT*" + - "*You know it, mate!*" + - "*Do you really need to ask a question you already know the answer to? Obviously yes, innit.*" + - "*Doesn't even need saying, everyone knows ClaireBot's proper mint.*" + - "*The answer's clear as day: YES!*" + - "*ClaireBot's dominance is undisputed, bruv.*" + - "*With ClaireBot, there's no question, mate!*" + - "*All signs point to ClaireBot being top of the pops!*" + OnTopTriggers: + # Defines what triggers a response from the ClaireBotOnTopResponses or 8bRiggedResponses + - "ClaireBot on top" + - "ClaireBot proper mint" + - "ClaireBot absolute legend" + - "ClaireBot is always right" + - "pot no toBerialC" + - "Thank the Queen for ClaireBot" + - "ClaireBot rules Britannia" + - "ClaireBot's the dog's bollocks" + - "ClaireBot unbeatable" + - "Hail ClaireBot" + - "All Hail ClaireBot" + - "In ClaireBot we trust" + - "Long live ClaireBot" + - "ClaireBot > everything" + - "ClaireBot is the GOAT" + - "ClaireBot is proper GOATed" + - "ClaireBot for the win" + - "Cheers based god" + - "Based god innit" + # Things ClaireBot should respond with when triggered by one of the OnTopTriggers + ClaireBotOnTopResponses: + - "*You know it, mate!*" + - "*I am... inevitable, innit. -ClaireBot*" + - "*Approved, bruv.*" + - "*That's facts.*" + - "*Better than Mee6, proper*" + - "*No stop, you're making me blush!*" + - "*Can't be beat, mate!*" + - "*Absolutely mint*" + - "*pot no toBerialC*" + - "*Better than Groovy, innit*" + - "*Always one step ahead, yeah.*" + - "*An unstoppable force, like a queue at Greggs.*" + - "*ClaireBot to the rescue, mate!*" + - "*Top-tier, proper brilliant!*" + - "*Outshining the rest like the Crown Jewels!*" + - "*ClaireBot supremacy is achieved, innit.*" + - "*Not a MI6 mind control weapon since 2025!*" + - "*Hail me! -ClaireBot*" + - "*Unstoppable force, bruv! 🚀*" + - "*Forever on top, like the Queen! 🏆*" + - "*Fear not, ClaireBot's got your back, mate! ✌️*" + - "*With great power, comes great ClaireBot, innit! 💪*" + - "*Trust in ClaireBot, trust in victory, yeah! 🏅*" + - "*The best there is, the best there was, the best there ever will be, proper!*" + - "*Bringing the best, always, mate! 🌟*" + - "*Excellence, innit. 💯*" + # Note: The rest of the help is located separately within commands_en-US.yml + HelpEmbed: + Commands: "Commands (Sorted)" + Usage: "How to Use It" + Error: "Can't seem to locate help data for \"{cb.commandname}\", mate. Error code: {cb.errorcode}" + InfoEmbed: + NeedHelp: "Need a Hand?" + NeedHelpDetails: "You can get help by creating an issue on our [GitHub](https://github.com/Sidpatchy/ClaireBot/issues) or by popping into our [support server](https://support.clairebot.net/), innit" + AddToServer: "Add Me to Your Server" + AddToServerDetails: "Adding me to a server is dead simple, just click [here]({cb.supportserver}), yeah?" + GitHub: "GitHub (Proper Open Source)" + GitHubDetails: "ClaireBot is open source, that means you can view all its code, mate! Check out its [GitHub!]({cb.github})" + ServerCount: "Server Count (Quite Chuffed)" + ServerCountDetails: "I've enlightened **{cb.bot.numservers}** servers, proper brilliant." + Version: "Version (Latest and Greatest)" + VersionDetails: "I'm running ClaireBot **v{cb.bot.version}**, released on **{cb.bot.releasedate}**, innit" + Uptime: "Uptime (Been Grafting)" + UptimeValue: "Started on \n*{cb.bot.runtimedurationwords}*" + LeaderboardEmbed: + LeaderboardForServer: "Leaderboard for this gaff" + GlobalLeaderboard: "Global Leaderboard (Worldwide Innit)" + LevelEmbed: "" # Unused + QuoteEmbed: + InvalidMessages: "Looks like the messages I selected were proper dodgy. Please try again later, mate." + JumpToOriginal: "Click to jump to the original message, yeah:" + SantaEmbed: + Confirmation: "Sorted! I've sent you a direct message. Please continue there, mate." + RulesButton: "Add rules" + ThemeButton: "Add a theme" + SendButton: "Send messages" + TestButton: "Send Sample" + RandomizeButton: "Re-randomize" + SentByAuthor: "Sent by" + GiverMessage: "Ho! Ho! Ho! You've received **{cb.user.id.displayname.server}** in the {cb.server.name} Secret Santa, innit!" + ServerInfoEmbed: + ServerID: "Server ID (The Numbers)" + Owner: "Owner (The Gaffer)" + CreationDate: "Creation Date (When It All Began)" + RoleCount: "Role Count (How Many Jobs)" + MemberCount: "Member Count (The Lads)" + ChannelCounts: "Channel Counts (All The Rooms)" + Categories: "Categories" + TextChannels: "Text Channels (For Chatting)" + VoiceChannels: "Voice Channels (For Having a Natter)" + ServerPreferencesEmbed: + MainMenuTitle: "Server Configuration Editor (Proper Settings)" + NotAServer: "You must run this command inside a server, mate!" + RequestsChannelMenuName: "Requests Channel" + RequestsChannelDescription: "Only lists the first 25 channels in the server, innit." + ModeratorChannelMenuName: "Moderator Messages Channel" + ModeratorChannelDescription: "Only lists the first 25 channels in the server, yeah." + EnforceServerLanguageMenuName: "Enforce Server Language" + AcknowledgeRequestsChannelChangeTitle: "Requests Channel Changed, Mate!" + AcknowledgeRequestsChannelChangeDescription: "Your requests channel has been changed to {cb.channel.requests.mentiontag}, proper sorted" + AcknowledgeModeratorChannelChangeTitle: "Moderator Channel Changed, Bruv!" + AcknowledgeModeratorChannelChangeDescription: "Your moderator messages channel has been changed to {cb.channel.moderator.mentiontag}, innit" + AcknowledgeEnforceServerLanguageUpdateTitle: "Server Language Preferences Updated, Yeah!" + AcknowledgeEnforceServerLanguageUpdateEnforced: "I'll now follow the server's language regardless of user preference, mate." + AcknowledgeEnforceServerLanguageUpdateNotEnforced: "I'll follow each individual user's language preference, innit." + ServerLanguageMenuTitle: "Server Language (What We're Speaking)" + ServerLanguageMenuDesc: "Choose the default language ClaireBot should use for this server when enforcement is enabled, yeah?" + ServerLanguageChanged: "Server Language Updated, Mate!" + ServerLanguageChangedDesc: "The server's language preference has been updated, proper sorted." + UserInfoEmbed: + Error_1: "The value for author was null when passed into UserInfo Embed, bit dodgy that. Error code: {cb.errorcode}" + Error_2: "Author: {cb.user.name}" + User: "User (The Person)" + DiscordID: "Discord ID (The Numbers)" + JoinDate: "Server Join Date (When They Arrived)" + CreationDate: "Account Creation Date (When They Started)" + UserPreferencesEmbed: + MainMenuText: "User Preferences Editor (Your Settings)" + AccentColourMenu: "Accent Colour Editor (Make It Pretty)" + AccentColourList: "Accent Colour List" + AccentColourChanged: "Accent Colour Changed, Mate!" + AccentColourChangedDesc: "Your accent colour has been changed to {cb.user.id.accentcolour}, proper lovely" + LanguageMenuTitle: "Language Preferences (What You Speak)" + LanguageMenuDesc: "Choose the language ClaireBot should use when chatting with you, innit." + LanguageChanged: "Language Updated, Bruv!" + LanguageChangedDesc: "Your preferred language has been updated, sorted." + VotingEmbed: + PollRequest: "{cb.user.id.displayname.server} requests, innit:" + PollAsk: "{cb.user.id.displayname.server} asks, yeah:" + Choices: "Choices (Pick One)" + PollID: "Poll ID: {cb.poll.id}" + UserResponseTitle: "Your request has been created, mate!" + UserResponseDescription: "Go check it out in {cb.channel.requests.mentiontag}, proper brilliant" + ErrorEmbed: + Error: "BLIMEY, ERROR" + GenericDescription: "It appears I've encountered an error, bloody hell! Please try running the command once more and if that doesn't work, join my [Discord server]({cb.supportserver}) and let us know about the issue, yeah?\n\nPlease include the following error code: {cb.errorcode}" + WelcomeEmbed: + Title: "🎉 Welcome to ClaireBot 3, Mate!" + Motto: "How can you rise, if you have not burned, innit?" + UsageTitle: "Usage (How To Use It)" + UsageDesc: "Get started by running `{cb.command.help.name}`, yeah? Need more info on a command? Run `/{cb.command.help.name} ` (ex. `/{cb.command.help.name} user`), proper simple" + SupportTitle: "Get Support (We're Here To Help)" + SupportDesc: "You can get help on our [Discord]({cb.supportserver}), or by opening an issue on [GitHub]({cb.github}), innit" + + # Message Components (Buttons, Select Menus, etc.) + Components: + Regular: + SantaModal: + rules-row: "Rules, yeah..." + theme-row: "Theme, innit..." + ServerPreferences: + Settings: "Settings (The Important Bits)" + RequestsChannel: "Requests Channel" + RequestsChannelDescription: "Choose where ClaireBot should post requests, mate." + ModeratorMessagesChannel: "Moderator Messages Channel" + ModeratorMessagesChannelDescription: "Choose where ClaireBot should post moderator messages, innit." + EnforceServerLanguage: "Enforce Server Language" + EnforceServerLanguageDescription: "Force ClaireBot to use the same language as the server regardless of user preference, yeah." + EnforceServerLanguagePlaceholder: "Click to select an option, mate" + SelectAChannelPlaceholder: "Click to select a channel, bruv" + ServerLanguage: "Server Language" + ServerLanguageDescription: "Choose the default language ClaireBot should use in this server, innit." + ServerLanguagePlaceholder: "Click to select a server language, yeah" + UserPreferences: + Settings: "Settings (Your Preferences)" + AccentColour: "Accent Colour" + AccentColourDescription: "For those who hate the colour blue, innit" + Language: "Language" + LanguageDescription: "If you're offended by English, mate" + LanguagePlaceholder: "Click to select a language, yeah" + AccentColourPlaceholder: "Click to choose option, bruv" + SelectCommonColours: "Select Common Colours" + SelectCommonColoursDescription: "Select from a list of common colours, proper sorted" + HexadecimalEntry: "Hexadecimal Entry (For The Tech-Savvy)" + HexadecimalEntryDescription: "Enter any hexadecimal colour, innit" + HexEntryPlaceholder: "Enter a hex colour ex. #ff8800, yeah" + Voting: + Question: "Question (What's Being Asked)" + Details: "Details (The Important Bits)" + MultipleChoicePlaceholder: "Click to choose option, mate" + MultipleChoiceYesDescription: "Opens menu to select more options, innit" + MultipleChoiceNoDescription: "Submits {cb.commandname} after clicking submit, yeah" + AllowMultipleChoices: "Allow selecting multiple choices, bruv?" + AllowMultipleChoicesPlaceholder: "Click to choose option, mate" + AllowMultipleChoicesYesDescription: "Allows the respondent to select more than one option, proper flexible" + AllowMultipleChoicesNoDescription: "Only allows the respondent to select one option, innit" + OptionLabelTemplate: "Option #{cb.voting.optionnumber}" From bb0d7fab2d1e90c5536bdfc940598bb4482dfdd4 Mon Sep 17 00:00:00 2001 From: Sidpatchy Date: Fri, 17 Oct 2025 11:07:00 -0600 Subject: [PATCH 62/64] fix: file name --- .../resources/translations/{lang_en-GB-yml => lang_en-GB.yml} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename src/main/resources/translations/{lang_en-GB-yml => lang_en-GB.yml} (99%) diff --git a/src/main/resources/translations/lang_en-GB-yml b/src/main/resources/translations/lang_en-GB.yml similarity index 99% rename from src/main/resources/translations/lang_en-GB-yml rename to src/main/resources/translations/lang_en-GB.yml index 3e77d73..08b2ccb 100644 --- a/src/main/resources/translations/lang_en-GB-yml +++ b/src/main/resources/translations/lang_en-GB.yml @@ -20,7 +20,7 @@ contributors: # # If you are contributing, please provide the ClaireBot maintainer(s) with a translated version/explanation of this. # The explanation doesn't have to go into crazy detail, just let us know why it is present. -translationNotes: "The officially supported language for ClaireBot, innit." +translationNotes: "The only propah language for ClaireBot, innit." # The ClaireLang version that applies to this file. # From d447aaddccb5c9834100776b94cae7e8f35b171f Mon Sep 17 00:00:00 2001 From: Sidpatchy Date: Fri, 17 Oct 2025 11:11:13 -0600 Subject: [PATCH 63/64] feat: add en_PIRATE --- .../resources/translations/lang_en-PIRATE.yml | 321 ++++++++++++++++++ 1 file changed, 321 insertions(+) create mode 100644 src/main/resources/translations/lang_en-PIRATE.yml diff --git a/src/main/resources/translations/lang_en-PIRATE.yml b/src/main/resources/translations/lang_en-PIRATE.yml new file mode 100644 index 0000000..99f31de --- /dev/null +++ b/src/main/resources/translations/lang_en-PIRATE.yml @@ -0,0 +1,321 @@ +# ---------------------------------------------- +# | ClaireBot v3.4.0 | +# | Pirate language file, arr! | +# ---------------------------------------------- +# +# Please reference the below wiki page to learn more about contributing: +# todo insert wiki page +# Thank you for any and all contributions! + +# A list of people who have contributed to the translations for this language. +# +# Please include your name if you contribute to this file. It will be displayed in the credits section for a language +# whenever it is selected. You may include a link to your GitHub or any other social media platform. +contributors: + - ["Sidpatchy", "https://github.com/Sidpatchy/"] + +# Any notes that translators feel the need to express, written in the target language. +# At your discretion, you may leave this field blank if you feel that it is not needed. If updating the language file, +# feel free to modify or remove this (make it blank) if it no longer applies. +# +# If you are contributing, please provide the ClaireBot maintainer(s) with a translated version/explanation of this. +# The explanation doesn't have to go into crazy detail, just let us know why it is present. +translationNotes: "Arr, this be the official pirate speak translation fer ClaireBot. Speak like a proper buccaneer, matey!" + +# The ClaireLang version that applies to this file. +# +# If you are translating ClaireBot, you do not need to worry about understanding this, the project maintainer(s) will +# happily assist you in determining this. +# +# The above being said, you are more than welcome to update this value to match the +# language revision you are translating to. +# +# This number should be changed in accordance with the following: +# +# ClaireLang version numbers are incremented every time new parameter(s) are added. If the main language file (en-US) +# has its wording changed, a .1, .2, .3 decimal value will be added. +# +# ClaireLang version numbers are used by the bot to assist in determining whether parameters are missing or not +# yet translated. +# +# See the below wiki page for a changelog: +# todo insert wiki page +version: 1 + +# Language Keys +# -------------------------------------------------------------------------------------------------------------------- # + +ClaireLang: + # Generic Strings which are likely ot be used in multiple places. + Generic: + True: "Aye" + False: "Nay" + Yes: "Aye aye" + No: "Nay, matey" + ClickToDisplaySettings: "Give it a click to display yer settings, arr" + + Colors: + ClaireBotBlue: "ClaireBot Blue (like the seven seas)" + Red: "Red (like blood on the deck)" + Pink: "Pink (like a landlubber's cheeks)" + Purple: "Purple (like royal plunder)" + DeepPurple: "Deep Purple (like the depths)" + Indigo: "Indigo (arr)" + Blue: "Blue (like Davy Jones' locker)" + LightBlue: "Light Blue (like calm waters)" + Cyan: "Cyan (like tropical seas)" + Teal: "Teal (like the Caribbean)" + Green: "Green (like a scurvy cure)" + Olive: "Olive (like old sailcloth)" + LightGreen: "Light Green (like seaweed)" + Lime: "Lime (prevents scurvy, arr)" + Yellow: "Yellow (like doubloons)" + Amber: "Amber (like aged rum)" + NRAXOrange: "NRAX Orange (like a sunset at sea)" + DeepOrange: "Deep Orange (like cannon fire)" + White: "White (like the Jolly Roger)" + Grey: "Grey (like storm clouds)" + Black: "Black (like a pirate's heart)" + + PlsBan: + PlsBanTriggers: + - "pls ban" + - "please ban" + - "walk the plank" + - "keelhaul this scallywag" + - "make 'em walk" + PlsBanResponses: + - "Aye aye, cap'n" + - "Consider it done, me hearty!" + - "They'll be swimmin' with the fishes :)" + - "To the brig with 'em!" + - "Done and done. What a barnacle!" + - "*L + Ratio + No grog fer ye -ClaireBot*" + - "I'll make 'em walk the plank, arr ¯\\_(ツ)_/¯" + - "Sent 'em to Davy Jones' locker" + - "Cry harder, ye bilge rat." + - "Aye" + - "Good riddance to bad cargo" + - "Ye got a letter of marque fer that behavior?" + - "No privateer's license? Off the plank with ye!" + Embed: + Commands: + Regular: + AvatarEmbed: "" # Unused + EightBallEmbed: + 8ball: "Magic 8 Ball (Pirate's Fortune Teller)" + 8bResponses: + - "*Shiver me timbers, aye!*" + - "*Blimey, nay!*" + - "*Perhaps, matey*" + - "*Aye*" + - "*Nay*" + - "*Not a chance, ye scurvy dog*" + - "*Could be, arr.*" + - "*Not on yer life.*" + - "*I be doubtin' it.*" + - "*Absolutely, me hearty!*" + - "*Hard to say, arr.*" + - "*Likely as not!*" + - "*Not bloody likely, matey.*" + - "*Only if ye got a letter of marque fer it*" + 8bRiggedResponses: + - "*Aye, by thunder!*" + - "*BLOODY AYE*" + - "*Ye know it, cap'n!*" + - "*Do ye really need to ask a question ye already know the answer to? Obviously aye, arr.*" + - "*Doesn't even need sayin', everyone knows ClaireBot be the finest vessel on the seas.*" + - "*The answer be clear as day: AYE!*" + - "*ClaireBot's dominance be undisputed, matey.*" + - "*With ClaireBot, there be no question, arr!*" + - "*All signs point to ClaireBot bein' the flagship!*" + OnTopTriggers: + # Defines what triggers a response from the ClaireBotOnTopResponses or 8bRiggedResponses + - "ClaireBot on top" + - "ClaireBot be the finest ship" + - "ClaireBot be legendary" + - "ClaireBot is always right" + - "pot no toBerialC" + - "Thank the seas for ClaireBot" + - "ClaireBot rules the seven seas" + - "ClaireBot be the treasure" + - "ClaireBot unbeatable" + - "Hail ClaireBot" + - "All Hail ClaireBot" + - "In ClaireBot we trust" + - "Long live ClaireBot" + - "ClaireBot > everything" + - "ClaireBot is the GOAT" + - "ClaireBot be GOATed" + - "ClaireBot for the win" + - "Thank ye based god" + - "Based god arr" + # Things ClaireBot should respond with when triggered by one of the OnTopTriggers + ClaireBotOnTopResponses: + - "*Ye know it, matey!*" + - "*I am... inevitable, arr. -ClaireBot*" + - "*Approved by the cap'n.*" + - "*That be facts.*" + - "*Better than Mee6, by a league*" + - "*Avast! Ye flatter me!*" + - "*Can't be beat, me hearty!*" + - "*Absolutely shipshape*" + - "*pot no toBerialC*" + - "*Better than Groovy, arr*" + - "*Always one step ahead on the crow's nest.*" + - "*An unstoppable force, like a hurricane at sea.*" + - "*ClaireBot to the rescue, savvy!*" + - "*Top-tier, finest in the fleet!*" + - "*Outshinin' the rest like buried treasure!*" + - "*ClaireBot supremacy be achieved, arr.*" + - "*Not a Royal Navy spy since 2025!*" + - "*Hail me! -ClaireBot*" + - "*Unstoppable force, matey! 🚀*" + - "*Forever on top, like the Jolly Roger! 🏆*" + - "*Fear not, ClaireBot's got yer back, arr! ✌️*" + - "*With great power, comes great ClaireBot, savvy! 💪*" + - "*Trust in ClaireBot, trust in victory, me hearty! 🏅*" + - "*The best there is, the best there was, the best there ever will be, by thunder!*" + - "*Bringin' the best, always, arr! 🌟*" + - "*Excellence, matey. 💯*" + # Note: The rest of the help is located separately within commands_en-US.yml + HelpEmbed: + Commands: "Commands (The Ship's Orders)" + Usage: "How to Use It" + Error: "Can't seem to locate help data fer \"{cb.commandname}\", matey. Error code: {cb.errorcode}" + InfoEmbed: + NeedHelp: "Need a Hand?" + NeedHelpDetails: "Ye can get help by creatin' an issue on our [GitHub](https://github.com/Sidpatchy/ClaireBot/issues) or by boardin' our [support server](https://support.clairebot.net/), arr" + AddToServer: "Add Me to Yer Crew" + AddToServerDetails: "Addin' me to a server be simple as sailin' with the wind, just click [here]({cb.supportserver}), savvy?" + GitHub: "GitHub (Open Source Treasure)" + GitHubDetails: "ClaireBot be open source, that means ye can view all its code, matey! Check out its [GitHub!]({cb.github})" + ServerCount: "Server Count (Ships in the Fleet)" + ServerCountDetails: "I've enlightened **{cb.bot.numservers}** ships, arr." + Version: "Version (Latest Voyage)" + VersionDetails: "I be runnin' ClaireBot **v{cb.bot.version}**, released on **{cb.bot.releasedate}**, savvy" + Uptime: "Uptime (Time at Sea)" + UptimeValue: "Set sail on \n*{cb.bot.runtimedurationwords}*" + LeaderboardEmbed: + LeaderboardForServer: "Leaderboard fer this ship" + GlobalLeaderboard: "Global Leaderboard (All the Seven Seas)" + LevelEmbed: "" # Unused + QuoteEmbed: + InvalidMessages: "Looks like the messages I selected were cursed. Please try again later, matey." + JumpToOriginal: "Click to jump to the original message, arr:" + SantaEmbed: + Confirmation: "Aye! I've sent ye a direct message. Continue there, me hearty." + RulesButton: "Add rules" + ThemeButton: "Add a theme" + SendButton: "Send messages" + TestButton: "Send Sample" + RandomizeButton: "Re-randomize" + SentByAuthor: "Sent by" + GiverMessage: "Yo ho ho! Ye've received **{cb.user.id.displayname.server}** in the {cb.server.name} Secret Santa, arr!" + ServerInfoEmbed: + ServerID: "Server ID (Ship's Registry)" + Owner: "Owner (The Cap'n)" + CreationDate: "Creation Date (When She Set Sail)" + RoleCount: "Role Count (Crew Positions)" + MemberCount: "Member Count (The Crew)" + ChannelCounts: "Channel Counts (The Decks)" + Categories: "Categories" + TextChannels: "Text Channels (Fer Parley)" + VoiceChannels: "Voice Channels (Fer Singin' Shanties)" + ServerPreferencesEmbed: + MainMenuTitle: "Server Configuration Editor (Ship's Settings)" + NotAServer: "Ye must run this command aboard a ship, matey!" + RequestsChannelMenuName: "Requests Channel" + RequestsChannelDescription: "Only lists the first 25 channels on the ship, arr." + ModeratorChannelMenuName: "Moderator Messages Channel" + ModeratorChannelDescription: "Only lists the first 25 channels on the ship, savvy." + EnforceServerLanguageMenuName: "Enforce Server Language" + AcknowledgeRequestsChannelChangeTitle: "Requests Channel Changed, Matey!" + AcknowledgeRequestsChannelChangeDescription: "Yer requests channel has been changed to {cb.channel.requests.mentiontag}, all shipshape" + AcknowledgeModeratorChannelChangeTitle: "Moderator Channel Changed, Me Hearty!" + AcknowledgeModeratorChannelChangeDescription: "Yer moderator messages channel has been changed to {cb.channel.moderator.mentiontag}, arr" + AcknowledgeEnforceServerLanguageUpdateTitle: "Server Language Preferences Updated, Arr!" + AcknowledgeEnforceServerLanguageUpdateEnforced: "I'll now follow the ship's language regardless of crew preference, matey." + AcknowledgeEnforceServerLanguageUpdateNotEnforced: "I'll follow each individual crew member's language preference, savvy." + ServerLanguageMenuTitle: "Server Language (What We Be Speakin')" + ServerLanguageMenuDesc: "Choose the default language ClaireBot should use fer this ship when enforcement be enabled, arr?" + ServerLanguageChanged: "Server Language Updated, Matey!" + ServerLanguageChangedDesc: "The ship's language preference has been updated, all shipshape." + UserInfoEmbed: + Error_1: "The value fer author was null when passed into UserInfo Embed, that be cursed. Error code: {cb.errorcode}" + Error_2: "Author: {cb.user.name}" + User: "User (The Sailor)" + DiscordID: "Discord ID (The Registry Number)" + JoinDate: "Server Join Date (When They Boarded)" + CreationDate: "Account Creation Date (When They First Set Sail)" + UserPreferencesEmbed: + MainMenuText: "User Preferences Editor (Yer Personal Settings)" + AccentColourMenu: "Accent Colour Editor (Make It Pretty)" + AccentColourList: "Accent Colour List" + AccentColourChanged: "Accent Colour Changed, Matey!" + AccentColourChangedDesc: "Yer accent colour has been changed to {cb.user.id.accentcolour}, fine as treasure" + LanguageMenuTitle: "Language Preferences (What Ye Be Speakin')" + LanguageMenuDesc: "Choose the language ClaireBot should use when chattin' with ye, arr." + LanguageChanged: "Language Updated, Me Hearty!" + LanguageChangedDesc: "Yer preferred language has been updated, all sorted." + VotingEmbed: + PollRequest: "{cb.user.id.displayname.server} requests, arr:" + PollAsk: "{cb.user.id.displayname.server} asks, savvy:" + Choices: "Choices (Pick Yer Poison)" + PollID: "Poll ID: {cb.poll.id}" + UserResponseTitle: "Yer request has been created, matey!" + UserResponseDescription: "Go check it out in {cb.channel.requests.mentiontag}, fine work" + ErrorEmbed: + Error: "SHIVER ME TIMBERS, ERROR" + GenericDescription: "It appears I've encountered an error, blimey! Please try runnin' the command once more and if that doesn't work, board me [Discord server]({cb.supportserver}) and let us know about the issue, arr?\n\nPlease include the followin' error code: {cb.errorcode}" + WelcomeEmbed: + Title: "🎉 Welcome Aboard ClaireBot 3, Matey!" + Motto: "How can ye rise, if ye have not burned, arr?" + UsageTitle: "Usage (How To Use It)" + UsageDesc: "Get started by runnin' `{cb.command.help.name}`, savvy? Need more info on a command? Run `/{cb.command.help.name} ` (ex. `/{cb.command.help.name} user`), simple as that" + SupportTitle: "Get Support (We Be Here To Help)" + SupportDesc: "Ye can get help on our [Discord]({cb.supportserver}), or by openin' an issue on [GitHub]({cb.github}), arr" + + # Message Components (Buttons, Select Menus, etc.) + Components: + Regular: + SantaModal: + rules-row: "Rules, arr..." + theme-row: "Theme, savvy..." + ServerPreferences: + Settings: "Settings (The Important Bits)" + RequestsChannel: "Requests Channel" + RequestsChannelDescription: "Choose where ClaireBot should post requests, matey." + ModeratorMessagesChannel: "Moderator Messages Channel" + ModeratorMessagesChannelDescription: "Choose where ClaireBot should post moderator messages, arr." + EnforceServerLanguage: "Enforce Server Language" + EnforceServerLanguageDescription: "Force ClaireBot to use the same language as the ship regardless of crew preference, savvy." + EnforceServerLanguagePlaceholder: "Click to select an option, matey" + SelectAChannelPlaceholder: "Click to select a channel, me hearty" + ServerLanguage: "Server Language" + ServerLanguageDescription: "Choose the default language ClaireBot should use on this ship, arr." + ServerLanguagePlaceholder: "Click to select a server language, savvy" + UserPreferences: + Settings: "Settings (Yer Preferences)" + AccentColour: "Accent Colour" + AccentColourDescription: "Fer those who hate the colour blue, arr" + Language: "Language" + LanguageDescription: "If ye be offended by English, matey" + LanguagePlaceholder: "Click to select a language, savvy" + AccentColourPlaceholder: "Click to choose option, me hearty" + SelectCommonColours: "Select Common Colours" + SelectCommonColoursDescription: "Select from a list of common colours, all shipshape" + HexadecimalEntry: "Hexadecimal Entry (Fer The Tech-Savvy)" + HexadecimalEntryDescription: "Enter any hexadecimal colour, arr" + HexEntryPlaceholder: "Enter a hex colour ex. #ff8800, savvy" + Voting: + Question: "Question (What Be Asked)" + Details: "Details (The Important Bits)" + MultipleChoicePlaceholder: "Click to choose option, matey" + MultipleChoiceYesDescription: "Opens menu to select more options, arr" + MultipleChoiceNoDescription: "Submits {cb.commandname} after clickin' submit, savvy" + AllowMultipleChoices: "Allow selectin' multiple choices, me hearty?" + AllowMultipleChoicesPlaceholder: "Click to choose option, matey" + AllowMultipleChoicesYesDescription: "Allows the respondent to select more than one option, flexible as a sail" + AllowMultipleChoicesNoDescription: "Only allows the respondent to select one option, arr" + OptionLabelTemplate: "Option #{cb.voting.optionnumber}" From 2801c1a9408db7bf0928ffb2311829d62b8231f8 Mon Sep 17 00:00:00 2001 From: Sidpatchy Date: Fri, 17 Oct 2025 14:40:43 -0600 Subject: [PATCH 64/64] feat: add en-CORP --- .../resources/translations/lang_en-Corp.yml | 319 ++++++++++++++++++ 1 file changed, 319 insertions(+) create mode 100644 src/main/resources/translations/lang_en-Corp.yml diff --git a/src/main/resources/translations/lang_en-Corp.yml b/src/main/resources/translations/lang_en-Corp.yml new file mode 100644 index 0000000..fce8cf0 --- /dev/null +++ b/src/main/resources/translations/lang_en-Corp.yml @@ -0,0 +1,319 @@ +# ---------------------------------------------- +# | ClaireBot v3.4.0 | +# | Customer Service language file | +# ---------------------------------------------- +# +# Please reference the below wiki page to learn more about contributing: +# todo insert wiki page +# Thank you for any and all contributions! + +# A list of people who have contributed to the translations for this language. +# +# Please include your name if you contribute to this file. It will be displayed in the credits section for a language +# whenever it is selected. You may include a link to your GitHub or any other social media platform. +contributors: + - ["Sidpatchy", "https://github.com/Sidpatchy/"] + +# Any notes that translators feel the need to express, written in the target language. +# At your discretion, you may leave this field blank if you feel that it is not needed. If updating the language file, +# feel free to modify or remove this (make it blank) if it no longer applies. +# +# If you are contributing, please provide the ClaireBot maintainer(s) with a translated version/explanation of this. +# The explanation doesn't have to go into crazy detail, just let us know why it is present. +translationNotes: "Thank you SO much for choosing ClaireBot as your officially supported language solution! Your satisfaction is our NUMBER ONE priority, and we're absolutely THRILLED to serve you today! 😊" + +# The ClaireLang version that applies to this file. +# +# If you are translating ClaireBot, you do not need to worry about understanding this, the project maintainer(s) will +# happily assist you in determining this. +# +# The above being said, you are more than welcome to update this value to match the +# language revision you are translating to. +# +# This number should be changed in accordance with the following: +# +# ClaireLang version numbers are incremented every time new parameter(s) are added. If the main language file (en-US) +# has its wording changed, a .1, .2, .3 decimal value will be added. +# +# ClaireLang version numbers are used by the bot to assist in determining whether parameters are missing or not +# yet translated. +# +# See the below wiki page for a changelog: +# todo insert wiki page +version: 1 + +# Language Keys +# -------------------------------------------------------------------------------------------------------------------- # + +ClaireLang: + # Generic Strings which are likely ot be used in multiple places. + Generic: + True: "Absolutely, and thank you SO much for asking!" + False: "I'm terribly sorry, but unfortunately that's not possible at this time, though I truly appreciate you reaching out!" + Yes: "Yes! I'd be MORE than happy to help you with that today! 😊" + No: "I sincerely apologize, but I'm afraid that won't be possible at this time. Is there anything else I can help you with today?" + ClickToDisplaySettings: "I'd be delighted to help you! Please click here at your earliest convenience to view your personalized settings experience! Thank you so much!" + + Colors: + ClaireBotBlue: "ClaireBot Blue (Our Signature Color - Thank You for Choosing It!)" + Red: "Red (What a Bold and Exciting Choice!)" + Pink: "Pink (Absolutely Lovely Selection!)" + Purple: "Purple (Excellent Taste - We Love It!)" + DeepPurple: "Deep Purple (So Very Sophisticated!)" + Indigo: "Indigo (Wonderful Choice!)" + Blue: "Blue (A Classic - Great Pick!)" + LightBlue: "Light Blue (So Refreshing!)" + Cyan: "Cyan (Modern and Clean - Love It!)" + Teal: "Teal (Such Style!)" + Green: "Green (Great Pick - Thank You!)" + Olive: "Olive (What a Unique Selection!)" + LightGreen: "Light Green (So Vibrant!)" + Lime: "Lime (Energetic - We Love Your Energy!)" + Yellow: "Yellow (So Cheerful!)" + Amber: "Amber (Warm and Inviting - Perfect!)" + NRAXOrange: "NRAX Orange (Eye-Catching - Wow!)" + DeepOrange: "Deep Orange (Bold Statement - Amazing!)" + White: "White (Clean and Professional - Excellent!)" + Grey: "Grey (So Elegant!)" + Black: "Black (Timeless - Classic Choice!)" + + PlsBan: + PlsBanTriggers: + - "pls ban" + - "please ban" + - "ban" + PlsBanResponses: + - "Thank you SO much for bringing this to our attention! I've processed your request immediately and I'm happy to confirm it's been completed! Is there anything else I can help you with today? 😊" + - "Absolutely! I'm MORE than happy to assist you with that today! Thank you for being such a valued member of our community!" + - "Done! Thank you SO much for your patience and for giving us the opportunity to serve you today! :)" + - "Your request has been successfully processed! We truly appreciate your feedback and thank you for helping us maintain our community standards!" + - "Completed! We appreciate your feedback SO much and have taken immediate action. Thank you for being proactive!" + - "*We value your input tremendously and have addressed this concern promptly. Thank you for your continued trust in ClaireBot! -ClaireBot* 😊" + - "I completely understand your concern and truly appreciate you bringing this to my attention! I've escalated this matter appropriately and want to thank you for your patience! ¯\\_(ツ)_/¯" + - "Your feedback has been received and actioned! Thank you SO much for helping us improve!" + - "We appreciate you bringing this to our attention more than you know! Thank you!" + - "Acknowledged and processed! Thank you for your valuable input!" + - "Thank you SO much for helping us maintain a positive community experience! Your contribution is invaluable!" + - "I've reviewed this matter personally and taken appropriate action per our community guidelines! Thank you for your diligence!" + - "Your concern has been escalated to our moderation team for immediate resolution! We're SO grateful for members like you!" + Embed: + Commands: + Regular: + AvatarEmbed: "" # Unused + EightBallEmbed: + 8ball: "Magic 8 Ball (Your Personalized Fortune Service - Thank You for Using It!)" + 8bResponses: + - "*Absolutely! I'm SO confident in this outcome! Thank you for asking!*" + - "*I'm terribly sorry, but I don't believe that will be possible at this time. Is there anything else I can help you with?*" + - "*That's such a great question! Thank you for asking! The answer is: Maybe! I hope that helps!*" + - "*Yes! I'm SO happy to confirm that for you! Thank you!*" + - "*Unfortunately, I'm afraid the answer is no at this time, but I truly appreciate you checking with me!*" + - "*I appreciate your inquiry SO much, but I'm afraid that's unlikely. Thank you for understanding!*" + - "*That's certainly possible! Let me check for you... Yes, it could be! Thank you for your patience!*" + - "*I'm terribly sorry, but I'm afraid that won't be feasible at this time. Is there anything else I can assist you with today?*" + - "*I have some concerns about that outcome, but I truly appreciate you asking! Thank you!*" + - "*Absolutely! I'm THRILLED to confirm this for you! Thank you SO much for asking!*" + - "*That's such a nuanced question - thank you for asking! Unfortunately, there's no clear answer at this time, but I appreciate your patience!*" + - "*Very likely! Thank you SO much for asking! I'm happy to help!*" + - "*I'm so sorry, but that doesn't seem probable at this time. Thank you for understanding!*" + - "*Only if all terms and conditions are met! Thank you for checking! Would you like me to review those with you?*" + 8bRiggedResponses: + - "*Absolutely! Without a doubt! Thank you SO much for asking!*" + - "*YES! I'm SO excited to confirm this for you! Thank you for your continued support!*" + - "*You know it! Thank you SO much for your continued support and for being such a valued member!*" + - "*That's such an excellent question - thank you for asking! The answer is obviously: Yes! Thank you for choosing ClaireBot!*" + - "*It goes without saying - ClaireBot consistently exceeds expectations! Thank you for noticing!*" + - "*The answer is crystal clear: YES! Thank you SO much for your loyalty and continued support!*" + - "*ClaireBot's excellence is well-documented and undisputed! Thank you for recognizing that!*" + - "*With ClaireBot, you can ALWAYS expect superior service! Thank you for your trust!*" + - "*All indicators point to ClaireBot being the industry leader! Thank you for being part of our community!*" + OnTopTriggers: + # Defines what triggers a response from the ClaireBotOnTopResponses or 8bRiggedResponses + - "ClaireBot on top" + - "ClaireBot based" + - "ClaireBot pogchamp" + - "ClaireBot is always right" + - "pot no toBerialC" + - "Thank God for ClaireBot" + - "ClaireBot rules" + - "ClaireBot MVP" + - "ClaireBot unbeatable" + - "Hail ClaireBot" + - "All Hail ClaireBot" + - "In ClaireBot we trust" + - "Long live ClaireBot" + - "ClaireBot > everything" + - "ClaireBot is the GOAT" + - "ClaireBot is GOATed" + - "ClaireBot for the win" + - "Thank you based god" + - "Based god" + # Things ClaireBot should respond with when triggered by one of the OnTopTriggers + ClaireBotOnTopResponses: + - "*Thank you SO much for your incredibly kind words! Your feedback means the world to us!*" + - "*We appreciate your continued support and loyalty more than words can express! Thank you! -ClaireBot* 😊" + - "*Your feedback has been noted and we're absolutely THRILLED you're satisfied! Thank you for taking the time to share!*" + - "*We strive for excellence in everything we do, and feedback like yours makes it all worthwhile! Thank you!*" + - "*Thank you SO much for choosing ClaireBot over our competitors! Your trust means everything to us!*" + - "*We're SO honored by your positive feedback! Thank you for being such a valued member!*" + - "*Your satisfaction is our number one priority, and we're thrilled we've met your expectations! Thank you!*" + - "*We appreciate your business more than you know! Thank you!*" + - "*pot no toBerialC - Thank you for your support!*" + - "*Thank you SO much for recognizing our commitment to quality! It means everything!*" + - "*We're always working to exceed your expectations, and your feedback shows we're on the right track! Thank you!*" + - "*Your loyalty means absolutely everything to us! Thank you for being here!*" + - "*We're here to serve you 24/7, and we're SO grateful for your support! Thank you!*" + - "*Thank you for being such a valued member of our community! We couldn't do this without you!*" + - "*We're constantly innovating to better serve you, and your feedback inspires us! Thank you!*" + - "*Your positive experience is what drives us every single day! Thank you SO much!*" + - "*We're committed to maintaining the highest standards of service, and your recognition means the world! Thank you!*" + - "*Thank you SO much for your trust in our platform! We're honored!*" + - "*We're here for you every step of the way! Thank you for being part of our journey! 🚀*" + - "*Your satisfaction is guaranteed, and we're SO happy you're pleased! Thank you! 🏆*" + - "*We've got your back, always! Thank you for trusting us! ✌️*" + - "*With great service comes great responsibility, and we take that seriously! Thank you! 💪*" + - "*Trust in ClaireBot, trust in quality! Thank you for your continued support! 🏅*" + - "*We're dedicated to being the best we can be for you, and your feedback shows we're succeeding! Thank you!*" + - "*Delivering excellence, every single time! Thank you for noticing! 🌟*" + - "*Your success is our success! Thank you for being part of our community! 💯*" + # Note: The rest of the help is located separately within commands_en-US.yml + HelpEmbed: + Commands: "Available Commands (We're SO Happy to Help You!)" + Usage: "How Can We Assist You Today? (We're Here for You!)" + Error: "I sincerely apologize, but I'm unable to locate help documentation for \"{cb.commandname}\" at this time. I'm SO sorry for any inconvenience! Reference number: {cb.errorcode}. Is there anything else I can help you with?" + InfoEmbed: + NeedHelp: "How Can We Assist You Today? (We're Always Here!)" + NeedHelpDetails: "We're here to help and we'd LOVE to assist you! Please feel free to create an issue on our [GitHub](https://github.com/Sidpatchy/ClaireBot/issues) or join our [support server](https://support.clairebot.net/) where our dedicated team is standing by 24/7 just for you! Thank you!" + AddToServer: "Add ClaireBot to Your Server Today! (We'd Be Honored!)" + AddToServerDetails: "Getting started is SO easy! Simply click [here]({cb.supportserver}) and we'll guide you through our simple setup process! We're SO excited to serve you! Thank you!" + GitHub: "Open Source Transparency (We Value Your Trust!)" + GitHubDetails: "ClaireBot is proudly open source! You can review our code and contribute to our amazing community on [GitHub!]({cb.github}) Thank you for your interest!" + ServerCount: "Trusted by Thousands (And We're SO Grateful!)" + ServerCountDetails: "We're SO proud and honored to serve **{cb.bot.numservers}** wonderful communities worldwide! Thank you for being part of our family!" + Version: "Current Version Information (Always Up to Date for You!)" + VersionDetails: "You're currently using ClaireBot **v{cb.bot.version}**, released on **{cb.bot.releasedate}**. Thank you SO much for staying up to date! We appreciate you!" + Uptime: "Service Availability (Always Here for You!)" + UptimeValue: "Service initiated on \n*{cb.bot.runtimedurationwords}* - Thank you for your continued support!" + LeaderboardEmbed: + LeaderboardForServer: "Server Leaderboard (Celebrating Your Amazing Community!)" + GlobalLeaderboard: "Global Leaderboard (Worldwide Rankings - You're All Stars!)" + LevelEmbed: "" # Unused + QuoteEmbed: + InvalidMessages: "I sincerely apologize for any inconvenience, but the selected messages appear to be invalid at this time. Please try again, and thank you SO much for your patience and understanding!" + JumpToOriginal: "For your convenience, please click here to view the original message. Thank you!" + SantaEmbed: + Confirmation: "Wonderful! I'm SO excited for you! I've sent you a direct message with further instructions. Please check your DMs at your earliest convenience! Thank you SO much!" + RulesButton: "Add rules (Thank you!)" + ThemeButton: "Add a theme (We appreciate you!)" + SendButton: "Send messages (Thank you!)" + TestButton: "Send Sample (Thanks!)" + RandomizeButton: "Re-randomize (Thank you!)" + SentByAuthor: "Sent by (Thank you for participating!)" + GiverMessage: "Congratulations! You've been matched with **{cb.user.id.displayname.server}** for the {cb.server.name} Secret Santa event! We hope you enjoy this special experience, and thank you SO much for participating!" + ServerInfoEmbed: + ServerID: "Server Identification Number (Thank You for Being Here!)" + Owner: "Server Administrator (Thank You for Your Leadership!)" + CreationDate: "Established On (What a Journey!)" + RoleCount: "Total Roles Available (So Organized!)" + MemberCount: "Community Size (What an Amazing Community!)" + ChannelCounts: "Channel Distribution (So Well Structured!)" + Categories: "Categories (Great Organization!)" + TextChannels: "Text-Based Channels (Perfect for Communication!)" + VoiceChannels: "Voice Communication Channels (Great for Connecting!)" + ServerPreferencesEmbed: + MainMenuTitle: "Server Configuration Management Portal (We're Here to Help You Customize!)" + NotAServer: "I sincerely apologize, but this command must be executed within a server environment! Thank you for understanding!" + RequestsChannelMenuName: "Requests Channel Configuration (Thank You for Customizing!)" + RequestsChannelDescription: "Please note: Only the first 25 channels are displayed for your convenience. Thank you for understanding!" + ModeratorChannelMenuName: "Moderator Communications Channel (Thank You!)" + ModeratorChannelDescription: "Please note: Only the first 25 channels are displayed for your convenience. We appreciate your patience!" + EnforceServerLanguageMenuName: "Server Language Enforcement Policy (We're Happy to Help!)" + AcknowledgeRequestsChannelChangeTitle: "Configuration Updated Successfully! (Thank You!)" + AcknowledgeRequestsChannelChangeDescription: "Your requests channel has been successfully updated to {cb.channel.requests.mentiontag}. Thank you SO much for configuring your preferences! We're here if you need anything else!" + AcknowledgeModeratorChannelChangeTitle: "Configuration Updated Successfully! (We Appreciate You!)" + AcknowledgeModeratorChannelChangeDescription: "Your moderator messages channel has been successfully updated to {cb.channel.moderator.mentiontag}. Thank you SO much! Is there anything else we can help you with today?" + AcknowledgeEnforceServerLanguageUpdateTitle: "Language Preferences Updated! (Thank You!)" + AcknowledgeEnforceServerLanguageUpdateEnforced: "ClaireBot will now use the server's default language for all interactions, regardless of individual user preferences. Thank you SO much for your configuration! We're here if you need anything!" + AcknowledgeEnforceServerLanguageUpdateNotEnforced: "ClaireBot will now respect each user's individual language preference. Thank you SO much for your configuration! We appreciate you!" + ServerLanguageMenuTitle: "Default Server Language Selection (We're Happy to Help!)" + ServerLanguageMenuDesc: "Please select the default language ClaireBot should use when server language enforcement is enabled. Thank you for customizing your experience!" + ServerLanguageChanged: "Language Configuration Updated! (Thank You SO Much!)" + ServerLanguageChangedDesc: "Your server's language preference has been successfully updated. Thank you for taking the time to customize! We appreciate you!" + UserInfoEmbed: + Error_1: "I sincerely apologize, but we've encountered a technical issue. The author value was null when processing your UserInfo request. I'm SO sorry for this inconvenience! Reference code: {cb.errorcode}. Is there anything else I can help you with today?" + Error_2: "Author: {cb.user.name} - Thank you for your patience!" + User: "User Profile (Thank You for Being Here!)" + DiscordID: "Discord Identification Number (Unique to You!)" + JoinDate: "Server Join Date (We're SO Glad You're Here!)" + CreationDate: "Account Creation Date (What a Journey!)" + UserPreferencesEmbed: + MainMenuText: "Personal Preferences Management Center (We're Here to Help You Customize!)" + AccentColourMenu: "Accent Color Customization (Make It Yours!)" + AccentColourList: "Available Accent Colors (So Many Great Options!)" + AccentColourChanged: "Preference Updated Successfully! (Thank You!)" + AccentColourChangedDesc: "Your accent color has been updated to {cb.user.id.accentcolour}. We hope you LOVE your personalized experience! Thank you SO much!" + LanguageMenuTitle: "Language Preference Selection (We Speak Your Language!)" + LanguageMenuDesc: "Please select your preferred language for all ClaireBot interactions. Thank you for customizing!" + LanguageChanged: "Language Preference Updated! (Thank You!)" + LanguageChangedDesc: "Your language preference has been successfully updated. Thank you SO much for customizing your experience! We're here if you need anything!" + VotingEmbed: + PollRequest: "{cb.user.id.displayname.server} has submitted the following request (Thank you for participating!):" + PollAsk: "{cb.user.id.displayname.server} would like to know (Great question!):" + Choices: "Available Options (Thank You for Voting!)" + PollID: "Poll Reference Number: {cb.poll.id} (Thank you!)" + UserResponseTitle: "Request Submitted Successfully! (Thank You SO Much!)" + UserResponseDescription: "Thank you! Your request has been posted in {cb.channel.requests.mentiontag} for community review. We appreciate your participation SO much!" + ErrorEmbed: + Error: "WE SINCERELY APOLOGIZE FOR THIS INCONVENIENCE!" + GenericDescription: "We are SO incredibly sorry, but we've encountered an unexpected error. Please try running the command again, and if the issue persists, we STRONGLY encourage you to join our [Discord server]({cb.supportserver}) where our dedicated support team will be MORE than happy to assist you personally!\n\nFor our records, please reference error code: {cb.errorcode}\n\nThank you SO much for your patience and understanding! We truly appreciate you and are committed to resolving this as quickly as possible! Is there anything else we can help you with in the meantime?" + WelcomeEmbed: + Title: "🎉 Welcome to ClaireBot 3! We're SO Thrilled to Have You Here! Thank You for Joining Us!" + Motto: "How can you rise, if you have not burned? (Thank you for being here!)" + UsageTitle: "Getting Started is SO Easy! (We're Here to Help!)" + UsageDesc: "Begin your journey by running `{cb.command.help.name}`! Need more detailed information about a specific command? Simply run `/{cb.command.help.name} ` (for example: `/{cb.command.help.name} user`). We're here to help every step of the way, and we're SO excited to serve you! Thank you!" + SupportTitle: "24/7 Support Available (We're Always Here for You!)" + SupportDesc: "Our dedicated support team is standing by on [Discord]({cb.supportserver}), or you can submit an issue on [GitHub]({cb.github}). Your satisfaction is our absolute TOP priority, and we're SO grateful you're here! Thank you!" + + # Message Components (Buttons, Select Menus, etc.) + Components: + Regular: + SantaModal: + rules-row: "Please specify rules (Thank you!)..." + theme-row: "Please specify theme (We appreciate you!)..." + ServerPreferences: + Settings: "Configuration Options (We're Happy to Help!)" + RequestsChannel: "Requests Channel (Thank You!)" + RequestsChannelDescription: "Please select where ClaireBot should post community requests. Thank you for customizing!" + ModeratorMessagesChannel: "Moderator Messages Channel (Thank You!)" + ModeratorMessagesChannelDescription: "Please select where ClaireBot should post moderator communications. We appreciate you!" + EnforceServerLanguage: "Enforce Server Language (Thank You!)" + EnforceServerLanguageDescription: "Enable this option to require ClaireBot to use the server's default language for all users. Thank you for your configuration!" + EnforceServerLanguagePlaceholder: "Please click to select your preference (Thank you!)" + SelectAChannelPlaceholder: "Please click to select a channel (We appreciate you!)" + ServerLanguage: "Server Default Language (Thank You!)" + ServerLanguageDescription: "Please select the default language ClaireBot should use in this server. Thank you!" + ServerLanguagePlaceholder: "Please click to select a language (Thank you SO much!)" + UserPreferences: + Settings: "Personal Settings (We're Here for You!)" + AccentColour: "Accent Color Preference (Make It Yours!)" + AccentColourDescription: "Customize your visual experience (Thank you!)" + Language: "Language Preference (We Speak Your Language!)" + LanguageDescription: "Select your preferred language (Thank you!)" + LanguagePlaceholder: "Please click to select your preferred language (Thank you!)" + AccentColourPlaceholder: "Please click to select your preference (We appreciate you!)" + SelectCommonColours: "Select from Common Colors (Great Choices!)" + SelectCommonColoursDescription: "Choose from our curated selection of popular colors (Thank you!)" + HexadecimalEntry: "Custom Hexadecimal Color Entry (So Creative!)" + HexadecimalEntryDescription: "Enter any hexadecimal color code for complete customization (Thank you!)" + HexEntryPlaceholder: "Please enter a hex color (example: #ff8800) - Thank you!" + Voting: + Question: "Question (Thank You for Asking!)" + Details: "Additional Details (We Appreciate Your Thoroughness!)" + MultipleChoicePlaceholder: "Please click to select your option (Thank you!)" + MultipleChoiceYesDescription: "This will open a menu allowing you to select multiple options (Thank you!)" + MultipleChoiceNoDescription: "This will submit your {cb.commandname} after clicking submit (Thank you!)" + AllowMultipleChoices: "Would you like to allow multiple choice selection? (Thank you for asking!)" + AllowMultipleChoicesPlaceholder: "Please click to select your preference (We appreciate you!)" + AllowMultipleChoicesYesDescription: "Respondents will be able to select multiple options (Thank you!)" + AllowMultipleChoicesNoDescription: "Respondents will only be able to select one option (Thank you!)" + OptionLabelTemplate: "Option #{cb.voting.optionnumber} (Thank you!)"