diff --git a/src/main/java/io/codemc/bot/commands/CmdApplication.java b/src/main/java/io/codemc/bot/commands/CmdApplication.java index 45df218..53125b2 100644 --- a/src/main/java/io/codemc/bot/commands/CmdApplication.java +++ b/src/main/java/io/codemc/bot/commands/CmdApplication.java @@ -20,39 +20,20 @@ import com.jagrosh.jdautilities.command.SlashCommand; import com.jagrosh.jdautilities.command.SlashCommandEvent; -import io.codemc.api.database.DatabaseAPI; import io.codemc.bot.CodeMCBot; -import io.codemc.bot.utils.APIUtil; +import io.codemc.bot.utils.ApplicationHandler; import io.codemc.bot.utils.CommandUtil; -import net.dv8tion.jda.api.EmbedBuilder; import net.dv8tion.jda.api.entities.Guild; import net.dv8tion.jda.api.entities.Member; -import net.dv8tion.jda.api.entities.MessageEmbed; -import net.dv8tion.jda.api.entities.Role; -import net.dv8tion.jda.api.entities.User; -import net.dv8tion.jda.api.entities.channel.concrete.TextChannel; -import net.dv8tion.jda.api.entities.channel.concrete.ThreadChannel; -import net.dv8tion.jda.api.exceptions.ErrorHandler; import net.dv8tion.jda.api.interactions.InteractionHook; import net.dv8tion.jda.api.interactions.commands.OptionMapping; import net.dv8tion.jda.api.interactions.commands.OptionType; import net.dv8tion.jda.api.interactions.commands.build.OptionData; -import net.dv8tion.jda.api.requests.ErrorResponse; -import net.dv8tion.jda.api.utils.messages.MessageCreateBuilder; -import net.dv8tion.jda.api.utils.messages.MessageCreateData; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; public class CmdApplication extends BotCommand{ - public static final Pattern GITHUB_URL_PATTERN = Pattern.compile("^https://github\\.com/([a-zA-Z0-9-]+)/([a-zA-Z0-9-_.]+?)(?:\\.git)?(?:/.*)?$"); - - private static final Logger LOGGER = LoggerFactory.getLogger(CmdApplication.class); - public CmdApplication(CodeMCBot bot){ super(bot); @@ -73,160 +54,6 @@ public void withModalReply(SlashCommandEvent event){} @Override public void withHookReply(InteractionHook hook, SlashCommandEvent event, Guild guild, Member member){} - public static void handle(CodeMCBot bot, InteractionHook hook, Guild guild, long messageId, String str, boolean accepted){ - TextChannel requestChannel = guild.getTextChannelById(bot.getConfigHandler().getLong("channels", "request_access")); - if(requestChannel == null){ - CommandUtil.EmbedReply.from(hook).error("Unable to retrieve `request-access` channel.").send(); - return; - } - - requestChannel.retrieveMessageById(messageId).queue(message -> { - List embeds = message.getEmbeds(); - if(embeds.isEmpty()){ - CommandUtil.EmbedReply.from(hook).error("Provided message does not have any embeds.").send(); - return; - } - - MessageEmbed embed = embeds.get(0); - if(embed.getFooter() == null || embed.getFields().isEmpty()){ - CommandUtil.EmbedReply.from(hook).error("Embed does not have a footer or any Embed Fields.").send(); - return; - } - - String userId = embed.getFooter().getText().trim(); - if(userId == null || userId.isEmpty()){ - CommandUtil.EmbedReply.from(hook).error("Embed does not have a valid footer.").send(); - return; - } - - String userLink = null; - String repoLink = null; - for(MessageEmbed.Field field : embed.getFields()){ - if(field.getName() == null || field.getValue() == null) - continue; - - if(field.getName().equalsIgnoreCase("user/organisation:")){ - userLink = field.getValue(); - }else - if(field.getName().equalsIgnoreCase("repository:")){ - String link = field.getValue(); - String url = link.substring(link.indexOf("(") + 1, link.indexOf(")")); - - repoLink = url.isEmpty() ? link : url; - } - } - - if(userLink == null || repoLink == null){ - CommandUtil.EmbedReply.from(hook).error("Embed does not have any valid Fields.").send(); - return; - } - - TextChannel channel = guild.getTextChannelById(accepted - ? bot.getConfigHandler().getLong("channels", "accepted_requests") - : bot.getConfigHandler().getLong("channels", "rejected_requests") - ); - if(channel == null){ - CommandUtil.EmbedReply.from(hook) - .error("Unable to retrieve `" + (accepted ? "accepted" : "rejected") + "-requests` channel.") - .send(); - return; - } - - Matcher matcher = GITHUB_URL_PATTERN.matcher(repoLink); - if (!matcher.matches()) { - CommandUtil.EmbedReply.from(hook) - .error("The user/organisation or repository name is invalid!") - .send(); - return; - } - - String username = matcher.group(1); - String project = matcher.group(2); - String jenkinsUrl = bot.getConfigHandler().getString("jenkins", "url") + "/job/" + username + "/job/" + project + "/"; - Member member = guild.getMemberById(userId); - - if (accepted) { - String password = APIUtil.newPassword(); - boolean jenkinsSuccess = APIUtil.createJenkinsJob(hook, username, password, project, repoLink); - boolean nexusSuccess = APIUtil.createNexus(hook, username, password); - if (!nexusSuccess || !jenkinsSuccess) return; - - if (member == null) - LOGGER.warn("Member with ID '{}' not found!", userId); - else { - if (DatabaseAPI.getUser(username) == null) - DatabaseAPI.addUser(username, member.getIdLong()); - } - } - - channel.sendMessage(getMessage(bot, userId, userLink, repoLink, str == null ? jenkinsUrl : str, hook.getInteraction().getUser(), accepted)).queue(m -> { - ThreadChannel thread = message.getStartedThread(); - if(thread != null && !thread.isArchived()){ - thread.getManager().setArchived(true) - .reason("Archiving Thread of deleted Request message.") - .queue(); - } - - message.delete().queue(); - - if(!accepted){ - CommandUtil.EmbedReply.from(hook) - .success("Denied Application of " + (member == null ? "Unknown" : member.getUser().getEffectiveName()) + "!") - .send(); - return; - } - - Role authorRole = guild.getRoleById(bot.getConfigHandler().getLong("author_role")); - if(authorRole == null){ - CommandUtil.EmbedReply.from(hook) - .error("Unable to retrieve Author Role!") - .send(); - return; - } - - if(member == null){ - CommandUtil.EmbedReply.from(hook) - .error("Unable to apply Role. Member not found!") - .send(); - return; - } - - guild.addRoleToMember(member, authorRole) - .reason("[Access Request] Application accepted.") - .queue( - v -> CommandUtil.EmbedReply.from(hook) - .success("Accepted application of " + member.getUser().getEffectiveName() + "!") - .send(), - new ErrorHandler() - .handle( - ErrorResponse.MISSING_PERMISSIONS, - e -> CommandUtil.EmbedReply.from(hook) - .appendWarning("I lack the `Manage Roles` permission to apply the role.") - .send() - ) - ); - }); - }); - } - - private static MessageCreateData getMessage(CodeMCBot bot, String userId, String userLink, String repoLink, String str, User reviewer, boolean accepted){ - String msg = String.join("\n", bot.getConfigHandler().getStringList("messages", (accepted ? "accepted" : "denied"))); - - MessageEmbed embed = new EmbedBuilder() - .setColor(accepted ? 0x00FF00 : 0xFF0000) - .setDescription(msg) - .addField("User/Organisation:", userLink, true) - .addField("Repository:", repoLink, true) - .addField("Reviewer:", reviewer.getAsMention(), true) - .addField(accepted ? "New Project:" : "Reason:", str, false) - .build(); - - return new MessageCreateBuilder() - .addContent("<@" + userId + ">") - .setEmbeds(embed) - .build(); - } - private static class Accept extends BotCommand{ public Accept(CodeMCBot bot){ @@ -265,7 +92,7 @@ public void withHookReply(InteractionHook hook, SlashCommandEvent event, Guild g return; } - handle(bot, hook, guild, messageId, null, true); + ApplicationHandler.handle(bot, hook, guild, messageId, null, true); } catch (NumberFormatException e) { CommandUtil.EmbedReply.from(hook).error("Invalid message ID!").send(); } @@ -313,7 +140,7 @@ public void withHookReply(InteractionHook hook, SlashCommandEvent event, Guild g return; } - handle(bot, hook, guild, messageId, reason, false); + ApplicationHandler.handle(bot, hook, guild, messageId, reason, false); } catch (NumberFormatException e) { CommandUtil.EmbedReply.from(hook).error("Invalid message ID!").send(); } diff --git a/src/main/java/io/codemc/bot/listeners/ButtonListener.java b/src/main/java/io/codemc/bot/listeners/ButtonListener.java index 28ac7e9..966082e 100644 --- a/src/main/java/io/codemc/bot/listeners/ButtonListener.java +++ b/src/main/java/io/codemc/bot/listeners/ButtonListener.java @@ -19,7 +19,7 @@ package io.codemc.bot.listeners; import io.codemc.bot.CodeMCBot; -import io.codemc.bot.commands.CmdApplication; +import io.codemc.bot.utils.ApplicationHandler; import io.codemc.bot.utils.CommandUtil; import net.dv8tion.jda.api.entities.Guild; import net.dv8tion.jda.api.entities.Member; @@ -95,7 +95,7 @@ public void onButtonInteraction(@NotNull ButtonInteractionEvent event){ } event.deferReply(true).queue( - hook -> CmdApplication.handle(bot, hook, guild, event.getMessageIdLong(), null, true) + hook -> ApplicationHandler.handle(bot, hook, guild, event.getMessageIdLong(), null, true) ); }else{ if(lacksRole(roleIds, denyApplicationRoles)){ diff --git a/src/main/java/io/codemc/bot/listeners/ModalListener.java b/src/main/java/io/codemc/bot/listeners/ModalListener.java index 61df47c..c64b3e7 100644 --- a/src/main/java/io/codemc/bot/listeners/ModalListener.java +++ b/src/main/java/io/codemc/bot/listeners/ModalListener.java @@ -20,7 +20,7 @@ import io.codemc.api.jenkins.JenkinsAPI; import io.codemc.bot.CodeMCBot; -import io.codemc.bot.commands.CmdApplication; +import io.codemc.bot.utils.ApplicationHandler; import io.codemc.bot.utils.CommandUtil; import net.dv8tion.jda.api.entities.Guild; import net.dv8tion.jda.api.entities.Message; @@ -253,7 +253,7 @@ public void onModalInteraction(@NotNull ModalInteractionEvent event){ if(reason == null || reason.isEmpty()) reason = "*No reason provided*"; - CmdApplication.handle(this.bot, hook, guild, messageId, reason, false); + ApplicationHandler.handle(this.bot, hook, guild, messageId, reason, false); }); default -> CommandUtil.EmbedReply.from(event) diff --git a/src/main/java/io/codemc/bot/utils/ApplicationHandler.java b/src/main/java/io/codemc/bot/utils/ApplicationHandler.java new file mode 100644 index 0000000..8d3908d --- /dev/null +++ b/src/main/java/io/codemc/bot/utils/ApplicationHandler.java @@ -0,0 +1,330 @@ +/* + * Copyright 2024 CodeMC.io + * + * 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. + */ + +package io.codemc.bot.utils; + +import io.codemc.bot.CodeMCBot; +import io.codemc.api.database.DatabaseAPI; +import net.dv8tion.jda.api.EmbedBuilder; +import net.dv8tion.jda.api.entities.*; +import net.dv8tion.jda.api.entities.channel.concrete.TextChannel; +import net.dv8tion.jda.api.entities.channel.concrete.ThreadChannel; +import net.dv8tion.jda.api.exceptions.ErrorHandler; +import net.dv8tion.jda.api.interactions.InteractionHook; +import net.dv8tion.jda.api.requests.ErrorResponse; +import net.dv8tion.jda.api.utils.messages.MessageCreateBuilder; +import net.dv8tion.jda.api.utils.messages.MessageCreateData; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class ApplicationHandler{ + + private static final Logger LOGGER = LoggerFactory.getLogger(ApplicationHandler.class); + + public static void handle(CodeMCBot bot, InteractionHook hook, Guild guild, long messageId, String str, boolean accepted){ + TextChannel requestChannel = guild.getTextChannelById(bot.getConfigHandler().getLong("channels", "request_access")); + if(requestChannel == null){ + CommandUtil.EmbedReply.from(hook).error("Unable to retrieve `request-access` channel.").send(); + return; + } + + hook.editOriginal( + "[1/5] Handling Join Request...\n" + + "- [1/1] Retrieving Request Message..." + ).queue(); + requestChannel.retrieveMessageById(messageId).queue(message -> { + hook.editOriginal( + """ + [2/5] Handling Join Request... + - [<:like:935126958193405962>] Message retrieved! + - [1/3] Validating Message... + - Retrieving Embed... + """ + ).queue(); + List embeds = message.getEmbeds(); + if(embeds.isEmpty()){ + CommandUtil.EmbedReply.from(hook).error("Provided Message does not have any embeds.").send(); + return; + } + + MessageEmbed embed = embeds.get(0); + if(embed.getFooter() == null || embed.getFields().isEmpty()){ + CommandUtil.EmbedReply.from(hook).error("Embed does not have a Footer or any Embed Fields").send(); + return; + } + + hook.editOriginal( + """ + [2/5] Handling Join Request... + - [<:like:935126958193405962>] Message retrieved! + - [2/3] Validating Message... + - Embed found! + - Retrieving User ID... + """ + ).queue(); + + String userId = embed.getFooter().getText() == null ? null : embed.getFooter().getText().trim(); + if(userId == null || userId.isEmpty()){ + CommandUtil.EmbedReply.from(hook).error("Embed does not have a valid footer.").send(); + return; + } + + hook.editOriginalFormat( + """ + [2/5] Handling Join Request... + - [<:like:935126958193405962>] Message retrieved! + - [3/3] Validating Message... + - Embed found! + - Found User ID `%s`. + - Find and validate User and Repository link... + """, userId + ).queue(); + + if (embed.getFields().size() < 2) { + CommandUtil.EmbedReply.from(hook).error("Embed does not have all valid Fields.").send(); + return; + } + + MessageEmbed.Field userField = embed.getFields().get(0); + MessageEmbed.Field repoField = embed.getFields().get(1); + if(userField == null || repoField == null){ + CommandUtil.EmbedReply.from(hook).error("Embed does not have all valid Fields.").send(); + return; + } + + String user = userField.getValue(); + String repo = repoField.getValue(); + + String username = user.substring(1, user.indexOf("]"));; + String userLink = user.substring(user.indexOf("(") + 1, user.length() - 1); + String repoName = repo.substring(1, repo.indexOf("]")); + String repoLink = repo.substring(repo.indexOf("(") + 1, repo.length() - 1); + + if(username == null || userLink == null || repoName == null || repoLink == null){ + CommandUtil.EmbedReply.from(hook).error("Embed does not have any valid Fields.").send(); + return; + } + + hook.editOriginalFormat( + """ + [3/5] Handling Join Request... + - [<:like:935126958193405962>] Message retrieved! + - [<:like:935126958193405962>] Message validated! + - Embed found! + - Found User ID `%s`. + - User and Repository Link found and validated! + - [1/1] Finding `%s-requests` channel... + """, userId, (accepted ? "accepted" : "rejected") + ).queue(); + + TextChannel channel = guild.getTextChannelById(accepted + ? bot.getConfigHandler().getLong("channels", "accepted_requests") + : bot.getConfigHandler().getLong("channels", "rejected_requests") + ); + if(channel == null){ + CommandUtil.EmbedReply.from(hook) + .error("Unable to retrieve `" + (accepted ? "accepted" : "rejected") + "-requests` channel.") + .send(); + return; + } + + hook.editOriginalFormat( + """ + [4/5] Handling Join Request... + - [<:like:935126958193405962>] Message retrieved! + - [<:like:935126958193405962>] Message validated! + - Embed found! + - Found User ID `%s`. + - User and Repository Link found and validated! + - [<:like:935126958193405962>] `%s-requests` channel found! + - [1/2] Removing Join Request... + - Archive Thread... + """, userId, (accepted ? "accepted" : "rejected") + ).queue(); + + String jenkinsUrl = bot.getConfigHandler().getString("jenkins", "url") + "/job/" + username + "/job/" + repoName + "/"; + Member member = guild.getMemberById(userId); + + if(accepted){ + String password = APIUtil.newPassword(); + boolean jenkinsSuccess = APIUtil.createJenkinsJob(hook, username, password, repoName, repoLink); + boolean nexusSuccess = APIUtil.createNexus(hook, username, password); + + if(!jenkinsSuccess || ! nexusSuccess) + return; + + if(member == null){ + LOGGER.warn("Member with ID {} not found!", userId); + }else{ + if(DatabaseAPI.getUser(username) == null) + DatabaseAPI.addUser(username, member.getIdLong()); + } + } + + channel.sendMessage(getMessage(bot, userId, userLink, repoLink, str == null ? jenkinsUrl : str, hook.getInteraction().getUser(), accepted)).queue(m -> { + ThreadChannel thread = message.getStartedThread(); + if(thread != null && !thread.isArchived()){ + thread.getManager().setArchived(true) + .reason("Archiving Thread of deleted Request message.") + .queue(); + } + + hook.editOriginalFormat( + """ + [4/5] Handling Join Request... + - [<:like:935126958193405962>] Message retrieved! + - [<:like:935126958193405962>] Message validated! + - Embed found! + - Found User ID `%s`. + - User and Repository Link found and validated! + - [<:like:935126958193405962>] `%s-requests` channel found! + - [2/2] Removing Join Request... + - Thread archived! + - Delete Request Message... + """, userId, (accepted ? "accepted" : "rejected") + ).queue(); + + message.delete().queue(); + + hook.editOriginalFormat( + """ + [5/5] Handling Join Request... + - [<:like:935126958193405962>] Message retrieved! + - [<:like:935126958193405962>] Message validated! + - Embed found! + - Found User ID `%s`. + - User and Repository Link found and validated! + - [<:like:935126958193405962>] `%s-requests` channel found! + - [<:like:935126958193405962>] Join Request removed! + - Thread archived! + - Request Message deleted! + - %s + """, userId, (accepted ? "accepted" : "rejected"), (accepted ? "[1/2] Giving User role...\n - Finding Author role..." : "[1/1] Finishing...") + ).queue(); + + if(!accepted){ + hook.editOriginalFormat( + """ + [<:like:935126958193405962>] Handling of Join Request complete! + - [<:like:935126958193405962>] Message retrieved! + - [<:like:935126958193405962>] Message validated! + - Embed found! + - Found User ID `%s`. + - User and Repository Link found and validated! + - [<:like:935126958193405962>] `rejected-requests` channel found! + - [<:like:935126958193405962>] Join Request removed! + - Thread archived! + - Request Message deleted! + - [<:like:935126958193405962>] Finished rejecting join request of %s! + """, userId, (member == null ? "*Unknown*" : member.getUser().getEffectiveName()) + ).queue(); + return; + } + + Role authorRole = guild.getRoleById(bot.getConfigHandler().getLong("author_role")); + if(authorRole == null){ + CommandUtil.EmbedReply.from(hook).error("Unable to retrieve Author Role!").send(); + return; + } + + hook.editOriginalFormat( + """ + [5/5] Handling Join Request... + - [<:like:935126958193405962>] Message retrieved! + - [<:like:935126958193405962>] Message validated! + - Embed found! + - Found User ID `%s`. + - User and Repository Link found and validated! + - [<:like:935126958193405962>] `accepted-requests` channel found! + - [<:like:935126958193405962>] Join Request removed! + - Thread archived! + - Request Message deleted! + - [2/2] Giving User role... + - Found Author Role! + - Applying role to user... + """, userId + ).queue(); + + if(member == null){ + CommandUtil.EmbedReply.from(hook).error("Unable to apply Role. Member not found!").send(); + return; + } + + guild.addRoleToMember(member, authorRole) + .reason("[Join Request] Application accepted.") + .queue(v -> hook.editOriginalFormat( + """ + [5/5] Handling Join Request... + - [<:like:935126958193405962>] Message retrieved! + - [<:like:935126958193405962>] Message validated! + - Embed found! + - Found User ID `%s`. + - User and Repository Link found and validated! + - [<:like:935126958193405962>] `accepted-requests` channel found! + - [<:like:935126958193405962>] Join Request removed! + - Thread archived! + - Request Message deleted! + - [<:like:935126958193405962>] Gave User Role! + - Found Author Role! + - Applied Author Role to User! + + **Successfully accepted Join Request of user %s!** + """, userId, member.getUser().getEffectiveName() + ).queue(), + new ErrorHandler() + .handle( + ErrorResponse.MISSING_PERMISSIONS, + e -> CommandUtil.EmbedReply.from(hook) + .error("I lack the `Manage Roles` permission to apply the role.") + .send() + ) + ); + }); + }, e -> { + CommandUtil.EmbedReply.from(hook) + .error( + "Unable to retrieve Message. Encountered error:", + "`" + e.getMessage() + "`" + ).send(); + + LOGGER.warn("Encountered an Exception while retrieving a message!", e); + }); + } + + private static MessageCreateData getMessage(CodeMCBot bot, String userId, String userLink, String repoLink, String str, User reviewer, boolean accepted){ + String msg = String.join("\n", bot.getConfigHandler().getStringList("messages", (accepted ? "accepted" : "denied"))); + + MessageEmbed embed = new EmbedBuilder() + .setColor(accepted ? 0x00FF00 : 0xFF0000) + .setDescription(msg) + .addField("User/Organisation:", userLink, true) + .addField("Repository:", repoLink, true) + .addField("Reviewer:", reviewer.getAsMention(), true) + .addField(accepted ? "New Project:" : "Reason:", str, false) + .build(); + + return new MessageCreateBuilder() + .addContent("<@" + userId + ">") + .setEmbeds(embed) + .build(); + } +}