diff --git a/.idea/copyright/GeyserDiscordBot.xml b/.idea/copyright/GeyserDiscordBot.xml index 510a393f..070e01ea 100644 --- a/.idea/copyright/GeyserDiscordBot.xml +++ b/.idea/copyright/GeyserDiscordBot.xml @@ -1,6 +1,6 @@ - \ No newline at end of file diff --git a/src/main/java/org/geysermc/discordbot/GeyserBot.java b/src/main/java/org/geysermc/discordbot/GeyserBot.java index 376a5a7a..54fc76d8 100644 --- a/src/main/java/org/geysermc/discordbot/GeyserBot.java +++ b/src/main/java/org/geysermc/discordbot/GeyserBot.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020-2022 GeyserMC. http://geysermc.org + * Copyright (c) 2020-2024 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -27,6 +27,7 @@ import com.jagrosh.jdautilities.command.Command; import com.jagrosh.jdautilities.command.CommandClientBuilder; +import com.jagrosh.jdautilities.command.ContextMenu; import com.jagrosh.jdautilities.command.SlashCommand; import com.jagrosh.jdautilities.commons.waiter.EventWaiter; import io.sentry.Sentry; @@ -78,6 +79,7 @@ public class GeyserBot { public static final Logger LOGGER = LoggerFactory.getLogger(GeyserBot.class); public static final List COMMANDS; public static final List SLASH_COMMANDS; + public static final List CONTEXT_MENUS; public static AbstractStorageManager storageManager; @@ -96,8 +98,9 @@ public class GeyserBot { Set> subTypes = reflections.getSubTypesOf(Command.class); for (Class theClass : subTypes) { // Don't load SubCommands - if (theClass.getName().contains("SubCommand")) + if (theClass.getName().contains("SubCommand")) { continue; + } try { commands.add(theClass.getDeclaredConstructor().newInstance()); LoggerFactory.getLogger(theClass).debug("Loaded Command Successfully!"); @@ -109,8 +112,9 @@ public class GeyserBot { Set> slashSubTypes = reflections.getSubTypesOf(SlashCommand.class); for (Class theClass : slashSubTypes) { // Don't load SubCommands - if (theClass.getName().contains("SubCommand")) + if (theClass.getName().contains("SubCommand")) { continue; + } slashCommands.add(theClass.getDeclaredConstructor().newInstance()); LoggerFactory.getLogger(theClass).debug("Loaded SlashCommand Successfully!"); } @@ -119,9 +123,26 @@ public class GeyserBot { } COMMANDS = commands; SLASH_COMMANDS = slashCommands; + + // Gathers all context menu items from "context_menu" package. + List contextMenus = new ArrayList<>(); + try { + Reflections reflections = new Reflections("org.geysermc.discordbot.context_menus"); + Set> subTypes = reflections.getSubTypesOf(ContextMenu.class); + for (Class theClass : subTypes) { + if (!theClass.getPackageName().startsWith("org.geysermc.discordbot.context_menus")) { + continue; + } + contextMenus.add(theClass.getDeclaredConstructor().newInstance()); + LoggerFactory.getLogger(theClass).debug("Loaded ContextMenu Successfully!"); + } + } catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) { + LOGGER.error("Unable to load context menus", e); + } + CONTEXT_MENUS = contextMenus; } - public static void main(String[] args) throws IOException, LoginException { + public static void main(String[] args) throws IOException { // Load properties into the PropertiesManager Properties prop = new Properties(); prop.load(new FileInputStream("bot.properties")); @@ -169,6 +190,7 @@ public static void main(String[] args) throws IOException, LoginException { client.useHelpBuilder(false); client.addCommands(COMMANDS.toArray(new Command[0])); client.addSlashCommands(SLASH_COMMANDS.toArray(new SlashCommand[0])); + client.addContextMenus(CONTEXT_MENUS.toArray(new ContextMenu[0])); client.setListener(new CommandErrorHandler()); client.setCommandPreProcessBiFunction((event, command) -> !SwearHandler.filteredMessages.contains(event.getMessage().getIdLong())); diff --git a/src/main/java/org/geysermc/discordbot/commands/moderation/RenameCommand.java b/src/main/java/org/geysermc/discordbot/commands/moderation/RenameCommand.java index cb1ceab0..fdb64b6b 100644 --- a/src/main/java/org/geysermc/discordbot/commands/moderation/RenameCommand.java +++ b/src/main/java/org/geysermc/discordbot/commands/moderation/RenameCommand.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020-2022 GeyserMC. http://geysermc.org + * Copyright (c) 2020-2024 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -81,7 +81,7 @@ protected void execute(CommandEvent event) { event.getMessage().replyEmbeds(handle(member, event.getMember(), event.getGuild())).queue(); } - private MessageEmbed handle(Member member, Member moderator, Guild guild) { + public static MessageEmbed handle(Member member, Member moderator, Guild guild) { // Check the user exists if (member == null) { return new EmbedBuilder() diff --git a/src/main/java/org/geysermc/discordbot/context_menus/RenameUserMenu.java b/src/main/java/org/geysermc/discordbot/context_menus/RenameUserMenu.java new file mode 100644 index 00000000..9722ccdc --- /dev/null +++ b/src/main/java/org/geysermc/discordbot/context_menus/RenameUserMenu.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2024-2024 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/GeyserDiscordBot + */ + +package org.geysermc.discordbot.context_menus; + +import com.jagrosh.jdautilities.command.UserContextMenu; +import com.jagrosh.jdautilities.command.UserContextMenuEvent; +import net.dv8tion.jda.api.Permission; +import org.geysermc.discordbot.commands.moderation.RenameCommand; + +public class RenameUserMenu extends UserContextMenu { + public RenameUserMenu() { + this.name = "Rename"; + + this.userPermissions = new Permission[] { Permission.NICKNAME_MANAGE }; + this.botPermissions = new Permission[] { Permission.NICKNAME_MANAGE }; + } + + @Override + protected void execute(UserContextMenuEvent event) { + event.replyEmbeds(RenameCommand.handle(event.getTargetMember(), event.getMember(), event.getGuild())).setEphemeral(true).queue(); + } +} diff --git a/src/main/java/org/geysermc/discordbot/listeners/CommandErrorHandler.java b/src/main/java/org/geysermc/discordbot/listeners/CommandErrorHandler.java index 696e5009..6b08612f 100644 --- a/src/main/java/org/geysermc/discordbot/listeners/CommandErrorHandler.java +++ b/src/main/java/org/geysermc/discordbot/listeners/CommandErrorHandler.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020-2022 GeyserMC. http://geysermc.org + * Copyright (c) 2020-2024 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -28,6 +28,12 @@ import com.jagrosh.jdautilities.command.Command; import com.jagrosh.jdautilities.command.CommandEvent; import com.jagrosh.jdautilities.command.CommandListener; +import com.jagrosh.jdautilities.command.MessageContextMenu; +import com.jagrosh.jdautilities.command.MessageContextMenuEvent; +import com.jagrosh.jdautilities.command.SlashCommand; +import com.jagrosh.jdautilities.command.SlashCommandEvent; +import com.jagrosh.jdautilities.command.UserContextMenu; +import com.jagrosh.jdautilities.command.UserContextMenuEvent; import net.dv8tion.jda.api.EmbedBuilder; import org.geysermc.discordbot.util.BotColors; import pw.chew.chewbotcca.listeners.BotCommandListener; @@ -38,28 +44,82 @@ public class CommandErrorHandler extends BotCommandListener implements CommandListener { @Override - public void onCommandException(CommandEvent event, Command command, Throwable throwable) { - StringWriter sw = new StringWriter(); - PrintWriter pw = new PrintWriter(sw); - throwable.printStackTrace(pw); - String[] errorStack = sw.toString().split("\n"); - int limit = Math.min(errorStack.length, 5); + public void onSlashCommandException(SlashCommandEvent event, SlashCommand command, Throwable throwable) { + String errorMessage = getErrorMessage(throwable); - StringBuilder errorMessage = new StringBuilder(); - for (int i = 0; i < limit; i++) { - errorMessage.append(errorStack[i]).append("\n"); - } + event.replyEmbeds(new EmbedBuilder() + .setTitle("Error handling command") + .setDescription("An error occurred while handling the command") + .addField("Command usage", "/" + command.getName() + (command.getArguments() != null ? " " + command.getArguments() : ""), false) + .addField("Command help", command.getHelp(), false) + .addField("Error", errorMessage, false) + .setTimestamp(Instant.now()) + .setColor(BotColors.FAILURE.getColor()) + .build()).setEphemeral(true).queue(); + + super.onSlashCommandException(event, command, throwable); + } + + @Override + public void onCommandException(CommandEvent event, Command command, Throwable throwable) { + String errorMessage = getErrorMessage(throwable); event.getMessage().replyEmbeds(new EmbedBuilder() .setTitle("Error handling command") .setDescription("An error occurred while handling the command") .addField("Command usage", event.getPrefix() + command.getName() + (command.getArguments() != null ? " " + command.getArguments() : ""), false) .addField("Command help", command.getHelp(), false) - .addField("Error", errorMessage.toString(), false) + .addField("Error", errorMessage, false) .setTimestamp(Instant.now()) .setColor(BotColors.FAILURE.getColor()) .build()).queue(); super.onCommandException(event, command, throwable); } + + @Override + public void onMessageContextMenuException(MessageContextMenuEvent event, MessageContextMenu menu, Throwable throwable) { + String errorMessage = getErrorMessage(throwable); + + event.replyEmbeds(new EmbedBuilder() + .setTitle("Error handling context menu option") + .setDescription("An error occurred while handling the message context menu option") + .addField("Menu option", menu.getName(), false) + .addField("Error", errorMessage, false) + .setTimestamp(Instant.now()) + .setColor(BotColors.FAILURE.getColor()) + .build()).setEphemeral(true).queue(); + + super.onMessageContextMenuException(event, menu, throwable); + } + + @Override + public void onUserContextMenuException(UserContextMenuEvent event, UserContextMenu menu, Throwable throwable) { + String errorMessage = getErrorMessage(throwable); + + event.replyEmbeds(new EmbedBuilder() + .setTitle("Error handling context menu option") + .setDescription("An error occurred while handling the user context menu option") + .addField("Menu option", menu.getName(), false) + .addField("Error", errorMessage, false) + .setTimestamp(Instant.now()) + .setColor(BotColors.FAILURE.getColor()) + .build()).setEphemeral(true).queue(); + + super.onUserContextMenuException(event, menu, throwable); + } + + private String getErrorMessage(Throwable throwable) { + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + throwable.printStackTrace(pw); + String[] errorStack = sw.toString().split("\n"); + int limit = Math.min(errorStack.length, 5); + + StringBuilder errorMessage = new StringBuilder(); + for (int i = 0; i < limit; i++) { + errorMessage.append(errorStack[i]).append("\n"); + } + return errorMessage.toString(); + } } diff --git a/src/main/java/org/geysermc/discordbot/listeners/SwearHandler.java b/src/main/java/org/geysermc/discordbot/listeners/SwearHandler.java index f0a4511f..58c360ff 100644 --- a/src/main/java/org/geysermc/discordbot/listeners/SwearHandler.java +++ b/src/main/java/org/geysermc/discordbot/listeners/SwearHandler.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020-2022 GeyserMC. http://geysermc.org + * Copyright (c) 2020-2024 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -40,6 +40,7 @@ import javax.annotation.Nullable; import java.io.IOException; import java.net.URISyntaxException; +import java.nio.charset.StandardCharsets; import java.text.Normalizer; import java.util.ArrayList; import java.util.HashMap; @@ -80,7 +81,7 @@ public static void loadFilters() { if (fileName.endsWith(".wlist")) { fileCount++; // Load the lines - String[] lines = new String(BotHelpers.bytesFromResource("filters/" + fileName)).split("\n"); + String[] lines = new String(BotHelpers.bytesFromResource("filters/" + fileName), StandardCharsets.UTF_8).split("\n"); for (String line : lines) { filterPatterns.add(Pattern.compile("(^| )" + line.trim() + "( |$)", Pattern.CASE_INSENSITIVE)); } @@ -92,7 +93,7 @@ public static void loadFilters() { GeyserBot.LOGGER.info("Loaded " + filterPatterns.size() + " filter patterns from " + fileCount + " files"); - nicknames = new String(BotHelpers.bytesFromResource("nicknames.wlist")).trim().split("\n"); + nicknames = new String(BotHelpers.bytesFromResource("nicknames.wlist"), StandardCharsets.UTF_8).trim().split("\n"); GeyserBot.LOGGER.info("Loaded " + nicknames.length + " nicknames"); }