From 67afca4c050028962ac6e09a4e5ae3e307666fe8 Mon Sep 17 00:00:00 2001 From: Tais993 Date: Thu, 3 Mar 2022 09:33:30 +0100 Subject: [PATCH 1/3] Added WhoIs command --- .../togetherjava/tjbot/commands/Features.java | 1 + .../commands/moderation/WhoIsCommand.java | 263 ++++++++++++++++++ 2 files changed, 264 insertions(+) create mode 100644 application/src/main/java/org/togetherjava/tjbot/commands/moderation/WhoIsCommand.java diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/Features.java b/application/src/main/java/org/togetherjava/tjbot/commands/Features.java index 095ee74662..89b55ad7db 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/Features.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/Features.java @@ -100,6 +100,7 @@ public enum Features { features.add(new RemindCommand(database)); features.add(new QuarantineCommand(actionsStore, config)); features.add(new UnquarantineCommand(actionsStore, config)); + features.add(new WhoIsCommand()); // Mixtures features.add(new FreeCommand(config)); diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/moderation/WhoIsCommand.java b/application/src/main/java/org/togetherjava/tjbot/commands/moderation/WhoIsCommand.java new file mode 100644 index 0000000000..9e0f6b0618 --- /dev/null +++ b/application/src/main/java/org/togetherjava/tjbot/commands/moderation/WhoIsCommand.java @@ -0,0 +1,263 @@ +package org.togetherjava.tjbot.commands.moderation; + +import net.dv8tion.jda.api.EmbedBuilder; +import net.dv8tion.jda.api.entities.GuildVoiceState; +import net.dv8tion.jda.api.entities.Member; +import net.dv8tion.jda.api.entities.Role; +import net.dv8tion.jda.api.entities.User; +import net.dv8tion.jda.api.events.interaction.SlashCommandEvent; +import net.dv8tion.jda.api.interactions.commands.OptionMapping; +import net.dv8tion.jda.api.interactions.commands.OptionType; +import net.dv8tion.jda.api.interactions.components.Button; +import net.dv8tion.jda.api.interactions.components.ButtonStyle; +import net.dv8tion.jda.api.requests.restaction.interactions.ReplyAction; +import org.jetbrains.annotations.NotNull; +import org.togetherjava.tjbot.commands.SlashCommandAdapter; +import org.togetherjava.tjbot.commands.SlashCommandVisibility; +import org.togetherjava.tjbot.commands.utils.DiscordClientAction; + +import javax.annotation.CheckReturnValue; +import java.awt.*; +import java.time.Instant; +import java.time.OffsetDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Collection; +import java.util.Objects; +import java.util.stream.Collectors; + +/** + * This command allows you to look up user (or member) info. + */ +@SuppressWarnings("ClassWithoutLogger") +public final class WhoIsCommand extends SlashCommandAdapter { + private static final String USER_OPTION = "user"; + private static final String SHOW_SERVER_INFO_OPTION = "show_server_specific_info"; + + private static final String USER_PROFILE_PICTURE_SIZE = "4096"; + + // Sun, December 11, 2016, 13:36:30 + private static final DateTimeFormatter DATE_TIME_FORMAT = + DateTimeFormatter.ofPattern("E, MMMM d, u, HH:mm:ss"); + + /** + * Creates an instance. + */ + public WhoIsCommand() { + super("whois", "Provides info about the given user", SlashCommandVisibility.GUILD); + + getData().addOption(OptionType.USER, USER_OPTION, "the user to look up", true) + .addOption(OptionType.BOOLEAN, SHOW_SERVER_INFO_OPTION, + "Whenever to show info that is specific to this server, such as their roles. This is true by default.", + false); + } + + @Override + public void onSlashCommand(@NotNull final SlashCommandEvent event) { + OptionMapping userOption = Objects.requireNonNull(event.getOption(USER_OPTION), + "The given user option cannot be null"); + OptionMapping showServerSpecificInfoOption = event.getOption(SHOW_SERVER_INFO_OPTION); + + User user = userOption.getAsUser(); + Member member = userOption.getAsMember(); + + boolean showServerSpecificInfo = null != member && (null == showServerSpecificInfoOption + || showServerSpecificInfoOption.getAsBoolean()); + + user.retrieveProfile().flatMap((User.Profile profile) -> { + if (showServerSpecificInfo) { + return handleWhoIsMember(event, member, profile); + } else { + return handleWhoIsUser(event, user, profile); + } + }).queue(); + } + + @CheckReturnValue + private static @NotNull ReplyAction handleWhoIsUser(final @NotNull SlashCommandEvent event, + final @NotNull User user, final @NotNull User.Profile profile) { + + StringBuilder descriptionBuilder = + new StringBuilder().append(userIdentificationToStringItem(user)) + .append("\n**Is bot:** ") + .append(user.isBot()) + .append(userFlagsToStringItem(user.getFlags())) + .append("\n**Registration date:** ") + .append(DATE_TIME_FORMAT.format(user.getTimeCreated())); + + EmbedBuilder embedBuilder = + generateEmbedBuilder(event, user, profile, profile.getAccentColor()).setAuthor( + user.getName(), user.getEffectiveAvatarUrl(), user.getEffectiveAvatarUrl()) + .setDescription(descriptionBuilder); + + return event.replyEmbeds(embedBuilder.build()) + .addActionRow(Button.of(ButtonStyle.LINK, "discord://-/users/" + user.getId(), + "Click to see profile")); + } + + @CheckReturnValue + private static @NotNull ReplyAction handleWhoIsMember(final @NotNull SlashCommandEvent event, + final @NotNull Member member, final @NotNull User.Profile profile) { + User user = member.getUser(); + + Color memberColor = member.getColor(); + Color effectiveColor = (null == memberColor) ? profile.getAccentColor() : memberColor; + + StringBuilder descriptionBuilder = + new StringBuilder().append(userIdentificationToStringItem(user)) + .append(voiceStateToStringItem(member)) + .append("\n**Is bot:** ") + .append(user.isBot()) + .append(possibleBoosterToStringItem(member)) + .append(userFlagsToStringItem(user.getFlags())) + .append("\n**Join date:** ") + .append(DATE_TIME_FORMAT.format(member.getTimeJoined())) + .append("\n**Registration date:** ") + .append(DATE_TIME_FORMAT.format(user.getTimeCreated())) + .append("\n**Roles:** ") + .append(formatRoles(member)); + + EmbedBuilder embedBuilder = generateEmbedBuilder(event, user, profile, effectiveColor) + .setAuthor(member.getEffectiveName(), member.getEffectiveAvatarUrl(), + member.getEffectiveAvatarUrl()) + .setDescription(descriptionBuilder); + + return event.replyEmbeds(embedBuilder.build()) + .addActionRow(DiscordClientAction.General.USER.asLinkButton("Click to see profile!", + user.getId())); + } + + private static @NotNull String voiceStateToStringItem(@NotNull final Member member) { + GuildVoiceState voiceState = Objects.requireNonNull(member.getVoiceState(), + "The given voiceState cannot be null"); + if (voiceState.inVoiceChannel()) { + return "\n**In voicechannel:** " + (voiceState.getChannel().getAsMention()); + } else { + return ""; + } + } + + + /** + * Generates whois embed based on the given parameters. + * + * @param event the {@link SlashCommandEvent} + * @param user the {@link User} getting whois'd + * @param profile the {@link net.dv8tion.jda.api.entities.User.Profile} of the whois'd user + * @param effectiveColor the {@link Color} that the embed will become + * @return the generated {@link EmbedBuilder} + */ + private static @NotNull EmbedBuilder generateEmbedBuilder( + @NotNull final SlashCommandEvent event, @NotNull final User user, + final @NotNull User.Profile profile, final Color effectiveColor) { + + EmbedBuilder embedBuilder = new EmbedBuilder().setThumbnail(user.getEffectiveAvatarUrl()) + .setColor(effectiveColor) + .setFooter("Requested by " + event.getUser().getAsTag(), + event.getMember().getEffectiveAvatarUrl()) + .setTimestamp(Instant.now()); + + if (null != profile.getBannerId()) { + embedBuilder.setImage(profile.getBannerUrl() + "?size=" + USER_PROFILE_PICTURE_SIZE); + } + + return embedBuilder; + } + + /** + * Handles boosting properties of a {@link Member} + * + * @param member the {@link Member} to take the booster properties from + * @return user readable {@link String} + */ + private static @NotNull String possibleBoosterToStringItem(final @NotNull Member member) { + OffsetDateTime timeBoosted = member.getTimeBoosted(); + if (null != timeBoosted) { + return "\n**Is booster:** true \n**Boosting since:** " + + DATE_TIME_FORMAT.format(timeBoosted); + } else { + return "\n**Is booster:** false"; + } + } + + /** + * Handles the user's identifying properties (such as ID, tag) + * + * @param user the {@link User} to take the identifiers from + * @return user readable {@link StringBuilder} + */ + private static @NotNull StringBuilder userIdentificationToStringItem(final @NotNull User user) { + return new StringBuilder("**Mention:** ").append(user.getAsMention()) + .append("\n**Tag:** ") + .append(user.getAsTag()) + .append("\n**ID:** ") + .append(user.getId()); + } + + /** + * Formats the roles into a user readable {@link String} + * + * @param member member to take the Roles from + * @return user readable {@link String} of the roles + */ + private static String formatRoles(final @NotNull Member member) { + return member.getRoles().stream().map(Role::getAsMention).collect(Collectors.joining(", ")); + } + + /** + * Formats Hypesquad and the flags + * + * @param flags the {@link Collection} of {@link net.dv8tion.jda.api.entities.User.UserFlag} + * (recommend {@link java.util.EnumSet} + * @return user readable {@link StringBuilder} + */ + private static @NotNull StringBuilder userFlagsToStringItem( + final @NotNull Collection flags) { + String formattedFlags = formatUserFlags(flags); + + if (formattedFlags.isBlank()) { + return hypeSquadToStringItem(flags); + } else { + return hypeSquadToStringItem(flags).append("\n**Flags:** ") + .append(formatUserFlags(flags)); + } + } + + /** + * Formats user readable Hypesquad item + * + * @param flags the {@link Collection} of {@link net.dv8tion.jda.api.entities.User.UserFlag} + * (recommend {@link java.util.EnumSet} + * @return user readable {@link StringBuilder} + */ + private static @NotNull StringBuilder hypeSquadToStringItem( + final @NotNull Collection flags) { + StringBuilder stringBuilder = new StringBuilder("**\nHypesquad:** "); + + if (flags.contains(User.UserFlag.HYPESQUAD_BALANCE)) { + stringBuilder.append(User.UserFlag.HYPESQUAD_BALANCE.getName()); + } else if (flags.contains(User.UserFlag.HYPESQUAD_BRAVERY)) { + stringBuilder.append(User.UserFlag.HYPESQUAD_BRAVERY.getName()); + } else if (flags.contains(User.UserFlag.HYPESQUAD_BRILLIANCE)) { + stringBuilder.append(User.UserFlag.HYPESQUAD_BRILLIANCE.getName()); + } else { + stringBuilder.append("joined none"); + } + + return stringBuilder; + } + + /** + * Formats the flags into a user readable {@link String}, filters Hypesquad relating flags + * + * @param flags the {@link Collection} of {@link net.dv8tion.jda.api.entities.User.UserFlag} + * (recommend {@link java.util.EnumSet} + * @return the user readable string + */ + @NotNull + private static String formatUserFlags(final @NotNull Collection flags) { + return flags.stream() + .map(User.UserFlag::getName) + .filter(name -> (name.contains("Hypesquad"))) + .collect(Collectors.joining(", ")); + } +} From d392fc2ae293c24129bf2ea58000367444be4fc2 Mon Sep 17 00:00:00 2001 From: Zabuzard Date: Tue, 3 May 2022 19:30:29 +0200 Subject: [PATCH 2/3] Fixes after rebase and some code style issues --- .../commands/moderation/WhoIsCommand.java | 105 ++++++++---------- 1 file changed, 46 insertions(+), 59 deletions(-) diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/moderation/WhoIsCommand.java b/application/src/main/java/org/togetherjava/tjbot/commands/moderation/WhoIsCommand.java index 9e0f6b0618..056f43ba8d 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/moderation/WhoIsCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/moderation/WhoIsCommand.java @@ -1,23 +1,20 @@ package org.togetherjava.tjbot.commands.moderation; import net.dv8tion.jda.api.EmbedBuilder; -import net.dv8tion.jda.api.entities.GuildVoiceState; -import net.dv8tion.jda.api.entities.Member; -import net.dv8tion.jda.api.entities.Role; -import net.dv8tion.jda.api.entities.User; -import net.dv8tion.jda.api.events.interaction.SlashCommandEvent; +import net.dv8tion.jda.api.entities.*; +import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; +import net.dv8tion.jda.api.interactions.Interaction; +import net.dv8tion.jda.api.interactions.callbacks.IReplyCallback; import net.dv8tion.jda.api.interactions.commands.OptionMapping; import net.dv8tion.jda.api.interactions.commands.OptionType; -import net.dv8tion.jda.api.interactions.components.Button; -import net.dv8tion.jda.api.interactions.components.ButtonStyle; -import net.dv8tion.jda.api.requests.restaction.interactions.ReplyAction; +import net.dv8tion.jda.api.requests.restaction.interactions.ReplyCallbackAction; import org.jetbrains.annotations.NotNull; import org.togetherjava.tjbot.commands.SlashCommandAdapter; import org.togetherjava.tjbot.commands.SlashCommandVisibility; import org.togetherjava.tjbot.commands.utils.DiscordClientAction; import javax.annotation.CheckReturnValue; -import java.awt.*; +import java.awt.Color; import java.time.Instant; import java.time.OffsetDateTime; import java.time.format.DateTimeFormatter; @@ -52,7 +49,7 @@ public WhoIsCommand() { } @Override - public void onSlashCommand(@NotNull final SlashCommandEvent event) { + public void onSlashCommand(@NotNull final SlashCommandInteractionEvent event) { OptionMapping userOption = Objects.requireNonNull(event.getOption(USER_OPTION), "The given user option cannot be null"); OptionMapping showServerSpecificInfoOption = event.getOption(SHOW_SERVER_INFO_OPTION); @@ -73,63 +70,56 @@ public void onSlashCommand(@NotNull final SlashCommandEvent event) { } @CheckReturnValue - private static @NotNull ReplyAction handleWhoIsUser(final @NotNull SlashCommandEvent event, + private static @NotNull ReplyCallbackAction handleWhoIsUser(final @NotNull IReplyCallback event, final @NotNull User user, final @NotNull User.Profile profile) { - - StringBuilder descriptionBuilder = - new StringBuilder().append(userIdentificationToStringItem(user)) - .append("\n**Is bot:** ") - .append(user.isBot()) - .append(userFlagsToStringItem(user.getFlags())) - .append("\n**Registration date:** ") - .append(DATE_TIME_FORMAT.format(user.getTimeCreated())); + String description = userIdentificationToStringItem(user) + "\n**Is bot:** " + user.isBot() + + userFlagsToStringItem(user.getFlags()) + "\n**Registration date:** " + + DATE_TIME_FORMAT.format(user.getTimeCreated()); EmbedBuilder embedBuilder = generateEmbedBuilder(event, user, profile, profile.getAccentColor()).setAuthor( user.getName(), user.getEffectiveAvatarUrl(), user.getEffectiveAvatarUrl()) - .setDescription(descriptionBuilder); + .setDescription(description); - return event.replyEmbeds(embedBuilder.build()) - .addActionRow(Button.of(ButtonStyle.LINK, "discord://-/users/" + user.getId(), - "Click to see profile")); + return sendEmbedWithProfileAction(event, embedBuilder.build(), user.getId()); } @CheckReturnValue - private static @NotNull ReplyAction handleWhoIsMember(final @NotNull SlashCommandEvent event, - final @NotNull Member member, final @NotNull User.Profile profile) { + private static @NotNull ReplyCallbackAction handleWhoIsMember( + final @NotNull IReplyCallback event, final @NotNull Member member, + final @NotNull User.Profile profile) { User user = member.getUser(); Color memberColor = member.getColor(); Color effectiveColor = (null == memberColor) ? profile.getAccentColor() : memberColor; - StringBuilder descriptionBuilder = - new StringBuilder().append(userIdentificationToStringItem(user)) - .append(voiceStateToStringItem(member)) - .append("\n**Is bot:** ") - .append(user.isBot()) - .append(possibleBoosterToStringItem(member)) - .append(userFlagsToStringItem(user.getFlags())) - .append("\n**Join date:** ") - .append(DATE_TIME_FORMAT.format(member.getTimeJoined())) - .append("\n**Registration date:** ") - .append(DATE_TIME_FORMAT.format(user.getTimeCreated())) - .append("\n**Roles:** ") - .append(formatRoles(member)); + String description = userIdentificationToStringItem(user) + voiceStateToStringItem(member) + + "\n**Is bot:** " + user.isBot() + possibleBoosterToStringItem(member) + + userFlagsToStringItem(user.getFlags()) + "\n**Join date:** " + + DATE_TIME_FORMAT.format(member.getTimeJoined()) + "\n**Registration date:** " + + DATE_TIME_FORMAT.format(user.getTimeCreated()) + "\n**Roles:** " + + formatRoles(member); EmbedBuilder embedBuilder = generateEmbedBuilder(event, user, profile, effectiveColor) .setAuthor(member.getEffectiveName(), member.getEffectiveAvatarUrl(), member.getEffectiveAvatarUrl()) - .setDescription(descriptionBuilder); + .setDescription(description); + + return sendEmbedWithProfileAction(event, embedBuilder.build(), user.getId()); + } - return event.replyEmbeds(embedBuilder.build()) - .addActionRow(DiscordClientAction.General.USER.asLinkButton("Click to see profile!", - user.getId())); + private static @NotNull ReplyCallbackAction sendEmbedWithProfileAction( + final @NotNull IReplyCallback event, @NotNull MessageEmbed embed, + @NotNull String userId) { + return event.replyEmbeds(embed) + .addActionRow( + DiscordClientAction.General.USER.asLinkButton("Click to see profile!", userId)); } private static @NotNull String voiceStateToStringItem(@NotNull final Member member) { GuildVoiceState voiceState = Objects.requireNonNull(member.getVoiceState(), "The given voiceState cannot be null"); - if (voiceState.inVoiceChannel()) { + if (voiceState.inAudioChannel()) { return "\n**In voicechannel:** " + (voiceState.getChannel().getAsMention()); } else { return ""; @@ -140,15 +130,15 @@ public void onSlashCommand(@NotNull final SlashCommandEvent event) { /** * Generates whois embed based on the given parameters. * - * @param event the {@link SlashCommandEvent} + * @param event the {@link SlashCommandInteractionEvent} * @param user the {@link User} getting whois'd * @param profile the {@link net.dv8tion.jda.api.entities.User.Profile} of the whois'd user * @param effectiveColor the {@link Color} that the embed will become * @return the generated {@link EmbedBuilder} */ - private static @NotNull EmbedBuilder generateEmbedBuilder( - @NotNull final SlashCommandEvent event, @NotNull final User user, - final @NotNull User.Profile profile, final Color effectiveColor) { + private static @NotNull EmbedBuilder generateEmbedBuilder(@NotNull final Interaction event, + @NotNull final User user, final @NotNull User.Profile profile, + final Color effectiveColor) { EmbedBuilder embedBuilder = new EmbedBuilder().setThumbnail(user.getEffectiveAvatarUrl()) .setColor(effectiveColor) @@ -183,14 +173,11 @@ public void onSlashCommand(@NotNull final SlashCommandEvent event) { * Handles the user's identifying properties (such as ID, tag) * * @param user the {@link User} to take the identifiers from - * @return user readable {@link StringBuilder} + * @return user readable {@link String} */ - private static @NotNull StringBuilder userIdentificationToStringItem(final @NotNull User user) { - return new StringBuilder("**Mention:** ").append(user.getAsMention()) - .append("\n**Tag:** ") - .append(user.getAsTag()) - .append("\n**ID:** ") - .append(user.getId()); + private static @NotNull String userIdentificationToStringItem(final @NotNull User user) { + return "**Mention:** " + user.getAsMention() + "\n**Tag:** " + user.getAsTag() + + "\n**ID:** " + user.getId(); } /** @@ -213,13 +200,13 @@ private static String formatRoles(final @NotNull Member member) { private static @NotNull StringBuilder userFlagsToStringItem( final @NotNull Collection flags) { String formattedFlags = formatUserFlags(flags); + StringBuilder result = hypeSquadToStringItem(flags); - if (formattedFlags.isBlank()) { - return hypeSquadToStringItem(flags); - } else { - return hypeSquadToStringItem(flags).append("\n**Flags:** ") - .append(formatUserFlags(flags)); + if (!formattedFlags.isBlank()) { + result.append("\n**Flags:** ").append(formattedFlags); } + + return result; } /** From a40b3ffea10aae056275d403512fbb41f25f6e16 Mon Sep 17 00:00:00 2001 From: Zabuzard Date: Tue, 3 May 2022 19:34:55 +0200 Subject: [PATCH 3/3] Fixes from CR (illu) --- .../tjbot/commands/moderation/WhoIsCommand.java | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/application/src/main/java/org/togetherjava/tjbot/commands/moderation/WhoIsCommand.java b/application/src/main/java/org/togetherjava/tjbot/commands/moderation/WhoIsCommand.java index 056f43ba8d..4403353880 100644 --- a/application/src/main/java/org/togetherjava/tjbot/commands/moderation/WhoIsCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/commands/moderation/WhoIsCommand.java @@ -119,11 +119,12 @@ public void onSlashCommand(@NotNull final SlashCommandInteractionEvent event) { private static @NotNull String voiceStateToStringItem(@NotNull final Member member) { GuildVoiceState voiceState = Objects.requireNonNull(member.getVoiceState(), "The given voiceState cannot be null"); - if (voiceState.inAudioChannel()) { - return "\n**In voicechannel:** " + (voiceState.getChannel().getAsMention()); - } else { + + if (!voiceState.inAudioChannel()) { return ""; } + + return "\n**In voicechannel:** " + (voiceState.getChannel().getAsMention()); } @@ -161,12 +162,13 @@ public void onSlashCommand(@NotNull final SlashCommandInteractionEvent event) { */ private static @NotNull String possibleBoosterToStringItem(final @NotNull Member member) { OffsetDateTime timeBoosted = member.getTimeBoosted(); - if (null != timeBoosted) { - return "\n**Is booster:** true \n**Boosting since:** " - + DATE_TIME_FORMAT.format(timeBoosted); - } else { + + if (null == timeBoosted) { return "\n**Is booster:** false"; } + + return "\n**Is booster:** true \n**Boosting since:** " + + DATE_TIME_FORMAT.format(timeBoosted); } /**