diff --git a/.idea/jsonSchemas.xml b/.idea/jsonSchemas.xml new file mode 100644 index 0000000..e873468 --- /dev/null +++ b/.idea/jsonSchemas.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/java/me/stuffy/stuffybot/Bot.java b/src/main/java/me/stuffy/stuffybot/Bot.java index fa6f087..366922c 100644 --- a/src/main/java/me/stuffy/stuffybot/Bot.java +++ b/src/main/java/me/stuffy/stuffybot/Bot.java @@ -1,9 +1,9 @@ package me.stuffy.stuffybot; -import me.stuffy.stuffybot.commands.*; import me.stuffy.stuffybot.events.ActiveEvents; import me.stuffy.stuffybot.events.UpdateBotStatsEvent; +import me.stuffy.stuffybot.interactions.InteractionHandler; import me.stuffy.stuffybot.utils.DiscordUtils; import me.stuffy.stuffybot.utils.Logger; import net.dv8tion.jda.api.JDA; @@ -14,6 +14,12 @@ import net.dv8tion.jda.api.events.guild.GuildJoinEvent; import net.dv8tion.jda.api.events.guild.GuildLeaveEvent; import net.dv8tion.jda.api.hooks.ListenerAdapter; +import net.dv8tion.jda.api.interactions.commands.OptionType; +import net.dv8tion.jda.api.interactions.commands.build.CommandData; +import net.dv8tion.jda.api.interactions.commands.build.Commands; +import net.dv8tion.jda.api.interactions.commands.build.OptionData; + +import java.util.ArrayList; public class Bot extends ListenerAdapter { private static Bot INSTANCE; @@ -33,29 +39,21 @@ public Bot() throws InterruptedException { // Initialize home guild this.homeGuild = jda.getGuildById("795108903733952562"); -// this.homeGuild = jda.getGuildById("818238263110008863"); assert this.homeGuild != null : "Failed to find home guild"; // Log startup String time = DiscordUtils.discordTimeNow(); String self = jda.getSelfUser().getAsMention(); - Logger logger = new Logger(); - logger.log(" Bot " + self + " started successfully " + time + "."); + Logger.log(" Bot " + self + " started successfully " + time + "."); - // Register commands + // Listen for interactions jda.addEventListener( - new VerifyCommand("verify", "Links your discord account to your Minecraft account"), - new MaxedGamesCommand("maxes", "Find the games with all achievements unlocked"), - new TournamentCommand("tournament", "Shows tournament stats"), - new AchievementCommand("achievements", "Shows achievements progress for a user"), - new StatsCommand("stats", "Shows overall hypixel stats for a user"), - new PitCommand("pit", "Shows pit stats for a user"), - new TkrCommand("tkr", "Shows completed maps in TKR"), - new MegaWallsCommand("megawalls", "Shows Mega Walls stats for a user") + new InteractionHandler() ); - + // Register commands "global"ly or "local"ly + registerCommands("local"); // Start events new UpdateBotStatsEvent().startFixedRateEvent(); @@ -94,4 +92,53 @@ public void onGuildLeave(GuildLeaveEvent event) { Guild leftGuild = event.getGuild(); Logger.log(" Bot left guild: " + leftGuild.getName() + " (" + leftGuild.getId() + ")"); } + + public void registerCommands(String scope) { + OptionData ignOption = new OptionData(OptionType.STRING, "ign", "The player's IGN", false); + // Create a list of commands first + ArrayList commandList = new ArrayList<>(); +// commandList.add(Commands.slash("help", "*Should* show a help message")); + commandList.add(Commands.slash("pit", "Get Pit stats for a player") + .addOptions(ignOption)); + commandList.add(Commands.slash("stats", "Get Hypixel stats for a player") + .addOptions(ignOption)); + commandList.add(Commands.slash("tkr", "Get TKR stats for a player") + .addOptions(ignOption)); + commandList.add(Commands.slash("maxes", "Get maxed games for a player") + .addOptions(ignOption)); + commandList.add(Commands.slash("blitz", "Get Blitz Ultimate Kit xp for a player") + .addOptions(ignOption)); + commandList.add(Commands.slash("megawalls", "Get Mega Walls skins for a player") + .addOptions(ignOption) + .addOptions(new OptionData(OptionType.STRING, "skins", "Which skins to look at", false).setAutoComplete(true))); + commandList.add(Commands.slash("tournament", "Get tournament stats for a player") + .addOptions(ignOption) + .addOptions(new OptionData(OptionType.INTEGER, "tournament", "Which tournament to look at (Leave empty for latest)", false).setAutoComplete(true))); + + + if (scope.equals("local")) { + //clearLocalCommands(); + this.homeGuild.updateCommands().addCommands( + commandList + ).queue(); + Logger.log(" Successfully Registered commands in guild."); + } else if (scope.equals("global")){ + jda.updateCommands().addCommands( + commandList + ).queue(); + Logger.log(" Successfully Registered commands globally."); + } else { + throw new IllegalArgumentException("Invalid scope: " + scope); + } + } + + public void clearCommands() { + jda.updateCommands().queue(); + Logger.log(" Successfully cleared commands."); + } + + public void clearLocalCommands() { + this.homeGuild.updateCommands().queue(); + Logger.log(" Successfully cleared local commands."); + } } diff --git a/src/main/java/me/stuffy/stuffybot/commands/AchievementCommand.java b/src/main/java/me/stuffy/stuffybot/commands/AchievementCommand.java deleted file mode 100644 index 3cc543a..0000000 --- a/src/main/java/me/stuffy/stuffybot/commands/AchievementCommand.java +++ /dev/null @@ -1,41 +0,0 @@ -package me.stuffy.stuffybot.commands; - -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; -import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent; -import net.dv8tion.jda.api.interactions.commands.OptionType; -import net.dv8tion.jda.api.interactions.commands.build.OptionData; - -import static me.stuffy.stuffybot.utils.DiscordUtils.getUsername; - -public class AchievementCommand extends BaseCommand{ - public AchievementCommand(String name, String description){ - super(name, description, - new OptionData(OptionType.STRING, "ign", "Minecraft Username", false), - new OptionData(OptionType.STRING, "game", "The game", false) - .addChoice("Skyblock", "skyblock"), - new OptionData(OptionType.STRING, "type", "Challenge/Tiered", false) - .addChoice("Challenge", "challenge") - .addChoice("Tiered", "tiered" - ) - ); - } - - - protected void onCommand(SlashCommandInteractionEvent event) { - String ign = getUsername(event); - if (event.getOption("game") == null) { - event.getHook().sendMessage("no game provided").queue(); - return; - } - String game = event.getOption("game").getAsString(); - event.getHook().sendMessage("game: " + game).queue(); - } - - public void onButton(ButtonInteractionEvent event) { - } - - @Override - protected void cleanupEventResources(String messageId) { - - } -} diff --git a/src/main/java/me/stuffy/stuffybot/commands/BaseCommand.java b/src/main/java/me/stuffy/stuffybot/commands/BaseCommand.java deleted file mode 100644 index 152bc10..0000000 --- a/src/main/java/me/stuffy/stuffybot/commands/BaseCommand.java +++ /dev/null @@ -1,72 +0,0 @@ -package me.stuffy.stuffybot.commands; - -import me.stuffy.stuffybot.Bot; -import me.stuffy.stuffybot.utils.Logger; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; -import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent; -import net.dv8tion.jda.api.hooks.ListenerAdapter; -import net.dv8tion.jda.api.interactions.commands.OptionMapping; -import net.dv8tion.jda.api.interactions.commands.build.OptionData; -import org.jetbrains.annotations.NotNull; - -import java.time.Instant; -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; - -public abstract class BaseCommand extends ListenerAdapter { - private String name; - private String description; - private final Map latestValidInteraction = new HashMap<>(); - private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); - - - public BaseCommand(String name, String description, OptionData... options) { - this.name = name; - this.description = description; - Bot bot = Bot.getInstance(); - bot.getHomeGuild().upsertCommand(name, description) - .addOptions(options) - .queue(); - Logger.log(" Registered command " + name + ": " + description); - } - - @Override - public void onSlashCommandInteraction(SlashCommandInteractionEvent event) { - if (event.getName().equals(this.name)) { - event.deferReply().queue(); - String options = ""; - for (OptionMapping option : event.getOptions()) { - options += option.getName() + ": " + option.getAsString() + ", "; - } - Logger.log(" @" + event.getUser().getName() + ": /" + this.name + " " + options); - this.onCommand(event); - latestValidInteraction.put(event.getHook().getId(), Instant.now()); - scheduler.scheduleAtFixedRate(this::endEvent, 0, 1, TimeUnit.SECONDS); - } - - // StatisticsManager.incrementTotalCommandsRun(); - } - - private void endEvent() { - latestValidInteraction.forEach((messageId, time) -> { - if (Instant.now().getEpochSecond() - time.getEpochSecond() > 30) { - latestValidInteraction.remove(messageId); - cleanupEventResources(messageId); - } - }); - } - - protected abstract void onCommand(SlashCommandInteractionEvent event); - - @Override - public void onButtonInteraction(@NotNull ButtonInteractionEvent event) { - this.onButton(event); - } - - protected abstract void onButton(ButtonInteractionEvent event); - - protected abstract void cleanupEventResources(String messageId); -} diff --git a/src/main/java/me/stuffy/stuffybot/commands/BlitzCommand.java b/src/main/java/me/stuffy/stuffybot/commands/BlitzCommand.java new file mode 100644 index 0000000..d6a184f --- /dev/null +++ b/src/main/java/me/stuffy/stuffybot/commands/BlitzCommand.java @@ -0,0 +1,47 @@ +package me.stuffy.stuffybot.commands; + +import me.stuffy.stuffybot.interactions.InteractionId; +import me.stuffy.stuffybot.profiles.HypixelProfile; +import me.stuffy.stuffybot.utils.APIException; +import net.dv8tion.jda.api.entities.MessageEmbed; +import net.dv8tion.jda.api.utils.messages.MessageCreateBuilder; +import net.dv8tion.jda.api.utils.messages.MessageCreateData; + +import java.text.DecimalFormat; +import java.util.Map; + +import static me.stuffy.stuffybot.utils.APIUtils.getHypixelProfile; +import static me.stuffy.stuffybot.utils.DiscordUtils.makeStatsEmbed; + +public class BlitzCommand { + public static MessageCreateData blitz(InteractionId interactionId) throws APIException { + String ign = interactionId.getOptions().get("ign"); + HypixelProfile hypixelProfile = getHypixelProfile(ign); + String username = hypixelProfile.getDisplayName(); + + Map blitzStats = hypixelProfile.getBlitzStats(); + + DecimalFormat df = new DecimalFormat("#,###"); + DecimalFormat df2 = new DecimalFormat("#,###.##"); + StringBuilder embedContent = new StringBuilder(); + + for (Map.Entry entry : blitzStats.entrySet()) { + String key = entry.getKey(); + Integer value = entry.getValue(); + if (value < 10000) { + embedContent.append(key).append(": **").append(df.format(value)).append("**/10,000 (").append(df2.format(value / 100)).append("%)\n"); + } else { + embedContent.append("~~").append(key).append(": **").append(df.format(value)).append("**/10,000~~\n"); + } + } + + MessageEmbed blitzEmbed = makeStatsEmbed( + "Blitz Ultimate Kit Xp for " + username, + embedContent.toString() + ); + + return new MessageCreateBuilder() + .addEmbeds(blitzEmbed) + .build(); + } +} diff --git a/src/main/java/me/stuffy/stuffybot/commands/MaxedGamesCommand.java b/src/main/java/me/stuffy/stuffybot/commands/MaxedGamesCommand.java deleted file mode 100644 index 7c1f4eb..0000000 --- a/src/main/java/me/stuffy/stuffybot/commands/MaxedGamesCommand.java +++ /dev/null @@ -1,45 +0,0 @@ -package me.stuffy.stuffybot.commands; - -import me.stuffy.stuffybot.profiles.HypixelProfile; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; -import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent; -import net.dv8tion.jda.api.interactions.commands.OptionType; -import net.dv8tion.jda.api.interactions.commands.build.OptionData; - -import static me.stuffy.stuffybot.utils.APIUtils.getHypixelProfile; -import static me.stuffy.stuffybot.utils.DiscordUtils.getUsername; -import static me.stuffy.stuffybot.utils.DiscordUtils.makeErrorEmbed; - -public class MaxedGamesCommand extends BaseCommand{ - - public MaxedGamesCommand(String name, String description) { - super(name, description, - new OptionData(OptionType.STRING, "ign", "Your Minecraft Username", false) - ); - } - @Override - protected void onCommand(SlashCommandInteractionEvent event) { - String ign = getUsername(event); - HypixelProfile hypixelProfile; - try { - hypixelProfile = getHypixelProfile(ign); - }catch (Exception e){ - event.getHook().sendMessage("").addEmbeds( - makeErrorEmbed( - "API Error", - "An error occurred while fetching your Hypixel profile. Please try again later." - ) - ).queue(); - return; - } - hypixelProfile.getMaxedGames(); - } - - public void onButton(ButtonInteractionEvent event) { - } - - @Override - protected void cleanupEventResources(String messageId) { - - } -} diff --git a/src/main/java/me/stuffy/stuffybot/commands/MaxesCommand.java b/src/main/java/me/stuffy/stuffybot/commands/MaxesCommand.java new file mode 100644 index 0000000..0eb645c --- /dev/null +++ b/src/main/java/me/stuffy/stuffybot/commands/MaxesCommand.java @@ -0,0 +1,45 @@ +package me.stuffy.stuffybot.commands; + +import me.stuffy.stuffybot.interactions.InteractionId; +import me.stuffy.stuffybot.profiles.HypixelProfile; +import me.stuffy.stuffybot.utils.APIException; +import net.dv8tion.jda.api.entities.MessageEmbed; +import net.dv8tion.jda.api.utils.messages.MessageCreateBuilder; +import net.dv8tion.jda.api.utils.messages.MessageCreateData; + +import java.util.ArrayList; + +import static me.stuffy.stuffybot.utils.APIUtils.getHypixelProfile; +import static me.stuffy.stuffybot.utils.DiscordUtils.makeStatsEmbed; +import static me.stuffy.stuffybot.utils.MiscUtils.toReadableName; + +public class MaxesCommand { + public static MessageCreateData maxes(InteractionId interactionId) throws APIException { + String ign = interactionId.getOptions().get("ign"); + HypixelProfile hypixelProfile = getHypixelProfile(ign); + String username = hypixelProfile.getDisplayName(); + + ArrayList maxesArray = hypixelProfile.getMaxGames(); + + StringBuilder embedContent = new StringBuilder(); + String subtitle = username + " has " + maxesArray.size() + " Maxed Games"; + for (String game : maxesArray) { + embedContent.append(toReadableName(game)).append("\n"); + } + + if (maxesArray.isEmpty()) { + subtitle = "No Maxed Games found. :("; + } + + MessageEmbed maxesEmbed = makeStatsEmbed( + "Maxed Games for " + username, + subtitle, + embedContent.toString() + ); + + return new MessageCreateBuilder() + .addEmbeds(maxesEmbed) + .build(); + + } +} diff --git a/src/main/java/me/stuffy/stuffybot/commands/MegaWallsCommand.java b/src/main/java/me/stuffy/stuffybot/commands/MegaWallsCommand.java index 8cae1d7..ec63d05 100644 --- a/src/main/java/me/stuffy/stuffybot/commands/MegaWallsCommand.java +++ b/src/main/java/me/stuffy/stuffybot/commands/MegaWallsCommand.java @@ -1,92 +1,137 @@ package me.stuffy.stuffybot.commands; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import me.stuffy.stuffybot.interactions.InteractionId; import me.stuffy.stuffybot.profiles.HypixelProfile; -import net.dv8tion.jda.api.events.Event; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; -import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent; -import net.dv8tion.jda.api.interactions.commands.OptionType; -import net.dv8tion.jda.api.interactions.commands.build.OptionData; -import net.dv8tion.jda.api.interactions.components.selections.SelectOption; -import net.dv8tion.jda.api.interactions.components.selections.StringSelectMenu; +import me.stuffy.stuffybot.utils.APIException; +import me.stuffy.stuffybot.utils.InvalidOptionException; +import net.dv8tion.jda.api.entities.MessageEmbed; +import net.dv8tion.jda.api.utils.messages.MessageCreateBuilder; +import net.dv8tion.jda.api.utils.messages.MessageCreateData; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; import java.text.DecimalFormat; -import java.util.HashMap; import java.util.Map; import static me.stuffy.stuffybot.utils.APIUtils.getHypixelProfile; -import static me.stuffy.stuffybot.utils.DiscordUtils.*; +import static me.stuffy.stuffybot.utils.DiscordUtils.makeStatsEmbed; -public class MegaWallsCommand extends BaseCommand{ - private final Map activeEvents = new HashMap<>(); - public MegaWallsCommand(String name, String description) { - super(name, description, - new OptionData(OptionType.STRING, "ign", "The username of the player you want to look up", false)); - } +public class MegaWallsCommand { - @Override - protected void onCommand(SlashCommandInteractionEvent event) { - activeEvents.put(event.getHook().getId(), event); - String ign = getUsername(event); - HypixelProfile hypixelProfile; - try { - hypixelProfile = getHypixelProfile(ign); - }catch (Exception e){ - event.getHook().sendMessage("").addEmbeds( - makeErrorEmbed( - "API Error", - "An error occurred while fetching your Hypixel profile. Please try again later." - ) - ).queue(); - return; + private static JsonObject getMwClasses() { + try (InputStream inputStream = MegaWallsCommand.class.getResourceAsStream("/data/mwclasses.json")) { + assert inputStream != null; + try (InputStreamReader reader = new InputStreamReader(inputStream, StandardCharsets.UTF_8)) { + return JsonParser.parseReader(reader).getAsJsonObject(); + } + } catch (Exception e) { + e.printStackTrace(); + return null; } + } + public static MessageCreateData megawalls(InteractionId interactionId) throws APIException, InvalidOptionException { + String ign = interactionId.getOption("ign"); + HypixelProfile hypixelProfile = getHypixelProfile(ign); String username = hypixelProfile.getDisplayName(); + + String mwClass = interactionId.getOption("skins", "all"); Map legendary_skins = hypixelProfile.getMegaWallsLegendaries(); int legendary_skins_unlocked = 0; - for (Boolean unlocked : legendary_skins.values()){ - if (unlocked){ - legendary_skins_unlocked++; - } - } - - Integer wins = hypixelProfile.getMegaWallsWins(); - Integer finalKills = hypixelProfile.getMegaWallsFinalKills(); - Integer classPoints = hypixelProfile.getMegaWallsClassPoints(); - String className = hypixelProfile.getMegaWallsSelectedClass(); + DecimalFormat dfPercent = new DecimalFormat("##.##"); + DecimalFormat dfComma = new DecimalFormat("#,###"); - DecimalFormat df = new DecimalFormat("#,###"); - event.getHook().sendMessage("").addEmbeds( - makeStatsEmbed( + MessageEmbed response = null; + switch (mwClass.toLowerCase()) { + case "legendary" -> { + StringBuilder legendarySkins = new StringBuilder(); + for (Map.Entry entry : legendary_skins.entrySet()) { + if (entry.getValue()) { + legendarySkins.append("✅ "); + legendary_skins_unlocked++; + } else { + legendarySkins.append("❌ "); + } + legendarySkins.append(entry.getKey()).append("\n"); + } + response = makeStatsEmbed( + "Legendary Skins for " + username, + "Unlocked: **" + legendary_skins_unlocked + "**/27 (" + dfPercent.format(legendary_skins_unlocked / 27.0 * 100) + "%)", + legendarySkins.toString() + ); + } + case "all" -> { + Integer wins = hypixelProfile.getMegaWallsWins(); + Integer finalKills = hypixelProfile.getMegaWallsFinalKills(); + Integer classPoints = hypixelProfile.getMegaWallsClassPoints(); + String selectedClass = hypixelProfile.getMegaWallsSelectedClass(); + for (Boolean unlocked : legendary_skins.values()) { + if (unlocked) { + legendary_skins_unlocked++; + } + } + response = makeStatsEmbed( "Mega Walls Stats for " + username, - "Wins: **" + df.format(wins) + "**\n" + - "Final Kills: **" + df.format(finalKills) + "**\n" + - "Total Class Points: **" + df.format(classPoints) + "**\n\n" + - "Selected Class: **" + className + "**\n" + + "Wins: **" + dfComma.format(wins) + "**\n" + + "Final Kills: **" + dfComma.format(finalKills) + "**\n" + + "Total Class Points: **" + dfComma.format(classPoints) + "**\n\n" + + "Selected Class: **" + selectedClass + "**\n" + "Legendary Skins Unlocked: **" + legendary_skins_unlocked + "**/27" + ); + } + default -> { + JsonObject mwClasses = getMwClasses(); + if (mwClasses == null) { + throw new APIException("Stuffy", "Failed to load Mega Walls classes data"); + } + for (JsonElement element : mwClasses.getAsJsonArray("classes")) { + JsonObject mwClassObject = element.getAsJsonObject(); + String id = mwClassObject.get("id").getAsString(); + if (id.equals(mwClass)) { + String className = mwClassObject.get("name").getAsString(); + JsonArray skins = mwClassObject.getAsJsonArray("skins"); + int skinCount = skins.size(); - ) - ).addActionRow( - StringSelectMenu.create("megawalls-select") - .addOption("Legendaries", "legendaries") - .addOptions( - SelectOption.of("Overall", "overall").withDefault(true) - ).build() - ).queue(); - - } - - @Override - protected void onButton(ButtonInteractionEvent event) { + String[] breakdown = new String[skinCount]; + DecimalFormat df = new DecimalFormat("#,###"); + for (int i = 0; i < skinCount; i++) { + StringBuilder skinString = new StringBuilder(); + JsonObject skin = skins.get(i).getAsJsonObject(); + String skinName = skin.get("name").getAsString(); + Integer requiredStats = skin.get("max").getAsInt(); + Integer playerProgress = 0; + for (JsonElement value : skin.get("values").getAsJsonArray()) { + playerProgress += hypixelProfile.getMegaWallsStat(value.getAsString()); + } - } + if (playerProgress >= requiredStats) { + skinString.append("~~").append(skinName).append(" **").append(df.format(playerProgress)).append("**/").append(df.format(requiredStats)).append("~~"); + } else { + skinString.append(skinName).append(" **").append(df.format(playerProgress)).append("**/").append(df.format(requiredStats)); + } + breakdown[i] = skinString.toString(); + } - @Override - protected void cleanupEventResources(String messageId) { - Event event = activeEvents.remove(messageId); - if (event instanceof SlashCommandInteractionEvent slashEvent) { - slashEvent.getHook().retrieveOriginal().queue(message -> { - message.editMessageComponents().queue(); - }); + response = makeStatsEmbed( + className + " Skins for " + username, + skinCount + " tracked skins", + String.join("\n", breakdown) + ); + break; + } + } + if (response == null) { + throw new InvalidOptionException("skins", mwClass); + } + } } + return new MessageCreateBuilder() + .addEmbeds(response) + .build(); } } diff --git a/src/main/java/me/stuffy/stuffybot/commands/PitCommand.java b/src/main/java/me/stuffy/stuffybot/commands/PitCommand.java index d71fd9b..e26ad92 100644 --- a/src/main/java/me/stuffy/stuffybot/commands/PitCommand.java +++ b/src/main/java/me/stuffy/stuffybot/commands/PitCommand.java @@ -1,54 +1,27 @@ package me.stuffy.stuffybot.commands; import kotlin.Triple; +import me.stuffy.stuffybot.interactions.InteractionId; import me.stuffy.stuffybot.profiles.HypixelProfile; -import net.dv8tion.jda.api.EmbedBuilder; +import me.stuffy.stuffybot.utils.APIException; import net.dv8tion.jda.api.entities.MessageEmbed; -import net.dv8tion.jda.api.events.Event; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; -import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent; -import net.dv8tion.jda.api.interactions.commands.OptionType; -import net.dv8tion.jda.api.interactions.commands.build.OptionData; -import net.dv8tion.jda.api.interactions.components.buttons.ButtonInteraction; +import net.dv8tion.jda.api.utils.messages.MessageCreateBuilder; +import net.dv8tion.jda.api.utils.messages.MessageCreateData; import java.text.DecimalFormat; -import java.util.HashMap; -import java.util.Map; import static me.stuffy.stuffybot.utils.APIUtils.getHypixelProfile; -import static me.stuffy.stuffybot.utils.DiscordUtils.*; +import static me.stuffy.stuffybot.utils.DiscordUtils.makeStatsEmbed; import static me.stuffy.stuffybot.utils.MiscUtils.convertToRomanNumeral; -import static net.dv8tion.jda.api.interactions.components.buttons.Button.*; +import static net.dv8tion.jda.api.interactions.components.buttons.Button.secondary; -public class PitCommand extends BaseCommand{ - private final Map originalEmbeds = new HashMap<>(); - private final Map originalDetailedEmbeds = new HashMap<>(); - private final Map activeEvents = new HashMap<>(); - - public PitCommand(String name, String description) { - super(name, description, - new OptionData(OptionType.STRING, "ign", "Your Minecraft Username", false) - ); - } - - @Override - protected void onCommand(SlashCommandInteractionEvent event) { - activeEvents.put(event.getHook().getId(), event); - String ign = getUsername(event); - HypixelProfile hypixelProfile; - try { - hypixelProfile = getHypixelProfile(ign); - }catch (Exception e){ - event.getHook().sendMessage("").addEmbeds( - makeErrorEmbed( - "API Error", - "An error occurred while fetching your Hypixel profile. Please try again later." - ) - ).queue(); - return; - } +public class PitCommand { + public static MessageCreateData pit(InteractionId interactionId) throws APIException { + String ign = interactionId.getOptions().get("ign"); + HypixelProfile hypixelProfile = getHypixelProfile(ign); String username = hypixelProfile.getDisplayName(); + String pitPrestige = convertToRomanNumeral(hypixelProfile.getPit("prestige")); Long totalPitXp = hypixelProfile.getPitXP(); @@ -72,18 +45,18 @@ protected void onCommand(SlashCommandInteractionEvent event) { embedContent ); - originalEmbeds.put(event.getHook().getId(), pitStats); - - - String runnerId = event.getUser().getId(); - event.getHook().sendMessage("") + String newInteractionId = InteractionId.newCommand("pitDetailed", interactionId).getInteractionString(); + return new MessageCreateBuilder() + .addEmbeds(pitStats) .addActionRow( - secondary("pitDetailed:" + runnerId, "Challenge Achievement Progress") + secondary(newInteractionId, "Challenge Achievement Progress") ) - .addEmbeds( - pitStats - ).queue(); + .build(); + } + public static MessageCreateData pitDetailed(InteractionId interactionId) throws APIException{ + String ign = interactionId.getOptions().get("ign"); + HypixelProfile hypixelProfile = getHypixelProfile(ign); Triple[] challengeAchievements = new Triple[]{ new Triple<>("Ingots Collector", hypixelProfile.getPit("ingots_collector"), 2000), @@ -96,59 +69,28 @@ protected void onCommand(SlashCommandInteractionEvent event) { new Triple<>("Raging Hunter", hypixelProfile.getPit("raging_hunter"), 100), new Triple<>("Bounty Hunter", hypixelProfile.getPit("bounty_hunter"), 30) }; - - StringBuilder embedContent2 = new StringBuilder(); + DecimalFormat df = new DecimalFormat("#,###"); + StringBuilder embedContent = new StringBuilder(); for (Triple challenge : challengeAchievements) { if (challenge.getSecond() >= challenge.getThird()) { - embedContent2.append("~~").append(challenge.getFirst()).append(": **").append(df.format(challenge.getSecond())).append("** / ").append(df.format(challenge.getThird())).append("~~\n"); + embedContent.append("~~").append(challenge.getFirst()).append(": **").append(df.format(challenge.getSecond())).append("** / ").append(df.format(challenge.getThird())).append("~~\n"); } else { String percentage = df.format((double) challenge.getSecond() / challenge.getThird() * 100); - embedContent2.append(challenge.getFirst()).append(": **").append(df.format(challenge.getSecond())).append("** / ").append(df.format(challenge.getThird())).append(" (").append(percentage).append("%)\n"); + embedContent.append(challenge.getFirst()).append(": **").append(df.format(challenge.getSecond())).append("** / ").append(df.format(challenge.getThird())).append(" (").append(percentage).append("%)\n"); } } MessageEmbed extraPitStats = makeStatsEmbed( - "Pit Achievement stats for " + username, - embedContent2.toString() + "Pit Achievement stats for " + hypixelProfile.getDisplayName(), + embedContent.toString() ); - originalDetailedEmbeds.put(event.getHook().getId(), extraPitStats); - } - - public void onButton(ButtonInteractionEvent event) { - String[] parts = event.getComponentId().split(":"); - String action = parts[0]; - String userId = parts[1]; - - if (action.equals("pitDetailed")) { - MessageEmbed detailedButton = originalDetailedEmbeds.get(event.getHook().getId()); - if(detailedButton == null) { - return; - } - event.editMessageEmbeds(detailedButton) - .setActionRow(secondary("go_back:" + userId, "Go Back")) - .queue(); - } - if (action.equals("go_back")) { - MessageEmbed backButton = originalEmbeds.get(event.getHook().getId()); - if(backButton == null) { - return; - } - event.editMessageEmbeds(backButton) - .setActionRow(secondary("pitDetailed:" + userId, "Challenge Achievement Progress")) - .queue(); - } - } - - @Override - protected void cleanupEventResources(String messageId) { - originalEmbeds.remove(messageId); - originalDetailedEmbeds.remove(messageId); - Event event = activeEvents.remove(messageId); - if (event instanceof SlashCommandInteractionEvent slashEvent) { - slashEvent.getHook().retrieveOriginal().queue(message -> { - message.editMessageComponents().queue(); - }); - } + String newInteractionId = InteractionId.newCommand("pit", interactionId).getInteractionString(); + return new MessageCreateBuilder() + .addEmbeds(extraPitStats) + .addActionRow( + secondary(newInteractionId, "Back to Pit Stats") + ) + .build(); } } diff --git a/src/main/java/me/stuffy/stuffybot/commands/StatsCommand.java b/src/main/java/me/stuffy/stuffybot/commands/StatsCommand.java index df99c51..c0c4e59 100644 --- a/src/main/java/me/stuffy/stuffybot/commands/StatsCommand.java +++ b/src/main/java/me/stuffy/stuffybot/commands/StatsCommand.java @@ -1,38 +1,21 @@ package me.stuffy.stuffybot.commands; +import me.stuffy.stuffybot.interactions.InteractionId; import me.stuffy.stuffybot.profiles.HypixelProfile; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; -import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent; -import net.dv8tion.jda.api.interactions.commands.OptionType; -import net.dv8tion.jda.api.interactions.commands.build.OptionData; +import me.stuffy.stuffybot.utils.APIException; +import net.dv8tion.jda.api.entities.MessageEmbed; +import net.dv8tion.jda.api.utils.messages.MessageCreateBuilder; +import net.dv8tion.jda.api.utils.messages.MessageCreateData; import java.text.DecimalFormat; import static me.stuffy.stuffybot.utils.APIUtils.getHypixelProfile; -import static me.stuffy.stuffybot.utils.DiscordUtils.*; - -public class StatsCommand extends BaseCommand{ - public StatsCommand(String name, String description){ - super(name, description, - new OptionData(OptionType.STRING, "ign", "The username of the player you want to look up", false)); - } - - @Override - protected void onCommand(SlashCommandInteractionEvent event) { - String ign = getUsername(event); - HypixelProfile hypixelProfile; - try{ - hypixelProfile = getHypixelProfile(ign); - } catch (Exception e){ - event.getHook().sendMessage("").addEmbeds( - makeErrorEmbed( - "API Error", - "An error occurred while fetching your Hypixel profile. Please try again later." - ) - ).queue(); - return; - } +import static me.stuffy.stuffybot.utils.DiscordUtils.makeStatsEmbed; +public class StatsCommand { + public static MessageCreateData stats(InteractionId interactionId) throws APIException { + String ign = interactionId.getOptions().get("ign"); + HypixelProfile hypixelProfile = getHypixelProfile(ign); String username = hypixelProfile.getDisplayName(); DecimalFormat df = new DecimalFormat("#,###"); @@ -55,29 +38,24 @@ protected void onCommand(SlashCommandInteractionEvent event) { String embedContent = onlineStatus + "\n" + - "Network Level: **" + df2.format(networkLevel) + "**\n" + - "Karma: **" + df.format(karma) + "**\n\n" + - "Achievement Points: **" + df.format(achievementPoints) + "** (+" + df.format(legacyAchievementPoints) + " legacy)" + "\n" + - "Quests Completed: **" + df.format(questsCompleted) + "**\n" + - "Challenges Completed: **" + df.format(challenges) + "**\n\n" + - "Reward Streak|Record: **" + df.format(rewardStreak) + "** | " + df.format(rewardRecord) + "\n" + - "Wins: **" + df.format(wins) + "**\n" + - "Kills: **" + df.format(kills) + "**\n" + - "First Login: " + firstLogin + "\n"; - - event.getHook().sendMessage("").addEmbeds( - makeStatsEmbed( - "Hypixel Stats for " + username, - embedContent - ) - ).queue(); - } - - public void onButton(ButtonInteractionEvent event) { - } - - @Override - protected void cleanupEventResources(String messageId) { + "Network Level: **" + df2.format(networkLevel) + "**\n" + + "Karma: **" + df.format(karma) + "**\n\n" + + "Achievement Points: **" + df.format(achievementPoints) + "** (+" + df.format(legacyAchievementPoints) + " legacy)" + "\n" + + "Quests Completed: **" + df.format(questsCompleted) + "**\n" + + "Challenges Completed: **" + df.format(challenges) + "**\n\n" + + "Reward Streak|Record: **" + df.format(rewardStreak) + "** | " + df.format(rewardRecord) + "\n" + + "Wins: **" + df.format(wins) + "**\n" + + "Kills: **" + df.format(kills) + "**\n" + + "First Login: " + firstLogin + "\n"; + + MessageEmbed stats = makeStatsEmbed( + "Stats for " + username, + embedContent + ); + + return new MessageCreateBuilder() + .addEmbeds(stats) + .build(); } } diff --git a/src/main/java/me/stuffy/stuffybot/commands/TkrCommand.java b/src/main/java/me/stuffy/stuffybot/commands/TkrCommand.java index c6157fd..9d2959b 100644 --- a/src/main/java/me/stuffy/stuffybot/commands/TkrCommand.java +++ b/src/main/java/me/stuffy/stuffybot/commands/TkrCommand.java @@ -1,43 +1,25 @@ package me.stuffy.stuffybot.commands; +import me.stuffy.stuffybot.interactions.InteractionId; import me.stuffy.stuffybot.profiles.HypixelProfile; -import net.dv8tion.jda.api.EmbedBuilder; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; -import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent; -import net.dv8tion.jda.api.interactions.commands.OptionType; -import net.dv8tion.jda.api.interactions.commands.build.OptionData; +import me.stuffy.stuffybot.utils.APIException; +import net.dv8tion.jda.api.entities.MessageEmbed; +import net.dv8tion.jda.api.utils.messages.MessageCreateBuilder; +import net.dv8tion.jda.api.utils.messages.MessageCreateData; import java.util.Map; import static me.stuffy.stuffybot.utils.APIUtils.getHypixelProfile; -import static me.stuffy.stuffybot.utils.DiscordUtils.*; - -public class TkrCommand extends BaseCommand{ - public TkrCommand(String name, String description) { - super(name, description, - new OptionData(OptionType.STRING, "ign", "The username of the player you want to look up", false)); - } - - @Override - protected void onCommand(SlashCommandInteractionEvent event) { - String ign = getUsername(event); - HypixelProfile hypixelProfile; - try{ - hypixelProfile = getHypixelProfile(ign); - } catch (Exception e){ - event.getHook().sendMessage("").addEmbeds( - makeErrorEmbed( - "API Error", - "An error occurred while fetching your Hypixel profile. Please try again later." - ) - ).queue(); - return; - } +import static me.stuffy.stuffybot.utils.DiscordUtils.makeStatsEmbed; +public class TkrCommand { + public static MessageCreateData tkr(InteractionId interactionId) throws APIException { + String ign = interactionId.getOptions().get("ign"); + HypixelProfile hypixelProfile = getHypixelProfile(ign); String username = hypixelProfile.getDisplayName(); Map uniqueGolds = hypixelProfile.getTkrMaps(); - String embedContent = ""; + StringBuilder embedContent = new StringBuilder(); int uniqueGoldCount = 0; for (Map.Entry entry : uniqueGolds.entrySet()) { @@ -46,28 +28,20 @@ protected void onCommand(SlashCommandInteractionEvent event) { if (value) { uniqueGoldCount++; } - embedContent += (value ? "✅ " : "❌ ") + key + "\n"; + embedContent.append(value ? "✅ " : "❌ ").append(key).append("\n"); } - - embedContent = "Unique Gold Medals: **" + uniqueGoldCount + "**/5\n\n" + embedContent; - - - event.getHook().sendMessage("").addEmbeds( - makeStatsEmbed( - "TKR Unique Gold Medals for " + username, - embedContent - ) - ).queue(); - } - - @Override - protected void onButton(ButtonInteractionEvent event) { - - } - - @Override - protected void cleanupEventResources(String messageId) { - + MessageEmbed tkrStats = makeStatsEmbed( + "TKR Stats for " + username, + "Unique Gold Medals: **" + uniqueGoldCount + "**/5", + embedContent.toString() + ); + + return new MessageCreateBuilder() + .addEmbeds(tkrStats) +// .addActionRow( +// secondary("achievements:" + interactionId.getUserId() + ":game=tkr," + interactionId.getOptionsString(), "Looking for TKR Achievements?") +// ) + .build(); } } diff --git a/src/main/java/me/stuffy/stuffybot/commands/TournamentCommand.java b/src/main/java/me/stuffy/stuffybot/commands/TournamentCommand.java index 8cf191e..8e715fc 100644 --- a/src/main/java/me/stuffy/stuffybot/commands/TournamentCommand.java +++ b/src/main/java/me/stuffy/stuffybot/commands/TournamentCommand.java @@ -1,30 +1,160 @@ package me.stuffy.stuffybot.commands; -import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; -import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent; -import net.dv8tion.jda.api.interactions.commands.OptionType; -import net.dv8tion.jda.api.interactions.commands.build.OptionData; - -public class TournamentCommand extends BaseCommand{ - public TournamentCommand(String name, String description){ - super(name, description, - new OptionData(OptionType.STRING, "tournament", "The tournament, defaults to most recent", false).addChoices( - - ), - new OptionData(OptionType.STRING, "username", "Player's username", false)); - } +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import me.stuffy.stuffybot.interactions.InteractionId; +import me.stuffy.stuffybot.profiles.HypixelProfile; +import me.stuffy.stuffybot.utils.APIException; +import me.stuffy.stuffybot.utils.InvalidOptionException; +import net.dv8tion.jda.api.interactions.components.buttons.Button; +import net.dv8tion.jda.api.interactions.components.buttons.ButtonStyle; +import net.dv8tion.jda.api.utils.messages.MessageCreateBuilder; +import net.dv8tion.jda.api.utils.messages.MessageCreateData; - @Override - protected void onCommand(SlashCommandInteractionEvent event) { - String tournament = event.getOption("tournament").getAsString(); - String username = event.getOption("username").getAsString(); - } +import java.util.ArrayList; +import java.util.Collection; - protected void onButton(ButtonInteractionEvent event) { - } +import static me.stuffy.stuffybot.utils.APIUtils.getHypixelProfile; +import static me.stuffy.stuffybot.utils.APIUtils.getTournamentData; +import static me.stuffy.stuffybot.utils.DiscordUtils.discordTimeUnix; +import static me.stuffy.stuffybot.utils.DiscordUtils.makeStatsEmbed; +import static me.stuffy.stuffybot.utils.MiscUtils.minutesFormatted; + +public class TournamentCommand { + + public static MessageCreateData tournament(InteractionId interactionId) throws APIException, InvalidOptionException { + String ign = interactionId.getOption("ign"); + HypixelProfile hypixelProfile = getHypixelProfile(ign); + String username = hypixelProfile.getDisplayName(); + + int tournamentId = interactionId.getOption("tournament", -1); + + JsonObject tournamentData = getTournamentData(); + if (tournamentData == null) { + throw new APIException("Stuffy", "Failed to load tournament data."); + } + + JsonObject selectedTournament = null; + int highestId = -1; + for (JsonElement entry : tournamentData.getAsJsonArray("tournaments")) { + JsonObject tournament = entry.getAsJsonObject(); + int id = tournament.get("id").getAsInt(); + if (id > highestId) { + highestId = id; + if (tournamentId == -1) { + selectedTournament = tournament; + } + } + if (id == tournamentId) { + selectedTournament = tournament; + } + } + if (tournamentId == -1) { + tournamentId = highestId; + } + + if (selectedTournament == null) { + throw new InvalidOptionException("tournament", "Invalid tournament ID."); + } + + String tournamentName = selectedTournament.get("name").getAsString(); + int tournamentIteration = selectedTournament.get("iteration").getAsInt(); + JsonObject tournamentDuration = selectedTournament.get("duration").getAsJsonObject(); + long tournamentStartTime = tournamentDuration.get("start").getAsLong(); + long tournamentEndTime = tournamentDuration.get("end").getAsLong(); + long currentTime = System.currentTimeMillis() / 1000; + + JsonObject selectedTournamentData = selectedTournament.get("data").getAsJsonObject(); + String tournamentField = selectedTournamentData.get("tourneyField").getAsString(); + + int tributes = hypixelProfile.getTourneyTributesEarned(tournamentField); + int playtime = hypixelProfile.getTourneyTimePlayed(tournamentField); + + StringBuilder title = new StringBuilder(); + title.append(tournamentName); + if (tournamentIteration != 0) { + title.append(" #").append(tournamentIteration + 1); + } + + String subtitle = " From " + discordTimeUnix(tournamentStartTime * 1000) + " to " + + discordTimeUnix(tournamentEndTime * 1000) + "\n-# ID:" + tournamentId; + + StringBuilder description = new StringBuilder(); + description.append("### **").append(username).append("**'s Tournament Stats\n"); + if(selectedTournamentData.has("gameLimit")){ + int gameLimit = selectedTournamentData.get("gameLimit").getAsInt(); + int gamesPlayed = hypixelProfile.getTourneyGamesPlayed(tournamentField); + description.append(gamesPlayed).append("/").append(gameLimit).append(" Games Played\n"); + } else { + int timeLimit = selectedTournamentData.get("timeLimit").getAsInt(); + description.append("Time Played: ").append(minutesFormatted(playtime)).append("/").append(minutesFormatted(timeLimit)).append("\n"); + } + description.append(tributes).append("/100 Tributes Earned\n"); + description.append("Playtime: ").append(minutesFormatted(playtime)).append("\n"); + description.append("\n"); + + if (selectedTournamentData.has("wins")) { + String winsField = selectedTournamentData.get("wins").getAsString(); + description.append("Wins: ").append(hypixelProfile.getStat(winsField)); + if (selectedTournamentData.has("losses")) { + String lossesField = selectedTournamentData.get("losses").getAsString(); + description.append(", Losses: ").append(hypixelProfile.getStat(lossesField)).append("\n"); + } else { + description.append("\n"); + } + if (selectedTournamentData.has("winstreak")) { + description.append("Win Streak: ").append(hypixelProfile.getStat(selectedTournamentData.get("winstreak").getAsString())).append("\n"); + } + } + + description.append("\n"); + + if (selectedTournamentData.has("kills")) { + description.append("Kills: ").append(hypixelProfile.getStat(selectedTournamentData.get("kills").getAsString())); + if (selectedTournamentData.has("killstreak")) { + description.append(", Kill Streak: ").append(hypixelProfile.getStat(selectedTournamentData.get("killstreak").getAsString())).append("\n"); + } else { + description.append("\n"); + } + } + + if (selectedTournamentData.has("assists")) { + description.append("Assists: ").append(hypixelProfile.getStat(selectedTournamentData.get("assists").getAsString())).append("\n"); + } + if (selectedTournamentData.has("deaths")) { + description.append("Deaths: ").append(hypixelProfile.getStat(selectedTournamentData.get("deaths").getAsString())).append("\n"); + } + + + String future = ":date:"; + String past = ":checkered_flag:"; + String active = ":video_game:"; + + String emoji; + + if (currentTime < tournamentStartTime) { + emoji = future; + } else if (currentTime > tournamentEndTime) { + emoji = past; + } else { + emoji = active; + } - @Override - protected void cleanupEventResources(String messageId) { + Collection