diff --git a/build-number.txt b/build-number.txt index e71cd9f5..3215a975 100644 --- a/build-number.txt +++ b/build-number.txt @@ -1 +1 @@ -2637 \ No newline at end of file +2653 \ No newline at end of file diff --git a/src/main/java/me/chayapak1/chomens_bot/Configuration.java b/src/main/java/me/chayapak1/chomens_bot/Configuration.java index c9d9cb4e..3ae51440 100644 --- a/src/main/java/me/chayapak1/chomens_bot/Configuration.java +++ b/src/main/java/me/chayapak1/chomens_bot/Configuration.java @@ -107,6 +107,8 @@ public class Configuration { public static class Discord { public boolean enabled = false; public String prefix = "default!"; + public String serverId; + public boolean enableDiscordHashing = true; public String token; public Map servers = new HashMap<>(); public EmbedColors embedColors = new EmbedColors(); diff --git a/src/main/java/me/chayapak1/chomens_bot/command/TrustLevel.java b/src/main/java/me/chayapak1/chomens_bot/command/TrustLevel.java index bdd87977..bdc4bf8d 100644 --- a/src/main/java/me/chayapak1/chomens_bot/command/TrustLevel.java +++ b/src/main/java/me/chayapak1/chomens_bot/command/TrustLevel.java @@ -1,8 +1,13 @@ package me.chayapak1.chomens_bot.command; +import me.chayapak1.chomens_bot.Configuration; +import me.chayapak1.chomens_bot.Main; +import net.dv8tion.jda.api.entities.Role; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; +import java.util.List; + public enum TrustLevel { PUBLIC(0, Component.text("Public").color(NamedTextColor.GREEN)), TRUSTED(1, Component.text("Trusted").color(NamedTextColor.RED)), @@ -16,4 +21,29 @@ public enum TrustLevel { this.level = level; this.component = component; } + + public static TrustLevel fromDiscordRoles (final List roles) { + if (Main.discord == null) return PUBLIC; + + final Configuration.Discord options = Main.discord.options; + + if (options == null) return PUBLIC; + + TrustLevel userTrustLevel = PUBLIC; + + for (final Role role : roles) { + if (role.getName().equalsIgnoreCase(options.ownerRoleName)) { + userTrustLevel = OWNER; + break; + } else if (role.getName().equalsIgnoreCase(options.adminRoleName)) { + userTrustLevel = ADMIN; + break; + } else if (role.getName().equalsIgnoreCase(options.trustedRoleName)) { + userTrustLevel = TRUSTED; + break; + } + } + + return userTrustLevel; + } } diff --git a/src/main/java/me/chayapak1/chomens_bot/data/logging/LogType.java b/src/main/java/me/chayapak1/chomens_bot/data/logging/LogType.java index 849fe0d1..e394b6a3 100644 --- a/src/main/java/me/chayapak1/chomens_bot/data/logging/LogType.java +++ b/src/main/java/me/chayapak1/chomens_bot/data/logging/LogType.java @@ -10,7 +10,8 @@ public enum LogType { ERROR(Component.text("Error").color(NamedTextColor.RED)), COMMAND_OUTPUT(Component.text("Command Output").color(NamedTextColor.LIGHT_PURPLE)), AUTH(Component.text("Auth").color(NamedTextColor.RED)), - SIMPLE_VOICE_CHAT(Component.text("Simple Voice Chat").color(NamedTextColor.AQUA)); + SIMPLE_VOICE_CHAT(Component.text("Simple Voice Chat").color(NamedTextColor.AQUA)), + DISCORD(Component.text("Discord").color(NamedTextColor.BLUE)); public final Component component; diff --git a/src/main/java/me/chayapak1/chomens_bot/discord/DirectMessageEventHandler.java b/src/main/java/me/chayapak1/chomens_bot/discord/DirectMessageEventHandler.java new file mode 100644 index 00000000..c8f9514d --- /dev/null +++ b/src/main/java/me/chayapak1/chomens_bot/discord/DirectMessageEventHandler.java @@ -0,0 +1,127 @@ +package me.chayapak1.chomens_bot.discord; + +import me.chayapak1.chomens_bot.Configuration; +import me.chayapak1.chomens_bot.command.TrustLevel; +import me.chayapak1.chomens_bot.data.logging.LogType; +import me.chayapak1.chomens_bot.plugins.HashingPlugin; +import me.chayapak1.chomens_bot.util.LoggerUtilities; +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.channel.ChannelType; +import net.dv8tion.jda.api.events.message.MessageReceivedEvent; +import net.dv8tion.jda.api.exceptions.ErrorResponseException; +import net.dv8tion.jda.api.hooks.ListenerAdapter; +import net.dv8tion.jda.api.requests.ErrorResponse; +import net.kyori.adventure.text.Component; +import org.jetbrains.annotations.NotNull; + +public class DirectMessageEventHandler extends ListenerAdapter { + private static final String HASH_MESSAGE = "hash"; + + private final JDA jda; + + private final Configuration.Discord options; + + public DirectMessageEventHandler ( + final JDA jda, + final Configuration.Discord options + ) { + this.jda = jda; + this.options = options; + + jda.addEventListener(this); + } + + @Override + public void onMessageReceived (@NotNull final MessageReceivedEvent event) { + if (!options.enableDiscordHashing || event.getChannelType() != ChannelType.PRIVATE) return; + + final Message message = event.getMessage(); + + if (!message.getContentDisplay().equalsIgnoreCase(HASH_MESSAGE)) return; + + final Guild guild; + + try { + guild = jda.getGuildById(options.serverId); + } catch (final NumberFormatException e) { + // there will be a custom error message + // from JDA when it fails to parse a snowflake, + // the user will read that + LoggerUtilities.error(e); + + return; + } + + if (guild == null) return; + + guild.retrieveMember(message.getAuthor()) + .queue( + member -> { + if (member == null) return; + + final TrustLevel trustLevel = TrustLevel.fromDiscordRoles(member.getRoles()); + + if (trustLevel == TrustLevel.PUBLIC) { + LoggerUtilities.log( + LogType.DISCORD, + Component.translatable( + "User %s tried to get hash in Discord without any trusted roles!", + Component.text(member.toString()) + ) + ); + message.reply("You do not have any trusted roles!") + .queue(); + return; + } + + sendHash(trustLevel, message, member); + }, + exception -> { + if (!(exception instanceof final ErrorResponseException error)) return; + + final ErrorResponse errorResponse = error.getErrorResponse(); + + if (errorResponse == ErrorResponse.UNKNOWN_MEMBER) { + message.reply("You are not in " + guild.getName() + "!") + .queue(); + } else if (errorResponse == ErrorResponse.UNKNOWN_USER) { + LoggerUtilities.error( + Component.translatable( + "Got ErrorResponse.UNKNOWN_USER while trying to " + + "retrieve member! Weird user. User: %s", + Component.text(message.getAuthor().toString()) + ) + ); + } + // that's all the possible errors, the javadocs said + } + ); + } + + private void sendHash (final TrustLevel trustLevel, final Message message, final Member member) { + final String result = HashingPlugin.generateDiscordHash(member.getIdLong(), trustLevel); + + message + .reply( + String.format( + "Hash for %s trust level: **%s**", + trustLevel, + result + ) + ) + .queue(); + + LoggerUtilities.log( + LogType.DISCORD, + Component.translatable( + "Generated hash %s (%s) for user %s", + Component.text(result), + Component.text(trustLevel.toString()), + Component.text(member.getEffectiveName()) + ) + ); + } +} diff --git a/src/main/java/me/chayapak1/chomens_bot/plugins/CommandHandlerPlugin.java b/src/main/java/me/chayapak1/chomens_bot/plugins/CommandHandlerPlugin.java index d15355b8..faaa351e 100644 --- a/src/main/java/me/chayapak1/chomens_bot/plugins/CommandHandlerPlugin.java +++ b/src/main/java/me/chayapak1/chomens_bot/plugins/CommandHandlerPlugin.java @@ -174,20 +174,7 @@ public class CommandHandlerPlugin implements TickPlugin.Listener { final List roles = member.getRoles(); - TrustLevel userTrustLevel = TrustLevel.PUBLIC; - - for (final Role role : roles) { - if (role.getName().equalsIgnoreCase(bot.config.discord.ownerRoleName)) { - userTrustLevel = TrustLevel.OWNER; - break; - } else if (role.getName().equalsIgnoreCase(bot.config.discord.adminRoleName)) { - userTrustLevel = TrustLevel.ADMIN; - break; - } else if (role.getName().equalsIgnoreCase(bot.config.discord.trustedRoleName)) { - userTrustLevel = TrustLevel.TRUSTED; - break; - } - } + final TrustLevel userTrustLevel = TrustLevel.fromDiscordRoles(roles); if (trustLevel.level > userTrustLevel.level) { context.sendOutput( diff --git a/src/main/java/me/chayapak1/chomens_bot/plugins/DiscordPlugin.java b/src/main/java/me/chayapak1/chomens_bot/plugins/DiscordPlugin.java index 6ef54db2..f4a1bce0 100644 --- a/src/main/java/me/chayapak1/chomens_bot/plugins/DiscordPlugin.java +++ b/src/main/java/me/chayapak1/chomens_bot/plugins/DiscordPlugin.java @@ -3,6 +3,7 @@ package me.chayapak1.chomens_bot.plugins; import me.chayapak1.chomens_bot.Bot; import me.chayapak1.chomens_bot.Configuration; import me.chayapak1.chomens_bot.Main; +import me.chayapak1.chomens_bot.discord.DirectMessageEventHandler; import me.chayapak1.chomens_bot.discord.GuildMessageEventHandler; import me.chayapak1.chomens_bot.util.CodeBlockUtilities; import me.chayapak1.chomens_bot.util.ComponentUtilities; @@ -29,6 +30,8 @@ import java.util.regex.Matcher; public class DiscordPlugin { public JDA jda; + public Configuration.Discord options; + public final Map servers; public final String prefix; @@ -38,7 +41,7 @@ public class DiscordPlugin { public final String discordUrl; public DiscordPlugin (final Configuration config) { - final Configuration.Discord options = config.discord; + this.options = config.discord; this.prefix = options.prefix; this.servers = options.servers; this.discordUrl = config.discord.inviteLink; @@ -53,7 +56,11 @@ public class DiscordPlugin { .clickEvent(ClickEvent.openUrl(discordUrl)); final JDABuilder builder = JDABuilder.createDefault(config.discord.token); - builder.enableIntents(GatewayIntent.MESSAGE_CONTENT); + builder.enableIntents( + GatewayIntent.MESSAGE_CONTENT, + GatewayIntent.DIRECT_MESSAGES, + GatewayIntent.GUILD_MEMBERS + ); builder.setEnableShutdownHook(false); try { @@ -66,6 +73,7 @@ public class DiscordPlugin { jda.getPresence().setPresence(Activity.playing(config.discord.statusMessage), false); new GuildMessageEventHandler(jda, prefix, messagePrefix); + new DirectMessageEventHandler(jda, options); Main.EXECUTOR.scheduleAtFixedRate(this::onDiscordTick, 0, 50, TimeUnit.MILLISECONDS); diff --git a/src/main/java/me/chayapak1/chomens_bot/plugins/HashingPlugin.java b/src/main/java/me/chayapak1/chomens_bot/plugins/HashingPlugin.java index 0dbdc8aa..cd1eb92c 100644 --- a/src/main/java/me/chayapak1/chomens_bot/plugins/HashingPlugin.java +++ b/src/main/java/me/chayapak1/chomens_bot/plugins/HashingPlugin.java @@ -4,11 +4,18 @@ import com.google.common.hash.Hashing; import me.chayapak1.chomens_bot.Bot; import me.chayapak1.chomens_bot.command.TrustLevel; import me.chayapak1.chomens_bot.data.player.PlayerEntry; +import me.chayapak1.chomens_bot.util.RandomStringUtilities; +import org.apache.commons.lang3.tuple.Pair; import java.nio.charset.StandardCharsets; +import java.util.ArrayList; import java.util.Arrays; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; public class HashingPlugin { + public static final Map> discordHashes = new ConcurrentHashMap<>(); + private final Bot bot; public HashingPlugin (final Bot bot) { @@ -64,10 +71,42 @@ public class HashingPlugin { checkHash(getOwnerHash(prefix, sender, false), input); } + public boolean isCorrectDiscordHash (final String input) { + for (final Pair pair : discordHashes.values()) { + if (checkHash(pair.getRight(), input)) return true; + } + + return false; + } + + public TrustLevel getDiscordHashTrustLevel (final String input) { + for (final Map.Entry> entry : new ArrayList<>(discordHashes.entrySet())) { + final Pair pair = entry.getValue(); + + if (!pair.getRight().equals(input)) continue; + + discordHashes.remove(entry.getKey()); + + return pair.getLeft(); + } + + return TrustLevel.PUBLIC; + } + public TrustLevel getTrustLevel (final String input, final String prefix, final PlayerEntry sender) { if (isCorrectOwnerHash(input, prefix, sender)) return TrustLevel.OWNER; else if (isCorrectAdminHash(input, prefix, sender)) return TrustLevel.ADMIN; else if (isCorrectHash(input, prefix, sender)) return TrustLevel.TRUSTED; + else if (isCorrectDiscordHash(input)) return getDiscordHashTrustLevel(input); else return TrustLevel.PUBLIC; } + + public static String generateDiscordHash (final long userId, final TrustLevel trustLevel) { + // i wouldn't say it's a hash, it's just a random string + final String string = RandomStringUtilities.generate(16); + + discordHashes.putIfAbsent(userId, Pair.of(trustLevel, string)); + + return discordHashes.get(userId).getRight(); + } } diff --git a/src/main/resources/default-config.yml b/src/main/resources/default-config.yml index a4d4be99..8327bbb4 100644 --- a/src/main/resources/default-config.yml +++ b/src/main/resources/default-config.yml @@ -41,6 +41,8 @@ chomeNSMod: discord: enabled: false prefix: 'default!' + serverId : 'id here' + enableDiscordHashing: true token: 'token here' trustedRoleName: 'Trusted' adminRoleName: 'Admin'