diff --git a/src/main/java/pw/chew/chewbotcca/commands/DiffCommand.java b/src/main/java/pw/chew/chewbotcca/commands/DiffCommand.java new file mode 100644 index 00000000..7e0d409e --- /dev/null +++ b/src/main/java/pw/chew/chewbotcca/commands/DiffCommand.java @@ -0,0 +1,303 @@ +/* + * Copyright (C) 2021 Chewbotcca + * + * 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 . + */ +package pw.chew.chewbotcca.commands; + +import com.jagrosh.jdautilities.command.Command; +import com.jagrosh.jdautilities.command.CommandEvent; +import net.dv8tion.jda.api.EmbedBuilder; +import net.dv8tion.jda.api.Permission; +import net.dv8tion.jda.api.entities.Member; +import net.dv8tion.jda.api.entities.Role; +import pw.chew.chewbotcca.util.ArgsParser; + +import java.util.ArrayList; +import java.util.List; + +public class DiffCommand extends Command { + public DiffCommand() { + this.name = "diff"; + this.aliases = new String[]{"compare"}; + this.guildOnly = true; + this.cooldown = 5; + this.cooldownScope = CooldownScope.USER; + this.children = new Command[]{new CompareRolesSubCommand(), new CompareMembersSubCommand()}; + } + + @Override + protected void execute(CommandEvent event) { + String[] args = event.getArgs().split(" "); + if (args.length != 3) { + event.reply("Invalid amount of arguments specified. Example: " + event.getPrefix() + "diff ROLE 708085624514543728 134445052805120001"); + return; + } + + event.reply("Invalid comparison type specified. Valid: `ROLE`, `CHANNEL`, `MEMBER`"); + } + + /** + * Compares roles. + * Takes as input 2 role IDs. Example: diff role 708085624514543728 134445052805120001 + */ + private static class CompareRolesSubCommand extends Command { + + public CompareRolesSubCommand() { + this.name = "role"; + this.guildOnly = true; + this.botPermissions = new Permission[]{Permission.MESSAGE_EMBED_LINKS}; + } + + @Override + protected void execute(CommandEvent event) { + String[] args = event.getArgs().split(" "); + + if (args.length != 2) { + event.reply("Invalid number of arguments provided. Please provide valid role IDs for this server."); + return; + } + + Role base = event.getGuild().getRoleById(args[0]); + if (base == null) { + event.reply("Unable to find base (first) role. Please ensure you are providing a valid ID to compare."); + return; + } + Role compare = event.getGuild().getRoleById(args[1]); + if (compare == null) { + event.reply("Unable to find compare (second) role. Please ensure you are providing a valid ID to compare."); + return; + } + + + EmbedBuilder embed = new EmbedBuilder() + .setTitle("Comparing roles") + .setDescription("Comparing name, color, permissions, and other information.\n" + + "Base role: " + base.getName() + " ( " + base.getId() + " ) " + "\n" + + "Compare role: " + compare.getName() + " ( " + compare.getId() + " ) "); + + if (base.getName().equals(compare.getName())) { + embed.addField("Name", "*Nothing to compare.\n Names are identical.*", true); + } else { + String name = base.getName() + "\n" + + compare.getName(); + embed.addField("Name", name, true); + } + + + if (base.getColor() == compare.getColor()) { + embed.addField("Color", "*Nothing to compare.\n Colors are identical.*", true); + } else { + String baseHex = colorToHex(base.getColor().getRed(), base.getColor().getGreen(), base.getColor().getBlue()); + String compareHex = colorToHex(compare.getColor().getRed(), compare.getColor().getGreen(), compare.getColor().getBlue()); + String color = (base.getColorRaw() == Role.DEFAULT_COLOR_RAW ? "Default color" : baseHex) + "\n" + + (compare.getColorRaw() == Role.DEFAULT_COLOR_RAW ? "Default color" : compareHex); + embed.addField("Color", color, true); + } + embed.addBlankField(false); + + // compare info section + + String information = """ + Hoisted + Mentionable + Bot role + Boost role + Integration role"""; + embed.addField("Information", information, true); + + List baseInfo = new ArrayList<>(); + baseInfo.add(getInfoState(base.isHoisted())); + baseInfo.add(getInfoState(base.isMentionable())); + baseInfo.add(getInfoState(base.getTags().isBot())); + baseInfo.add(getInfoState(base.getTags().isBoost())); + baseInfo.add(getInfoState(base.getTags().isIntegration())); + + embed.addField(base.getName(), String.join("\n", baseInfo), true); + + List compareInfo = new ArrayList<>(); + compareInfo.add(getInfoState(compare.isHoisted())); + compareInfo.add(getInfoState(compare.isMentionable())); + compareInfo.add(getInfoState(compare.getTags().isBot())); + compareInfo.add(getInfoState(compare.getTags().isBoost())); + compareInfo.add(getInfoState(compare.getTags().isIntegration())); + + embed.addField(compare.getName(), String.join("\n", compareInfo), true); + + + if (base.getPermissionsRaw() == compare.getPermissionsRaw()) { + embed.addField("✅ Permissions 🚫", "*Nothing to compare.\n Permissions are identical*", false); + } else { + List baseOnly = new ArrayList<>(); + List compareOnly = new ArrayList<>(); + + + for (Permission perm : base.getPermissions()) { + if (!compare.getPermissions().contains(perm)) { + baseOnly.add(perm); + } + } + + for (Permission perm : compare.getPermissions()) { + if (!base.getPermissions().contains(perm)) { + compareOnly.add(perm); + } + } + + List perms = new ArrayList<>(); + + perms.add(""" + + means compare role has the permission, and base doesn't. + - means compare role doesn't have the perm, but base does. + ```diff"""); + + for (Permission perm : compareOnly) { + perms.add("+ " + perm.getName()); + } + + for (Permission perm : baseOnly) { + perms.add("- " + perm.getName()); + } + + perms.add("```"); + + embed.addField("✅ Permissions 🚫", String.join("\n", perms), false); + } + + event.reply(embed.build()); + } + + /** + * Helper method for %^diff role command about a role's info state. + * + * @param yes if it's green or not + * @return a green sign if true, red if false + */ + private String getInfoState(boolean yes) { + if (yes) { + return "\uD83D\uDFE2"; + } else { + return "\uD83D\uDD34"; + } + } + + /** + * Source: https://stackoverflow.com/questions/3607858/convert-a-rgb-color-value-to-a-hexadecimal-string + * Function that converts RGB values to hexadecimal code. + * + * @param red red value of color + * @param green green value of color + * @param blue blue value of color + * @return the hexadecimal code of the color + */ + private String colorToHex(int red, int green, int blue) { + return String.format("#%02x%02x%02x", red, green, blue); + } + } + + /** + * Compares members + * Takes as input 2 mentioned members. Example diff member @random @random2 + */ + private static class CompareMembersSubCommand extends Command { + + public CompareMembersSubCommand() { + this.name = "member"; + this.guildOnly = true; + this.botPermissions = new Permission[]{Permission.MESSAGE_EMBED_LINKS}; + } + + @Override + protected void execute(CommandEvent event) { + List members; + + try { + members = ArgsParser.parseMembers(event.getArgs(), 2, event.getMessage()); + } catch (IllegalArgumentException e) { + event.reply(""" + Invalid number of arguments provided. + Please provide valid members. + Example : diff member @random @random2."""); + return; + } + + Member base = members.get(0); + Member compare = members.get(1); + + EmbedBuilder embed = new EmbedBuilder() + .setTitle("Comparing members") + .setDescription("Comparing time joined, time boosted, roles and other information.\n" + + "Base member: " + base.getUser().getAsTag() + "\n" + + "Compare member: " + compare.getUser().getAsTag()); + + // Dates of arrival in server + String date = base.getTimeJoined().toString().substring(0, 10) + "\n" + compare.getTimeJoined().toString().substring(0, 10); + embed.addField("Date Joined", date, true); + embed.addBlankField(false); + + // Status comparison + String onlineStatus = base.getUser().getAsTag() + ": " + base.getOnlineStatus().name() + "\n" + + compare.getUser().getAsTag() + ": " + compare.getOnlineStatus().name(); + if (base.getOnlineStatus().equals(compare.getOnlineStatus())) { + embed.addField("Status", "Nothing to compare.", true); + } else { + embed.addField("Status", onlineStatus, true); + } + + // Starting date of boosting time for a member. + String baseBoostingTime; + String compareBoostingTime; + if (base.getTimeBoosted() != null) { + // keeps yyyy-mm-dd + baseBoostingTime = "Boosting since: " + base.getTimeBoosted().toString().substring(0, 10); + } else { + baseBoostingTime = "Not boosting"; + } + + if (compare.getTimeBoosted() != null) { + // keeps yyyy-mm-dd + compareBoostingTime = "Boosting since: " + compare.getTimeBoosted().toString().substring(0, 10); + } else { + compareBoostingTime = "Not boosting"; + } + + embed.addField("Server Boosting", baseBoostingTime + "\n" + compareBoostingTime, true); + embed.addBlankField(false); + + // Roles comparison of members + List baseRoles = new ArrayList<>(); + List compareRoles = new ArrayList<>(); + + baseRoles.add(""" + ```diff"""); + compareRoles.add(""" + ```diff"""); + for (Role role : base.getRoles()) { + baseRoles.add(role.getName()); + } + + for (Role role : compare.getRoles()) { + compareRoles.add(role.getName()); + } + + baseRoles.add("```"); + compareRoles.add("```"); + + embed.addField(base.getUser().getName() + "' Roles ", String.join("\n", baseRoles), true); + embed.addField(compare.getUser().getName() + "' Roles ", String.join("\n", compareRoles), true); + + event.reply(embed.build()); + } + } +} diff --git a/src/main/java/pw/chew/chewbotcca/util/ArgsParser.java b/src/main/java/pw/chew/chewbotcca/util/ArgsParser.java new file mode 100644 index 00000000..c23667e6 --- /dev/null +++ b/src/main/java/pw/chew/chewbotcca/util/ArgsParser.java @@ -0,0 +1,133 @@ +package pw.chew.chewbotcca.util; + +import net.dv8tion.jda.api.entities.Member; +import net.dv8tion.jda.api.entities.Message; +import net.dv8tion.jda.api.utils.MiscUtil; + +import java.util.ArrayList; +import java.util.List; + +public class ArgsParser { + /** + * Parse arguments and return a list of members.
+ * Supports:
+ * 1) User name#0000
+ * 2) <@!mention>
+ * 3) IDs + * + * @param arg the arguments + * @param amount the amount of members needed to parse + * @param msg the message (to get members from) + * @return a list of (possibly null) members with length amount + * @throws IllegalArgumentException if only one member can be parsed + */ + public static List parseMembers(String arg, int amount, Message msg) { + // Try convenience method first + List mentionedMember = msg.getMentionedMembers(); + if (mentionedMember.size() == amount) { + return mentionedMember; + } + + List members = new ArrayList<>(); + + String[] tags = arg.split("#"); + // joe bob#0000 bob joe#0000 becomes "joe bob" "0000 bob joe" "0000", so need amount + 1 + if (tags.length > amount + 1) { + throw new IllegalArgumentException("Too many tags provided!"); + } + + // First check for args + String[] args = new String[amount]; + int i = 0; + + String temp = ""; + for (String anArg : arg.split(" ")) { + if ((i + 1) > args.length) { + throw new IllegalArgumentException("Too many arguments provided!"); + } + // Check for mention + if (Mention.isValidMention(Message.MentionType.USER, anArg)) { + args[i] = anArg; + i++; + continue; + } + + // Check for User ID + if (anArg.length() >= 17) { + try { + MiscUtil.parseSnowflake(anArg); + args[i] = anArg; + i++; + continue; + } catch (NumberFormatException ignored) {} + } + + // Check if it's a valid tag on its own + if (isValidTag(anArg)) { + args[i] = anArg; + i++; + temp = ""; // Reset temp just in case + continue; + } + + // Check if temp is valid + if (isValidTag(temp)) { + args[i] = anArg; + i++; + temp = ""; + } else { + // Add to temp and pray for the best + temp += anArg; + } + } + + // Cycle through each split arg + for (String anArg : args) { + if (anArg == null) { + members.add(null); + continue; + } + try { + // Try parsing ID + long id = Long.parseLong(anArg); + members.add(msg.getGuild().retrieveMemberById(id).complete()); + continue; + } catch (NumberFormatException e) { + // ID failed, let's try parsing mention + Object parsed = Mention.parseMention(anArg, msg.getGuild(), msg.getJDA()); + if (parsed instanceof Member) { + members.add((Member) parsed); + continue; + } + // Okay, that failed. Let's check if it's a tag + if (parsed == null && isValidTag(anArg)) { + members.add(msg.getGuild().getMemberByTag(anArg)); + continue; + } + } + // If all else fails... + members.add(null); + } + return members; + } + + /** + * Checks for "Valid Tag#0000" + * + * @param input the input to test + * @return true if valid, false if not + */ + private static boolean isValidTag(String input) { + if (!input.contains("#")) { + return false; + } + + String[] test = input.split("#"); + if (test.length > 2) { + return false; + } + + // Not valid discriminator, final check so IntelliJ decided to merge these. thanks. + return test[1].length() == 4; + } +} diff --git a/src/main/java/pw/chew/chewbotcca/util/Mention.java b/src/main/java/pw/chew/chewbotcca/util/Mention.java index 043ec731..9f2dc44a 100644 --- a/src/main/java/pw/chew/chewbotcca/util/Mention.java +++ b/src/main/java/pw/chew/chewbotcca/util/Mention.java @@ -19,7 +19,9 @@ import net.dv8tion.jda.api.JDA; import net.dv8tion.jda.api.entities.Guild; import net.dv8tion.jda.api.entities.Member; +import net.dv8tion.jda.api.entities.Message; import net.dv8tion.jda.api.entities.User; +import net.dv8tion.jda.api.utils.MiscUtil; public class Mention { public static User parseUserMention(String mention, JDA jda) { @@ -55,4 +57,43 @@ public static Object parseMention(String mention, Guild server, JDA jda) { } return null; } + + public static boolean isValidMention(Message.MentionType type, String mention) { + String internal = "not numbers"; + if (!(mention.startsWith("<") && mention.endsWith(">"))) { + return false; + } + switch (type) { + case USER -> { + if (mention.startsWith("<@!")) { + internal = mention.replaceAll("[<>!@]", ""); + } else { + return false; + } + } + case CHANNEL -> { + if (mention.startsWith("<#")) { + internal = mention.replaceAll("[<>#]", ""); + } else { + return false; + } + } + case ROLE -> { + if (mention.startsWith("@&")) { + internal = mention.replaceAll("[<>@&]", ""); + } else { + return false; + } + } + } + if (internal.length() < 17) { + return false; + } + try { + MiscUtil.parseSnowflake(internal); + return true; + } catch (NumberFormatException e) { + return false; + } + } }