diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 0000000..f37a3ef
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1,15 @@
+# These are supported funding model platforms
+
+github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
+patreon: # Replace with a single Patreon username
+open_collective: # Replace with a single Open Collective username
+ko_fi: stuffydev # Replace with a single Ko-fi username
+tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
+community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
+liberapay: # Replace with a single Liberapay username
+issuehunt: # Replace with a single IssueHunt username
+lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
+polar: # Replace with a single Polar username
+buy_me_a_coffee: # Replace with a single Buy Me a Coffee username
+thanks_dev: # Replace with a single thanks.dev username
+custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
diff --git a/.gitignore b/.gitignore
index 524f096..6fb5922 100644
--- a/.gitignore
+++ b/.gitignore
@@ -22,3 +22,9 @@
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
replay_pid*
+
+# Configuration files
+*.properties
+
+# All target files
+/target/
\ No newline at end of file
diff --git a/Procfile b/Procfile
new file mode 100644
index 0000000..c03fdf3
--- /dev/null
+++ b/Procfile
@@ -0,0 +1 @@
+worker: java -jar ./target/stuffybot-java-1.0-SNAPSHOT.jar
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 6dfc478..46c65cc 100644
--- a/pom.xml
+++ b/pom.xml
@@ -15,6 +15,7 @@
+
org.apache.maven.plugins
maven-compiler-plugin
@@ -23,13 +24,56 @@
17
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+ 3.2.0
+
+
+
+ me.stuffy.stuffybot.Bot
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-shade-plugin
+ 3.2.4
+
+
+ package
+
+ shade
+
+
+
+
+ me.stuffy.stuffybot.Bot
+
+
+
+
+
+
+
+
+
+ com.fasterxml.jackson
+ jackson-bom
+ 2.15.4
+ pom
+ import
+
+
+
net.dv8tion
JDA
- 5.0.0
+ 6.2.0
com.google.code.gson
@@ -39,7 +83,17 @@
com.google.guava
guava
- 30.1-jre
+ 33.4.8-jre
+
+
+ org.kohsuke
+ github-api
+ 1.324
+
+
+ com.opencsv
+ opencsv
+ 5.5.2
\ 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 366922c..8fa59a0 100644
--- a/src/main/java/me/stuffy/stuffybot/Bot.java
+++ b/src/main/java/me/stuffy/stuffybot/Bot.java
@@ -3,8 +3,10 @@
import me.stuffy.stuffybot.events.ActiveEvents;
import me.stuffy.stuffybot.events.UpdateBotStatsEvent;
+import me.stuffy.stuffybot.interactions.AutoCompleteHandler;
import me.stuffy.stuffybot.interactions.InteractionHandler;
-import me.stuffy.stuffybot.utils.DiscordUtils;
+import me.stuffy.stuffybot.profiles.GlobalData;
+import me.stuffy.stuffybot.utils.Config;
import me.stuffy.stuffybot.utils.Logger;
import net.dv8tion.jda.api.JDA;
import net.dv8tion.jda.api.JDABuilder;
@@ -14,62 +16,125 @@
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.IntegrationType;
+import net.dv8tion.jda.api.interactions.InteractionContextType;
+import net.dv8tion.jda.api.interactions.commands.Command;
+import net.dv8tion.jda.api.interactions.commands.DefaultMemberPermissions;
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 net.dv8tion.jda.api.interactions.commands.build.*;
+import org.kohsuke.github.GitHub;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
+import static me.stuffy.stuffybot.utils.APIUtils.connectToGitHub;
+import static me.stuffy.stuffybot.utils.APIUtils.uploadLogs;
+
public class Bot extends ListenerAdapter {
private static Bot INSTANCE;
private final JDA jda;
- private Guild homeGuild;
+ private final Guild homeGuild;
+ private static GitHub GITHUB;
+ private static GlobalData GLOBAL_DATA;
public Bot() throws InterruptedException {
INSTANCE = this;
// Get token from env variable
String token = System.getenv("BOT_TOKEN");
- JDABuilder builder = JDABuilder.createDefault(token) ;
- builder.setActivity(Activity.customStatus("hating slash commands"));
+ JDABuilder builder = JDABuilder.createDefault(token);
+// builder.enableIntents(GatewayIntent.MESSAGE_CONTENT); // # TODO: Remove intents when possible
+ String customStatus = Config.getCustomStatus();
+ builder.setActivity(Activity.customStatus(customStatus));
builder.addEventListeners(this);
JDA jda = builder.build().awaitReady();
this.jda = jda;
+ String homeGuildID = Config.getHomeGuildId();
// Initialize home guild
- this.homeGuild = jda.getGuildById("795108903733952562");
+ this.homeGuild = jda.getGuildById(homeGuildID);
assert this.homeGuild != null : "Failed to find home guild";
-
// Log startup
- String time = DiscordUtils.discordTimeNow();
- String self = jda.getSelfUser().getAsMention();
- Logger.log(" Bot " + self + " started successfully " + time + ".");
+ String startupTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd_HH.mm.ss"));
+ String self = jda.getSelfUser().getName();
+ Logger.setLogName(startupTime);
+ String environment = Config.getEnvironment();
+ Logger.log(" Bot " + self + " started successfully " + startupTime + ". Environment: " + environment);
+
+ // Initialize GitHub
+ GITHUB = connectToGitHub();
+
+ // Initialize Global Data
+ GLOBAL_DATA = new GlobalData();
// Listen for interactions
jda.addEventListener(
- new InteractionHandler()
+ new InteractionHandler(),
+ new AutoCompleteHandler()
);
// Register commands "global"ly or "local"ly
- registerCommands("local");
+ String environmentScope = switch (Config.getEnvironment()) {
+ case "development" -> "local";
+ case "production", "development_global" -> "global";
+ default -> throw new IllegalArgumentException("Invalid environment: " + Config.getEnvironment());
+ };
+ registerCommands(environmentScope);
// Start events
new UpdateBotStatsEvent().startFixedRateEvent();
new ActiveEvents().startFixedRateEvent();
+
+ // Handle SIGTERM
+ Runtime.getRuntime().addShutdownHook(new Thread(() -> {
+ Logger.log(" Bot shutting down, saving data...");
+
+ // Close JDA
+ jda.shutdown();
+
+ // Update Bot Stats
+ try{
+ UpdateBotStatsEvent.publicExecute();
+ Logger.log(" Bot Stats saved, allowing for shutdown.");
+ } catch (Exception e) {
+ Logger.log(" Failed to save Bot Stats, allowing for shutdown.");
+ }
+ try {
+ uploadLogs();
+ Logger.log(" Logs uploaded, allowing for shutdown.");
+ }
+ catch (Exception e) {
+ Logger.log(" Failed to upload logs, allowing for shutdown.");
+ }
+ }));
}
public static Bot getInstance() {
return INSTANCE;
}
+ public static GitHub getGitHub() {
+ return GITHUB;
+ }
+
+ public static GlobalData getGlobalData() {
+ return GLOBAL_DATA;
+ }
+
public Guild getHomeGuild() {
return this.homeGuild;
}
public Role getVerifiedRole() {
- return this.homeGuild.getRoleById("795118862940635216");
+ String roleID = Config.getVerifiedRoleId();
+ return this.homeGuild.getRoleById(roleID);
+ }
+
+ public Role getNotVerifiedRole() {
+ String roleID = Config.getNotVerifiedRoleId();
+ return this.homeGuild.getRoleById(roleID);
}
public static void main(String[] args) throws InterruptedException {
@@ -93,31 +158,55 @@ public void onGuildLeave(GuildLeaveEvent event) {
Logger.log(" Bot left guild: " + leftGuild.getName() + " (" + leftGuild.getId() + ")");
}
- public void registerCommands(String scope) {
+ private SlashCommandData createSlashCommand(String name, String description) {
+ return Commands.slash(name, description).setContexts(InteractionContextType.ALL).setIntegrationTypes(IntegrationType.ALL);
+ }
+
+ private void registerCommands(String scope) {
OptionData ignOption = new OptionData(OptionType.STRING, "ign", "The player's IGN", false);
+ OptionData ignOptionRequired = new OptionData(OptionType.STRING, "ign", "The player's IGN", true);
// 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")
+ commandList.add(createSlashCommand("help", "Learn about the bot and its commands"));
+ commandList.add(createSlashCommand("pit", "Get Pit stats for a player")
.addOptions(ignOption));
- commandList.add(Commands.slash("stats", "Get Hypixel stats for a player")
+ commandList.add(createSlashCommand("stats", "Get Hypixel stats for a player")
.addOptions(ignOption));
- commandList.add(Commands.slash("tkr", "Get TKR stats for a player")
+ commandList.add(createSlashCommand("tkr", "Get TKR stats for a player")
.addOptions(ignOption));
- commandList.add(Commands.slash("maxes", "Get maxed games for a player")
+ commandList.add(createSlashCommand("maxes", "Get maxed games for a player")
.addOptions(ignOption));
- commandList.add(Commands.slash("blitz", "Get Blitz Ultimate Kit xp for a player")
+ commandList.add(createSlashCommand("blitz", "Get Blitz Ultimate Kit xp for a player")
.addOptions(ignOption));
- commandList.add(Commands.slash("megawalls", "Get Mega Walls skins for a player")
+ commandList.add(createSlashCommand("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")
+ commandList.add(createSlashCommand("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)));
+ commandList.add(createSlashCommand("achievements", "Get achievement stats for a player")
+ .addOptions(ignOption)
+ .addOptions(new OptionData(OptionType.STRING, "game", "Which game to look at", false).setAutoComplete(true))
+ .addOptions(new OptionData(OptionType.STRING, "type", "Which achievements to look at", false).addChoices(
+ new Command.Choice("All", "all"),
+ new Command.Choice("Challenge", "challenge"),
+ new Command.Choice("Tiered", "tiered")
+ )
+ ));
+ commandList.add(createSlashCommand("link", "Link a Minecraft account so you don't have to type your IGN every time")
+ .addOptions(ignOptionRequired));
+ commandList.add(createSlashCommand("playcommand", "Lookup the command to quickly hop into a game")
+ .addOptions(new OptionData(OptionType.STRING, "game", "Search for a play command", true).setAutoComplete(true)));
+ commandList.add(createSlashCommand("search", "Search for an achievement by name, or description.")
+ .addOptions(new OptionData(OptionType.STRING, "search", "Search for an Achievement", true).setAutoComplete(true)));
+ commandList.add(createSlashCommand("uuid", "Get UUID info for a Minecraft player")
+ .addOptions(ignOptionRequired));
+ commandList.add(createSlashCommand("warlords", "View Warlords Stats and Weapons Inventory")
+ .addOptions(ignOption));
if (scope.equals("local")) {
- //clearLocalCommands();
+ jda.updateCommands().queue();
this.homeGuild.updateCommands().addCommands(
commandList
).queue();
@@ -130,15 +219,14 @@ public void registerCommands(String scope) {
} 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.");
+ // Setup commands for home guild only
+ this.homeGuild.upsertCommand(
+ Commands.slash("setup", "Home guild setup command")
+ .setDefaultPermissions(DefaultMemberPermissions.DISABLED)
+ .addOptions(new OptionData(OptionType.STRING, "toSetup", "Which thing you wish to Setup", true).addChoices(
+ new Command.Choice("Verify", "verify")
+ ))
+ ).queue();
}
}
diff --git a/src/main/java/me/stuffy/stuffybot/commands/AchievementsCommand.java b/src/main/java/me/stuffy/stuffybot/commands/AchievementsCommand.java
new file mode 100644
index 0000000..92d7c6b
--- /dev/null
+++ b/src/main/java/me/stuffy/stuffybot/commands/AchievementsCommand.java
@@ -0,0 +1,204 @@
+package me.stuffy.stuffybot.commands;
+
+import com.google.gson.JsonArray;
+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.components.actionrow.ActionRow;
+import net.dv8tion.jda.api.components.buttons.Button;
+import net.dv8tion.jda.api.components.buttons.ButtonStyle;
+import net.dv8tion.jda.api.utils.messages.MessageCreateBuilder;
+import net.dv8tion.jda.api.utils.messages.MessageCreateData;
+
+import java.text.DecimalFormat;
+import java.util.*;
+
+import static me.stuffy.stuffybot.utils.APIUtils.getAchievementsResources;
+import static me.stuffy.stuffybot.utils.APIUtils.getHypixelProfile;
+import static me.stuffy.stuffybot.utils.DiscordUtils.makeStatsEmbed;
+import static me.stuffy.stuffybot.utils.MiscUtils.*;
+
+public class AchievementsCommand {
+ private static final Map gameDataCache = new HashMap<>();
+
+ public static MessageCreateData achievements(InteractionId interactionId) throws APIException, InvalidOptionException {
+ String ign = interactionId.getOption("ign");
+ HypixelProfile hypixelProfile = getHypixelProfile(ign);
+ String username = hypixelProfile.getDisplayName();
+
+ String readableName = interactionId.getOption("game", "none");
+ String viewType = interactionId.getOption("type", "all");
+
+ JsonObject gameAchievements = null;
+
+ if (readableName.equals("none")) {
+ StringBuilder content = new StringBuilder();
+ int unlockedAchievements = hypixelProfile.getAchievementsUnlocked();
+ int maxAchievements = getMaxAchievements();
+ int achievementPoints = hypixelProfile.getAchievementPoints();
+ int maxAchievementPoints = getMaxAchievementPoints();
+
+ int legacyUnlocked = hypixelProfile.getLegacyAchievementsUnlocked();
+ int legacyPoints = hypixelProfile.getLegacyAchievementPoints();
+
+ DecimalFormat thousands = new DecimalFormat("#,###");
+ DecimalFormat percentage = new DecimalFormat("#.##");
+
+ content.append("Unlocked: **").append(thousands.format(unlockedAchievements)).append("**/").append(thousands.format(maxAchievements)).append(" (").append(percentage.format((double) unlockedAchievements / maxAchievements * 100)).append("%)\n");
+ content.append("Points: **").append(thousands.format(achievementPoints)).append("**/").append(thousands.format(maxAchievementPoints)).append(" (").append(percentage.format((double) achievementPoints / maxAchievementPoints * 100)).append("%)\n\n");
+ content.append("Legacy Unlocked: **").append(thousands.format(legacyUnlocked)).append("**\n");
+ content.append("Legacy Points: **").append(thousands.format(legacyPoints)).append("**");
+
+// content.append("\n\nEasiest challenge: **").append(hypixelProfile.getEasiestChallenge()).append("**\n");
+// content.append("Closest tiered: **").append(hypixelProfile.getEasiestTiered()).append("**\n");
+
+ return new MessageCreateBuilder()
+ .addEmbeds(makeStatsEmbed("Achievement Data for " + username, content.toString()))
+ .build();
+ }
+
+ JsonObject achievementData = getAchievementsResources().getAsJsonObject();
+ String gameId = fromReadableName(readableName);
+
+ String _id = interactionId.getId();
+ if (gameDataCache.containsKey(_id)) {
+ gameAchievements = gameDataCache.get(_id);
+ } else {
+ for (String game : achievementData.keySet()) {
+ if (Objects.equals(game, gameId)) {
+ gameAchievements = achievementData.getAsJsonObject(game);
+ gameDataCache.put(_id, gameAchievements);
+ }
+ }
+ }
+
+ if (gameAchievements == null) {
+ throw new InvalidOptionException("game", readableName);
+ }
+
+ MessageCreateBuilder messageCreateBuilder = new MessageCreateBuilder();
+
+
+ Button allButton = Button.of(ButtonStyle.PRIMARY, interactionId.setOption("type", "all").getInteractionString(), "All");
+ Button challengeButton = Button.of(ButtonStyle.PRIMARY, interactionId.setOption("type", "challenge").getInteractionString(), "Challenge");
+ Button tieredButton = Button.of(ButtonStyle.PRIMARY, interactionId.setOption("type", "tiered").getInteractionString(), "Tiered");
+
+ String embedTitle = "";
+ String embedContent = "";
+
+ List challengeUnlocked = new ArrayList<>();
+ List challengeLocked = new ArrayList<>();
+ int challengeMaxUnlocked = 0;
+ int challengeMaxPoints = 0;
+ int challengeUnlockedPoints = 0;
+ JsonArray challengeAchievements = hypixelProfile.getAchievements().get("achievementsOneTime").getAsJsonArray();
+ List playerOneTimeString = new ArrayList<>();
+ for (JsonElement element : challengeAchievements) {
+ try {
+ playerOneTimeString.add(element.getAsString());
+ } catch (Exception ignored) {
+ }
+ }
+ for (String achievement : gameAchievements.get("one_time").getAsJsonObject().keySet()) {
+ String inData = gameId + "_" + achievement.toLowerCase();
+ JsonObject achievementObject = gameAchievements.get("one_time").getAsJsonObject().get(achievement).getAsJsonObject();
+ String readableAchievement = achievementObject.get("name").getAsString() + achievementObject.get("points").getAsString();
+ int points = achievementObject.get("points").getAsInt();
+ if(achievementObject.has("legacy")) {
+ if (achievementObject.get("legacy").getAsBoolean()) {
+ continue;
+ }
+ }
+ challengeMaxUnlocked++;
+ challengeMaxPoints += points;
+
+
+ if (playerOneTimeString.contains(inData)) {
+ challengeUnlockedPoints += points;
+ challengeUnlocked.add(readableAchievement);
+ } else {
+ challengeLocked.add(readableAchievement);
+ }
+ }
+
+ int tieredMaxUnlocked = 0;
+ int tieredMaxPoints = 0;
+ int tieredUnlockedPoints = 0;
+ int tieredTotalUnlocked = 0;
+
+
+ JsonObject tieredAchievements = hypixelProfile.getAchievements().get("achievementsTiered").getAsJsonObject();
+ for (String achievement : gameAchievements.get("tiered").getAsJsonObject().keySet()) {
+ JsonObject achievementObject = gameAchievements.get("tiered").getAsJsonObject().get(achievement).getAsJsonObject();
+ if(achievementObject.has("legacy")) {
+ if (achievementObject.get("legacy").getAsBoolean()) {
+ continue;
+ }
+ }
+ String readableAchievement = achievementObject.get("name").getAsString();
+ JsonArray tieredTiers = achievementObject.get("tiers").getAsJsonArray();
+ String inData = gameId + "_" + achievement.toLowerCase();
+
+ int tierCount = 0;
+ int tieredUnlocked = 0;
+ for (JsonElement tier : tieredTiers) {
+ JsonObject tierObject = tier.getAsJsonObject();
+ int points = tierObject.get("points").getAsInt();
+ int amount = tierObject.get("amount").getAsInt();
+ tieredMaxUnlocked++;
+ tieredMaxPoints += points;
+ tierCount++;
+
+ if (tieredAchievements.has(inData)) {
+ int playerProgress = tieredAchievements.get(inData).getAsInt();
+ if (playerProgress >= amount) {
+ tieredUnlocked++;
+ tieredUnlockedPoints += points;
+ }
+ }
+ }
+ tieredTotalUnlocked += tieredUnlocked;
+ }
+
+
+ if (Objects.equals(viewType, "challenge")) {
+ challengeButton = challengeButton.asDisabled();
+ embedTitle = readableName + " Challenge Achievements for " + username;
+
+ embedContent += "Unlocked: " + challengeUnlocked.size() + "/" + challengeMaxUnlocked + "\n";
+ embedContent += "Points: " + challengeUnlockedPoints + "/" + challengeMaxPoints + "\n\n";
+ }
+ if (Objects.equals(viewType, "tiered")) {
+ tieredButton = tieredButton.asDisabled();
+ embedTitle = readableName + " Tiered Achievements for " + username;
+
+ embedContent += "Unlocked: " + tieredTotalUnlocked + "/" + tieredMaxUnlocked + "\n";
+ embedContent += "Points: " + tieredUnlockedPoints + "/" + tieredMaxPoints + "\n\n";
+ }
+ if (Objects.equals(viewType, "all")) {
+ allButton = allButton.asDisabled();
+ embedTitle = readableName + " Achievement Summary for " + username;
+
+ int totalUnlocked = challengeUnlocked.size() + tieredTotalUnlocked;
+ int totalMaxUnlocked = challengeMaxUnlocked + tieredMaxUnlocked;
+ int totalPoints = challengeUnlockedPoints + tieredUnlockedPoints;
+ int totalMaxPoints = challengeMaxPoints + tieredMaxPoints;
+ embedContent += "Total Unlocked: " + totalUnlocked + "/" + totalMaxUnlocked + "\n";
+ embedContent += "Challenge Unlocked: " + challengeUnlocked.size() + "/" + challengeMaxUnlocked + "\n";
+ embedContent += "Tiered Unlocked: " + tieredTotalUnlocked + "/" + tieredMaxUnlocked + "\n\n";
+
+ embedContent += "Total Points: " + totalPoints + "/" + totalMaxPoints + "\n";
+ embedContent += "Challenge Points: " + challengeUnlockedPoints + "/" + challengeMaxPoints + "\n";
+ embedContent += "Tiered Points: " + tieredUnlockedPoints + "/" + tieredMaxPoints + "\n\n";
+ }
+
+ messageCreateBuilder.setComponents(ActionRow.of(allButton, challengeButton, tieredButton));
+
+ messageCreateBuilder.addEmbeds(makeStatsEmbed(embedTitle, embedContent));
+
+ return messageCreateBuilder.build();
+ }
+}
diff --git a/src/main/java/me/stuffy/stuffybot/commands/HelpCommand.java b/src/main/java/me/stuffy/stuffybot/commands/HelpCommand.java
new file mode 100644
index 0000000..176e43c
--- /dev/null
+++ b/src/main/java/me/stuffy/stuffybot/commands/HelpCommand.java
@@ -0,0 +1,44 @@
+package me.stuffy.stuffybot.commands;
+
+import net.dv8tion.jda.api.utils.messages.MessageCreateBuilder;
+import net.dv8tion.jda.api.utils.messages.MessageCreateData;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static me.stuffy.stuffybot.utils.DiscordUtils.makeEmbed;
+
+public class HelpCommand {
+ public static MessageCreateData help() {
+ Map commands = new HashMap<>();
+ commands.put("`/link`", "Set a Default account to check when running commands");
+ commands.put("`/stats`", "Get your Hypixel stats");
+ commands.put("`/maxes`", "Get your maxed games");
+ commands.put("`/playcommand`", "Lookup the command to quickly hop into a game");
+ commands.put("`/tournament`", "Get your tournament stats");
+
+ StringBuilder commandList = new StringBuilder();
+ for (Map.Entry entry : commands.entrySet()) {
+ commandList.append(entry.getKey()).append(": ").append(entry.getValue()).append("\n");
+ }
+
+ String helpText = "-# Bot Commands\n" + commandList;
+ helpText += """
+ ...and more!
+
+ -# Discord Server
+ Join the [discord server](https://discord.gg/X6WJT7WNVz) for help, feedback, and more!
+ We also announce Staff Rank changes, Hypixel leaks, and more!
+
+ -# Source Code
+ The source code for this bot, its ToS, and Privacy Policy are
+ available on [GitHub](https://github.com/stuffyerface/stuffybot-java).
+ Feel free to contribute, report bugs, or suggest new features!
+ Don't forget to follow, star, and check out our API!
+ """;
+
+ return new MessageCreateBuilder().addEmbeds(
+ makeEmbed("Stuffy Bot Help Menu", null, helpText, 0x570FF4, 30)
+ ).build();
+ }
+}
diff --git a/src/main/java/me/stuffy/stuffybot/commands/LinkCommand.java b/src/main/java/me/stuffy/stuffybot/commands/LinkCommand.java
new file mode 100644
index 0000000..f91b5f8
--- /dev/null
+++ b/src/main/java/me/stuffy/stuffybot/commands/LinkCommand.java
@@ -0,0 +1,71 @@
+package me.stuffy.stuffybot.commands;
+
+import me.stuffy.stuffybot.Bot;
+import me.stuffy.stuffybot.interactions.InteractionId;
+import me.stuffy.stuffybot.profiles.GlobalData;
+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.HashMap;
+import java.util.Map;
+import java.util.UUID;
+
+import static me.stuffy.stuffybot.utils.APIUtils.getHypixelProfile;
+import static me.stuffy.stuffybot.utils.APIUtils.updateLinkedDB;
+import static me.stuffy.stuffybot.utils.DiscordUtils.makeEmbed;
+import static me.stuffy.stuffybot.utils.DiscordUtils.makeErrorEmbed;
+
+public class LinkCommand {
+ static Map linkDelay = new HashMap<>();
+ static int linkDelayTime = 60000;
+ public static MessageCreateData link(InteractionId interactionId) throws APIException {
+ String ign = interactionId.getOptions().get("ign");
+ String discordId = interactionId.getUserId();
+
+ HypixelProfile hypixelProfile = getHypixelProfile(ign);
+
+ String mcUsername = hypixelProfile.getDisplayName();
+ UUID uuid = hypixelProfile.getUuid();
+
+ // #TODO: Check if the user is verified
+ long current = System.currentTimeMillis();
+ if (linkDelay.containsKey(discordId) && current - linkDelay.get(discordId) < linkDelayTime) {
+ return new MessageCreateBuilder()
+ .addEmbeds(makeErrorEmbed("Account linking failed", "Please wait a bit before trying to link your account again."))
+ .build();
+ }
+
+ GlobalData globalData = Bot.getGlobalData();
+ Map linkedAccounts = globalData.getLinkedAccounts();
+
+ if (linkedAccounts.containsKey(discordId) && linkedAccounts.get(discordId).equals(uuid)){
+ return new MessageCreateBuilder()
+ .addEmbeds(makeErrorEmbed("Account linking failed", "You are already linked to this account."))
+ .build();
+ }
+
+ linkDelay.put(discordId, current);
+
+ try {
+ updateLinkedDB(discordId, uuid, mcUsername);
+ } catch (Exception e) {
+ e.printStackTrace();
+ return new MessageCreateBuilder()
+ .addEmbeds(makeErrorEmbed("Account linking failed", "An error occurred while linking your account. Please try again later."))
+ .build();
+ }
+
+ MessageEmbed linkEmbed = makeEmbed(
+ "Account Linked",
+ "Successfully linked as **" + mcUsername + "**!",
+ "You can now run commands without the ign parameter.",
+ 0x6AC672
+ );
+
+ return new MessageCreateBuilder()
+ .addEmbeds(linkEmbed).build();
+ }
+}
diff --git a/src/main/java/me/stuffy/stuffybot/commands/PitCommand.java b/src/main/java/me/stuffy/stuffybot/commands/PitCommand.java
index e26ad92..4763075 100644
--- a/src/main/java/me/stuffy/stuffybot/commands/PitCommand.java
+++ b/src/main/java/me/stuffy/stuffybot/commands/PitCommand.java
@@ -4,6 +4,7 @@
import me.stuffy.stuffybot.interactions.InteractionId;
import me.stuffy.stuffybot.profiles.HypixelProfile;
import me.stuffy.stuffybot.utils.APIException;
+import net.dv8tion.jda.api.components.actionrow.ActionRow;
import net.dv8tion.jda.api.entities.MessageEmbed;
import net.dv8tion.jda.api.utils.messages.MessageCreateBuilder;
import net.dv8tion.jda.api.utils.messages.MessageCreateData;
@@ -13,7 +14,7 @@
import static me.stuffy.stuffybot.utils.APIUtils.getHypixelProfile;
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.secondary;
+import static net.dv8tion.jda.api.components.buttons.Button.secondary;
public class PitCommand {
@@ -48,9 +49,9 @@ public static MessageCreateData pit(InteractionId interactionId) throws APIExcep
String newInteractionId = InteractionId.newCommand("pitDetailed", interactionId).getInteractionString();
return new MessageCreateBuilder()
.addEmbeds(pitStats)
- .addActionRow(
+ .setComponents(ActionRow.of(
secondary(newInteractionId, "Challenge Achievement Progress")
- )
+ ))
.build();
}
@@ -88,9 +89,9 @@ public static MessageCreateData pitDetailed(InteractionId interactionId) throws
String newInteractionId = InteractionId.newCommand("pit", interactionId).getInteractionString();
return new MessageCreateBuilder()
.addEmbeds(extraPitStats)
- .addActionRow(
+ .setComponents(ActionRow.of(
secondary(newInteractionId, "Back to Pit Stats")
- )
+ ))
.build();
}
}
diff --git a/src/main/java/me/stuffy/stuffybot/commands/PlayCommandCommand.java b/src/main/java/me/stuffy/stuffybot/commands/PlayCommandCommand.java
new file mode 100644
index 0000000..60298ed
--- /dev/null
+++ b/src/main/java/me/stuffy/stuffybot/commands/PlayCommandCommand.java
@@ -0,0 +1,53 @@
+package me.stuffy.stuffybot.commands;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import me.stuffy.stuffybot.interactions.InteractionId;
+import net.dv8tion.jda.api.utils.messages.MessageCreateBuilder;
+import net.dv8tion.jda.api.utils.messages.MessageCreateData;
+
+import static me.stuffy.stuffybot.utils.APIUtils.getPlayCommands;
+import static me.stuffy.stuffybot.utils.DiscordUtils.makeErrorEmbed;
+import static me.stuffy.stuffybot.utils.DiscordUtils.makeStatsEmbed;
+
+public class PlayCommandCommand {
+ public static MessageCreateData playCommand(InteractionId interactionId) {
+ String input = interactionId.getOption("game");
+
+ JsonElement gameData = getPlayCommands().getAsJsonObject().get("gameData");
+ if (gameData == null) {
+ return new MessageCreateBuilder().setContent("Failed to load play command data.").build();
+ }
+
+ JsonArray games = gameData.getAsJsonArray();
+ for (JsonElement game : games) {
+ String gameName = game.getAsJsonObject().get("name").getAsString();
+ JsonArray modes = game.getAsJsonObject().get("modes").getAsJsonArray();
+ for (JsonElement mode : modes) {
+ if(!mode.getAsJsonObject().has("identifier") || !mode.getAsJsonObject().has("name")) {
+ continue;
+ }
+ String modeName = mode.getAsJsonObject().get("name").getAsString();
+ String identifier = mode.getAsJsonObject().get("identifier").getAsString();
+ String fullName;
+ if (gameName.equals(modeName)) {
+ fullName = gameName;
+ } else {
+ fullName = gameName + ": " + modeName;
+ }
+ if (input.equals(identifier)) {
+ return new MessageCreateBuilder().addEmbeds(
+ makeStatsEmbed("Play Command Search", "-# Use this to quickly join a game from anywhere.\n" +
+ "\n**" + fullName + "**\n `/play " + identifier + "`")
+ ).build();
+ }
+ }
+ }
+
+ return new MessageCreateBuilder().addEmbeds(
+ makeErrorEmbed("Invalid Game or Mode", "I don't have a play command for that game or mode." +
+ "\n-# Try using the search feature.")
+ ).build();
+ }
+}
diff --git a/src/main/java/me/stuffy/stuffybot/commands/SearchCommand.java b/src/main/java/me/stuffy/stuffybot/commands/SearchCommand.java
new file mode 100644
index 0000000..c745038
--- /dev/null
+++ b/src/main/java/me/stuffy/stuffybot/commands/SearchCommand.java
@@ -0,0 +1,117 @@
+package me.stuffy.stuffybot.commands;
+
+import com.google.gson.JsonArray;
+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.Logger;
+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.ArrayList;
+
+import static me.stuffy.stuffybot.utils.APIUtils.getAchievementsResources;
+import static me.stuffy.stuffybot.utils.APIUtils.getHypixelProfile;
+import static me.stuffy.stuffybot.utils.DiscordUtils.makeErrorEmbed;
+import static me.stuffy.stuffybot.utils.DiscordUtils.makeStatsEmbed;
+import static me.stuffy.stuffybot.utils.MiscUtils.toReadableName;
+
+public class SearchCommand {
+ public static MessageCreateData search(InteractionId interactionId) {
+ try{
+ String searchTerm = interactionId.getOptions().get("search");
+
+ String[] parts = searchTerm.split("_", 2);
+ String gameID = parts[0].toLowerCase();
+ String achievementID = parts[1];
+
+ JsonObject achievementResources = getAchievementsResources().getAsJsonObject();
+ JsonObject gameAchievements = achievementResources.get(gameID).getAsJsonObject();
+ JsonObject gameOneTimes = gameAchievements.get("one_time").getAsJsonObject();
+ JsonObject gameTiered = gameAchievements.get("tiered").getAsJsonObject();
+
+ String gameType = toReadableName(gameID);
+
+ StringBuilder achievementBody = new StringBuilder();
+
+ if (gameOneTimes.has(achievementID)) {
+ // CHALLENGE ACHIEVEMENTS
+ JsonObject searchedAchievement = gameOneTimes.get(achievementID).getAsJsonObject();
+ String achievementName = searchedAchievement.get("name").getAsString();
+ String achievementDescription = searchedAchievement.get("description").getAsString();
+ Integer achievementPoints = searchedAchievement.get("points").getAsInt();
+
+ achievementBody.append("-# ").append(gameType).append(" Challenge Achievement Found.\n");
+ achievementBody.append("**").append(achievementName).append("** (+**").append(achievementPoints).append("** points)\n");
+ achievementBody.append(achievementDescription).append("\n");
+
+ if (searchedAchievement.has("gamePercentUnlocked") && searchedAchievement.has("globalPercentUnlocked")){
+ DecimalFormat df = new DecimalFormat("#0.00");
+ Double gamePercentUnlocked = searchedAchievement.get("gamePercentUnlocked").getAsDouble();
+ Double globalPercentUnlocked = searchedAchievement.get("globalPercentUnlocked").getAsDouble();
+ achievementBody.append("\nUnlocked by `");
+ achievementBody.append(df.format(gamePercentUnlocked));
+ achievementBody.append("%` of ").append(gameType).append(", `");
+ achievementBody.append(df.format(globalPercentUnlocked));
+ achievementBody.append("%` of all players");
+ }
+
+ if(searchedAchievement.has("legacy") && searchedAchievement.get("legacy").getAsBoolean()){
+ achievementBody.append("\n-# Legacy Achievement");
+ }
+ } else if (gameTiered.has(achievementID)) {
+ // TIERED ACHIEVEMENTS
+ JsonObject searchedAchievement = gameTiered.get(achievementID).getAsJsonObject();
+ String achievementName = searchedAchievement.get("name").getAsString();
+ String achievementDescription = searchedAchievement.get("description").getAsString();
+
+ achievementBody.append("-# ").append(gameType).append(" Tiered Achievement Found.\n");
+ achievementBody.append("**").append(achievementName).append("**\n");
+
+ DecimalFormat df = new DecimalFormat("#,###");
+ JsonArray tiers = searchedAchievement.get("tiers").getAsJsonArray();
+ Integer maxReq = 0;
+ StringBuilder tiersBuilder = new StringBuilder();
+ tiersBuilder.append("```");
+ for (JsonElement tier : tiers){
+ int points = tier.getAsJsonObject().get("points").getAsInt();
+ int amount = tier.getAsJsonObject().get("amount").getAsInt();
+ tiersBuilder.append("\n ").append(df.format(amount)).append(" | ").append(points).append(" points");
+
+ if(amount > maxReq) {
+ maxReq = amount;
+ }
+ }
+ tiersBuilder.append("```");
+
+ achievementBody.append(achievementDescription.replace("%s", df.format(maxReq))).append("\n");
+ achievementBody.append(tiersBuilder);
+
+ if(searchedAchievement.has("legacy") && searchedAchievement.get("legacy").getAsBoolean()){
+ achievementBody.append("\n-# Legacy Achievement");
+ }
+ } else {
+ throw new Exception();
+ }
+
+ achievementBody.append("\n-# Internal ID: `").append(searchTerm).append("`.");
+
+ return new MessageCreateBuilder().addEmbeds(
+ makeStatsEmbed("Achievement Search Result", achievementBody.toString())
+ ).build();
+ } catch (Exception e) {
+ Logger.logError(e.getMessage());
+ MessageEmbed errorEmbed = makeErrorEmbed(
+ "Something went wrong",
+ "You should report this!"
+ );
+ return new MessageCreateBuilder()
+ .addEmbeds(errorEmbed)
+ .build();
+ }
+ }
+}
diff --git a/src/main/java/me/stuffy/stuffybot/commands/SetupCommand.java b/src/main/java/me/stuffy/stuffybot/commands/SetupCommand.java
new file mode 100644
index 0000000..28e75bb
--- /dev/null
+++ b/src/main/java/me/stuffy/stuffybot/commands/SetupCommand.java
@@ -0,0 +1,43 @@
+package me.stuffy.stuffybot.commands;
+
+import me.stuffy.stuffybot.utils.Config;
+import net.dv8tion.jda.api.EmbedBuilder;
+import net.dv8tion.jda.api.components.actionrow.ActionRow;
+import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
+import net.dv8tion.jda.api.components.buttons.Button;
+import net.dv8tion.jda.api.components.buttons.ButtonStyle;
+import net.dv8tion.jda.api.utils.messages.MessageCreateBuilder;
+import net.dv8tion.jda.api.utils.messages.MessageCreateData;
+
+public class SetupCommand {
+ public static void setupLinkingButton(SlashCommandInteractionEvent event) {
+ String linkCommandId = Config.getLinkCommandId();
+ EmbedBuilder embedBuilder = new EmbedBuilder();
+ embedBuilder.setTitle("Verify or Update");
+ embedBuilder.setDescription(
+ "Verification gives you permission to chat in this discord and have linked roles based on your Hypixel Stats. You must have a discord linked in game to do that. You can always use the button below to unverify your account, but you will lose all perks of being a verified user.\n" +
+ "-# :globe_with_meridians: You do __not__ need to verify your account to use commands inside or outside of this discord, receive announcements from Stuffy Bot, or any other feature we offer.\n" +
+ "\n" +
+ "If you just wish to link your account so slash commands will automatically assume your username for the `ign` field, you may use use " +
+ "" +
+ ", which will not require verifying in game.\n" +
+ "\n" +
+ "If you've earned new accomplishments and want to update them, click the update button below.\n" +
+ "-# :warning: Stuffy Bot and Staff of this Discord will __never__ ask for your passwords or other personal information, please protect yourself online."
+ );
+ embedBuilder.setFooter("Stuffy Bot by @stuffy");
+ embedBuilder.setColor(0x3d84a2);
+ embedBuilder.build();
+
+
+ MessageCreateData toBeSent = new MessageCreateBuilder().addEmbeds(
+ embedBuilder.build()
+ ).setComponents(ActionRow.of(
+ Button.of(ButtonStyle.SECONDARY, "000:verify:null", "Verify"),
+ Button.of(ButtonStyle.SECONDARY, "000:update:null", "Update"),
+ Button.of(ButtonStyle.DANGER, "000:unverify:null", "Unverify")
+ )).build();
+
+ event.getChannel().sendMessage(toBeSent).queue();
+ }
+}
diff --git a/src/main/java/me/stuffy/stuffybot/commands/TournamentCommand.java b/src/main/java/me/stuffy/stuffybot/commands/TournamentCommand.java
index 8e715fc..c66ee4a 100644
--- a/src/main/java/me/stuffy/stuffybot/commands/TournamentCommand.java
+++ b/src/main/java/me/stuffy/stuffybot/commands/TournamentCommand.java
@@ -6,8 +6,9 @@
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.components.actionrow.ActionRow;
+import net.dv8tion.jda.api.components.buttons.Button;
+import net.dv8tion.jda.api.components.buttons.ButtonStyle;
import net.dv8tion.jda.api.utils.messages.MessageCreateBuilder;
import net.dv8tion.jda.api.utils.messages.MessageCreateData;
@@ -154,7 +155,7 @@ public static MessageCreateData tournament(InteractionId interactionId) throws A
return new MessageCreateBuilder()
.addEmbeds(makeStatsEmbed(emoji + " " + title.toString(), subtitle, description.toString()))
- .addActionRow(buttons)
+ .setComponents(ActionRow.of(buttons))
.build();
}
}
diff --git a/src/main/java/me/stuffy/stuffybot/commands/UuidCommand.java b/src/main/java/me/stuffy/stuffybot/commands/UuidCommand.java
new file mode 100644
index 0000000..3279a2e
--- /dev/null
+++ b/src/main/java/me/stuffy/stuffybot/commands/UuidCommand.java
@@ -0,0 +1,68 @@
+package me.stuffy.stuffybot.commands;
+
+import me.stuffy.stuffybot.interactions.InteractionId;
+import me.stuffy.stuffybot.profiles.MojangProfile;
+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.UUID;
+
+import static me.stuffy.stuffybot.utils.APIUtils.getMojangProfile;
+import static me.stuffy.stuffybot.utils.DiscordUtils.makeStatsEmbed;
+
+public class UuidCommand {
+ public static MessageCreateData uuid(InteractionId interactionId) throws APIException {
+ String ign = interactionId.getOptions().get("ign");
+ MojangProfile mojangProfile = getMojangProfile(ign);
+ UUID uuid = mojangProfile.getUuid();
+ UUIDStats uuidStats = new UUIDStats(mojangProfile.getUsername(), uuid);
+
+
+ MessageEmbed uuidEmbed = makeStatsEmbed("UUID Data",
+ "`" + uuidStats.username + "`'s UUID is `" + uuidStats.uuid + "`\n" +
+ "That's better than `" + uuidStats.getFormattedBetterThanPercentage() + "`% of all UUIDs!" +
+ "\n-# Position `#" + String.format("%,d", uuidStats.estimatedRank(65340094)) + "`."
+ );
+
+ return new MessageCreateBuilder()
+ .addEmbeds(uuidEmbed)
+ .build();
+
+ }
+
+ private static class UUIDStats {
+ private final String username;
+ private final UUID uuid;
+ private final double betterThanPercentage;
+
+ public UUIDStats(String username, UUID uuid) {
+ this.username = username;
+ this.uuid = uuid;
+ this.betterThanPercentage = getBetterThanPercentage();
+ }
+
+ private double getBetterThanPercentage() {
+ String characterRanking = "0123456789abcdef";
+ String uuid = this.uuid.toString().replace("-", "");
+ double totalScore = 0;
+ double remainingScore = 100.0;
+ for (int i = 0; i < uuid.length(); i++) {
+ int totalChars = characterRanking.length();
+ int pos = characterRanking.indexOf(uuid.charAt(i));
+ totalScore += remainingScore * (pos / (double) totalChars);
+ remainingScore = remainingScore / 16;
+ }
+ return totalScore;
+ }
+
+ public int estimatedRank(int totalPlayers) {
+ return (int) (Math.ceil((1 - (this.betterThanPercentage / 100)) * totalPlayers));
+ }
+
+ public String getFormattedBetterThanPercentage() {
+ return String.format("%.2f", this.betterThanPercentage);
+ }
+ }
+}
diff --git a/src/main/java/me/stuffy/stuffybot/commands/WarlordsCommand.java b/src/main/java/me/stuffy/stuffybot/commands/WarlordsCommand.java
new file mode 100644
index 0000000..1cbc917
--- /dev/null
+++ b/src/main/java/me/stuffy/stuffybot/commands/WarlordsCommand.java
@@ -0,0 +1,467 @@
+package me.stuffy.stuffybot.commands;
+
+import com.google.gson.JsonArray;
+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.profiles.games.warlords.Weapon;
+import me.stuffy.stuffybot.utils.APIException;
+import me.stuffy.stuffybot.utils.DiscordUtils;
+import net.dv8tion.jda.api.components.actionrow.ActionRow;
+import net.dv8tion.jda.api.components.buttons.Button;
+import net.dv8tion.jda.api.components.buttons.ButtonStyle;
+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.Arrays;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Map;
+
+import static java.lang.Math.min;
+import static me.stuffy.stuffybot.profiles.games.warlords.WarlordsUtils.*;
+import static me.stuffy.stuffybot.utils.APIUtils.getHypixelProfile;
+import static me.stuffy.stuffybot.utils.DiscordUtils.discordTimeUnix;
+import static me.stuffy.stuffybot.utils.DiscordUtils.makeStatsEmbed;
+import static net.dv8tion.jda.api.components.buttons.Button.secondary;
+
+public class WarlordsCommand {
+
+ private static final DecimalFormat decimalFormat = new DecimalFormat("#.##");
+
+ public static MessageCreateData warlords(InteractionId interactionId) throws APIException {
+ String ign = interactionId.getOptions().get("ign");
+ HypixelProfile hypixelProfile = getHypixelProfile(ign);
+ String username = hypixelProfile.getDisplayName();
+
+ String embedContent =
+ "Selected:\n" + getSpecName(getSelectedSpec(hypixelProfile)) + "\n" +
+ getSelectedWeapon(hypixelProfile) + "\n\n" +
+ "**Wins: " + hypixelProfile.getStatFormatted("Battleground.wins") + "**\n" +
+ "CTF: " + hypixelProfile.getStatFormatted("Battleground.wins_capturetheflag") + "\n" +
+ "TDM: " + hypixelProfile.getStatFormatted("Battleground.wins_teamdeathmatch") + "\n" +
+ "Dom: " + hypixelProfile.getStatFormatted("Battleground.wins_domination") + "\n\n" +
+ "Kills: " + hypixelProfile.getStatFormatted("Battleground.kills") + "\n" +
+ "Assists: " + hypixelProfile.getStatFormatted("Battleground.assists");
+
+
+ MessageEmbed warlordsStats = makeStatsEmbed(
+ username + "'s Warlords Stats",
+ embedContent
+ );
+
+ String classesInteractionId = InteractionId.newCommand("warlordsClasses", interactionId).getInteractionString();
+ String weaponsInteractionId = InteractionId.newCommand("warlordsWeapons", interactionId).getInteractionString();
+
+ return new MessageCreateBuilder()
+ .addEmbeds(warlordsStats)
+ .setComponents(ActionRow.of(
+ secondary(classesInteractionId, "Classes"),
+ secondary(weaponsInteractionId, "Weapons Inventory")
+ ))
+ .build();
+ }
+
+ public static MessageCreateData warlordsClasses(InteractionId interactionId) throws APIException{
+ String ign = interactionId.getOptions().get("ign");
+ HypixelProfile hypixelProfile = getHypixelProfile(ign);
+ String username = hypixelProfile.getDisplayName();
+ int selectedClass = interactionId.getOption("mwClass", 0);
+ String classNameRaw = getClassRaw(selectedClass);
+ String selectedClassName = getClassName(classNameRaw);
+ StringBuilder stringBuilder = new StringBuilder();
+ String classHeader = selectedClassName + " Lvl " + getClassLevel(hypixelProfile, getClassRaw(selectedClass));
+ stringBuilder.append(classHeader).append("\n");
+ for (int i = 0; i <= 2; i++) {
+ String specName = getSpecName(classNameRaw, i);
+ String isPrestiged = isPrestiged(hypixelProfile, specName);
+ String specBoost = getSpecBoost(hypixelProfile, specName.toLowerCase());
+ stringBuilder.append("__").append(specName).append("__").append(isPrestiged).append(specBoost).append("\n")
+ .append(hypixelProfile.getStat("Battleground.wins_" + specName.toLowerCase())).append(" wins").append("\n");
+
+ String boundWeaponId = getBoundWeaponId(hypixelProfile, classNameRaw, specName.toLowerCase());
+ if (boundWeaponId != null) {
+ Weapon weapon = getWeaponById(hypixelProfile, boundWeaponId);
+ if (weapon != null) {
+ String weaponName = weapon.getName();
+ if (!weaponName.isEmpty()) {
+ stringBuilder.append(weaponName);
+ } else {
+ stringBuilder.append("Unknown Weapon");
+ }
+ }
+ } else {
+ stringBuilder.append("No Weapon Bound");
+ }
+ stringBuilder.append("\n\n");
+ }
+
+
+ MessageEmbed warlordsStats = makeStatsEmbed(
+ username + "'s Warlords Classes (" + (selectedClass + 1) + "/4)",
+ stringBuilder.toString()
+ );
+
+ String goBackInteractionId = InteractionId.newCommand("warlords", interactionId).getInteractionString();
+
+
+ Button prev = Button.of(ButtonStyle.SECONDARY, interactionId.setOption("mwClass", selectedClass-1).getInteractionString(), "◀");
+ Button next = Button.of(ButtonStyle.SECONDARY, interactionId.setOption("mwClass", selectedClass+1).getInteractionString(), "▶");
+ if (selectedClass <= 0) {
+ prev = prev.asDisabled();
+ }
+ if (selectedClass >= 3) {
+ next = next.asDisabled();
+ }
+ return new MessageCreateBuilder()
+ .addEmbeds(warlordsStats)
+ .setComponents(ActionRow.of(
+ secondary(goBackInteractionId, "General Stats"),
+ prev,
+ next
+ ))
+ .build();
+ }
+
+ public static MessageCreateData warlordsWeapons(InteractionId interactionId) throws APIException{
+ String ign = interactionId.getOptions().get("ign");
+ HypixelProfile hypixelProfile = getHypixelProfile(ign);
+ String username = hypixelProfile.getDisplayName();
+
+ int page = interactionId.getOption("page", 0);
+ Weapon[] weaponsInventory = getAllWeapons(hypixelProfile);
+ int weaponsPerPage = 3;
+ int startIndex = page * weaponsPerPage;
+ int endIndex = min(startIndex + weaponsPerPage, weaponsInventory.length);
+ int totalPages = (int) Math.ceil((double) weaponsInventory.length / weaponsPerPage);
+ StringBuilder stringBuilder = new StringBuilder();
+
+ Map boundWeapons = new HashMap<>();
+ for (JsonObject classObject : hypixelProfile.getStatObject("Battleground.bound_weapon")
+ .entrySet().stream().map(Map.Entry::getValue).map(JsonElement::getAsJsonObject).toList()) {
+ for (Map.Entry specEntry : classObject.entrySet()) {
+ String specName = specEntry.getKey();
+ String weaponId = specEntry.getValue().getAsString();
+ boundWeapons.put(weaponId, specName);
+ }
+ }
+
+ for (int i = startIndex; i < endIndex; i++) {
+ stringBuilder.append(formatWeapon(weaponsInventory[i], boundWeapons));
+ if (i < endIndex - 1) {
+ stringBuilder.append("\n");
+ }
+ }
+ MessageEmbed warlordsStats = makeStatsEmbed(
+ username + "'s Warlords Weapons(" + (page+1) + "/" + totalPages + ")",
+ stringBuilder.toString()
+ );
+
+ String goBackInteractionId = InteractionId.newCommand("warlords", interactionId).getInteractionString();
+ String prevPageId = interactionId.setOption("page", page - 1).getInteractionString();
+ String nextPageId = interactionId.setOption("page", page + 1).getInteractionString();
+ Button prev = Button.of(ButtonStyle.SECONDARY, prevPageId, "◀");
+ Button next = Button.of(ButtonStyle.SECONDARY, nextPageId, "▶");
+ if (page <= 0) {
+ prev = prev.asDisabled();
+ }
+ if (endIndex >= weaponsInventory.length) {
+ next = next.asDisabled();
+ }
+ return new MessageCreateBuilder()
+ .addEmbeds(warlordsStats)
+ .setComponents(ActionRow.of(
+ secondary(goBackInteractionId, "General Stats"),
+ prev,
+ next,
+ secondary(InteractionId.newCommand("warlordsWeaponSummary", interactionId).setOption("page", 0).getInteractionString(), "Summary"
+ )))
+ .build();
+ }
+
+ public static MessageCreateData warlordsWeaponSummary(InteractionId interactionId) throws APIException {
+ String ign = interactionId.getOptions().get("ign");
+ HypixelProfile hypixelProfile = getHypixelProfile(ign);
+ String username = hypixelProfile.getDisplayName();
+
+ int page = interactionId.getOption("page", 0);
+
+ Map boundWeapons = new HashMap<>();
+ for (JsonObject classObject : hypixelProfile.getStatObject("Battleground.bound_weapon")
+ .entrySet().stream().map(Map.Entry::getValue).map(JsonElement::getAsJsonObject).toList()) {
+ for (Map.Entry specEntry : classObject.entrySet()) {
+ String specName = specEntry.getKey();
+ String weaponId = specEntry.getValue().getAsString();
+ boundWeapons.put(weaponId, specName);
+ }
+ }
+
+ Weapon[] weapons = getAllWeapons(hypixelProfile);
+
+ int weaponsPerPage = 10;
+ int startIndex = page * weaponsPerPage;
+ int endIndex = min(startIndex + weaponsPerPage, weapons.length);
+ int count = endIndex - startIndex;
+ String[] weaponsList = new String[count];
+ for(int i = 0; i < count; i++) {
+ Weapon weapon = weapons[i+startIndex];
+ String weaponSummary = "";
+ weaponSummary += i+startIndex+1 + ". ";
+ weaponSummary += weapon.getRarity() + " ";
+ weaponSummary += "`" + decimalFormat.format(100*weapon.getWeaponScore()) + "%` ";
+ weaponSummary += weapon.getName();
+ if(weapon.getUpgradeLevel() >= 1) {
+ weaponSummary += " [" + weapon.getUpgradeLevel() + "/" + weapon.getMaxUpgradeLevel() + "]";
+ }
+ if(boundWeapons.containsKey(weapon.getWeaponId())) {
+ weaponSummary += " **B**";
+ }
+ weaponsList[i] = weaponSummary;
+ }
+ String embedContent = String.join("\n", weaponsList);
+ MessageEmbed warlordsStats = makeStatsEmbed(
+ username + "'s Warlords Weapon Summary",
+ embedContent
+ );
+
+ String goBackInteractionId = InteractionId.newCommand("warlordsWeapons", interactionId).setOption("page", 0).getInteractionString();
+ String prevPageId = interactionId.setOption("page", page - 1).getInteractionString();
+ String nextPageId = interactionId.setOption("page", page + 1).getInteractionString();
+ Button prev = Button.of(ButtonStyle.SECONDARY, prevPageId, "◀");
+ Button next = Button.of(ButtonStyle.SECONDARY, nextPageId, "▶");
+ if (page <= 0) {
+ prev = prev.asDisabled();
+ }
+ if (endIndex >= weapons.length) {
+ next = next.asDisabled();
+ }
+
+ return new MessageCreateBuilder()
+ .addEmbeds(warlordsStats)
+ .setComponents(ActionRow.of(
+ secondary(goBackInteractionId, "Back to Weapons"),
+ prev,
+ next
+ ))
+ .build();
+ }
+
+ private static Weapon[] getAllWeapons(HypixelProfile hypixelProfile) {
+ JsonArray weaponsObject = hypixelProfile.getStatArray("Battleground.weapon_inventory");
+ Weapon[] weaponsInventory = new Weapon[weaponsObject.size()];
+ for (int i = 0; i < weaponsObject.size(); i++) {
+ JsonObject weaponObject = weaponsObject.get(i).getAsJsonObject();
+ Weapon weapon = new Weapon(weaponObject);
+ weaponsInventory[i] = weapon;
+ }
+ return Arrays.stream(weaponsInventory)
+ .sorted(Comparator.comparingDouble(Weapon::getWeaponPoints).reversed())
+ .toArray(Weapon[]::new);
+ }
+
+ private static String formatWeapon(Weapon weapon, Map boundWeapons) {
+ String weaponName = weapon.getName();
+ String weaponId = weapon.getWeaponId();
+ double weaponScore = weapon.getWeaponScore();
+ int upgradeLevel = weapon.getUpgradeLevel();
+ int maxUpgradeLevel = weapon.getMaxUpgradeLevel();
+ StringBuilder weaponInfo = new StringBuilder();
+ weaponInfo.append("__").append(weaponName).append("__");
+ if(upgradeLevel >= 1) {
+ weaponInfo.append(" [").append(upgradeLevel).append("/").append(maxUpgradeLevel).append("]");
+ }
+
+ weaponInfo.append("\n").append("Obtained ").append(discordTimeUnix(Long.parseLong(weaponId))).append("\n")
+ .append("Weapon Score: `").append(decimalFormat.format(100*weaponScore)).append("%`\n");
+
+ if(boundWeapons.containsKey(weaponId)) {
+ weaponInfo.append("**BOUND**: ").append(getSpecName(boundWeapons.get(weaponId))).append("\n");
+ }
+ return weaponInfo.toString();
+ }
+
+ private static String getTimeStamp(JsonObject weaponObject) {
+ long timestamp = weaponObject.get("id").getAsLong();
+ return DiscordUtils.discordTimeUnix(timestamp);
+ }
+
+ private static Weapon getWeaponById(HypixelProfile hypixelProfile, String weaponId) {
+ Weapon[] allWeapons = getAllWeapons(hypixelProfile);
+ for (Weapon weapon : allWeapons) {
+ String currentWeaponId = weapon.getWeaponId();
+ if (currentWeaponId.equals(weaponId)) {
+ return weapon;
+ }
+ }
+ return null;
+ }
+
+ private static String getSelectedSpec(HypixelProfile hypixelProfile) {
+ String chosenClass = hypixelProfile.getStatString("Battleground.chosen_class");
+ String selectedSpec = hypixelProfile.getStatString("Battleground." + chosenClass + "_spec");
+ if (selectedSpec.isEmpty()) {
+ selectedSpec = "Unknown";
+ }
+ return selectedSpec;
+ }
+
+ private static int getClassLevel(HypixelProfile hypixelProfile, String classNameRaw) {
+ return hypixelProfile.getStat("Battleground." + classNameRaw + "_skill1")
+ + hypixelProfile.getStat("Battleground." + classNameRaw + "_skill2")
+ + hypixelProfile.getStat("Battleground." + classNameRaw + "_skill3")
+ + hypixelProfile.getStat("Battleground." + classNameRaw + "_skill4")
+ + hypixelProfile.getStat("Battleground." + classNameRaw + "_skill5")
+ + hypixelProfile.getStat("Battleground." + classNameRaw + "_health")
+ + hypixelProfile.getStat("Battleground." + classNameRaw + "_energy")
+ + hypixelProfile.getStat("Battleground." + classNameRaw + "_cooldown")
+ + hypixelProfile.getStat("Battleground." + classNameRaw + "_critchance")
+ + hypixelProfile.getStat("Battleground." + classNameRaw + "_critmultiplier");
+ }
+
+ private static String getSelectedWeapon(HypixelProfile hypixelProfile) {
+ Weapon[] allWeapons = getAllWeapons(hypixelProfile);
+ String selectedWeaponId = hypixelProfile.getStatString("Battleground.current_weapon");
+ for (Weapon weapon : allWeapons) {
+ String weaponId = weapon.getWeaponId();
+ if (weaponId.equals(selectedWeaponId)) {
+ return weapon.getName();
+ }
+ }
+ return "Unknown Weapon";
+ }
+
+ private static String getBoundWeaponId(HypixelProfile hypixelProfile, String className, String specName) {
+ String boundWeaponId = hypixelProfile.getStatString("Battleground.bound_weapon." + className + "." + specName);
+ if (boundWeaponId.isEmpty()) {
+ return null;
+ }
+ return boundWeaponId;
+ }
+
+ private static String isPrestiged(HypixelProfile hypixelProfile, String specName) {
+ JsonArray prestigedClasses = hypixelProfile.getStatArray("Battleground.prestiged");
+ if (prestigedClasses != null) {
+ for (int i = 0; i < prestigedClasses.size(); i++) {
+ if (prestigedClasses.get(i).getAsString().equalsIgnoreCase(specName)) {
+ return " ✪";
+ }
+ }
+ }
+ return "";
+ }
+
+ private static String getSpecBoost(HypixelProfile hypixelProfile, String specName) {
+ JsonObject specBoosts = hypixelProfile.getStatObject("Battleground.active_boost");
+ if (specBoosts != null && specBoosts.has(specName)) {
+ String specBoost = specBoosts.get(specName).getAsString();
+ String specBoostName = boostRawToName(specBoost);
+ if(specBoostName != null) {
+ String specBoostLevel = getSpecBoostLevel(hypixelProfile, specBoost);
+ return " [" + specBoostName + " " + specBoostLevel + "]";
+ }
+ }
+ return "";
+ }
+
+ private static String boostRawToName(String boostRaw) {
+ return switch (boostRaw) {
+ // Pyro
+ case "meteor" -> "Meteor";
+ case "arcane_shatter" -> "Arcane Shatter";
+ case "dimensional_warp" -> "Dimensional Warp";
+ case "burst_chain" -> "Burst Chain";
+ case "flame_breath" -> "Flame Breath";
+ // Cryo
+ case "frost_missile" -> "Frost Missile";
+ case "arcane_recluse" -> "Arcane Recluse";
+ case "chilly_aura" -> "Chilly Aura";
+ case "blizzard_breath" -> "Blizzard Breath";
+ case "steadfast_warp" -> "Steadfast Warp";
+ // Aqua
+ case "typhoon_bolt" -> "Typhoon Bolt";
+ case "arcane_reflection" -> "Arcane Reflection";
+ case "acid_rain" -> "Acid Rain";
+ case "clairvoyance" -> "Clairvoyance";
+ case "divine_purification" -> "Divine Purification";
+ //Bers
+ case "wounding_strike_berserker" -> "Wounding Strike";
+ case "blood_frenzy" -> "Blood Frenzy";
+ case "mighty_fists" -> "Mighty Fists";
+ case "berserkers_fury" -> "Berserker's Fury";
+ case "seismic_shift" -> "Seismic Shift";
+ // Def
+ case "wounding_strike_defender" -> "Wounding Strike";
+ case "heroic_intervention" -> "Heroic Intervention";
+ case "solitary_resistance" -> "Solitary Resistance";
+ case "fervent_force" -> "Fervent Force";
+ case "vitality_boost" -> "Vitality Boost";
+ // Rev
+ case "orbs_of_life" -> "Orbs of Life";
+ case "one_man_army" -> "One Man Army";
+ case "reckless_ascent" -> "Reckless Ascent";
+ case "undying_steed" -> "Undying Steed";
+ case "healing_link" -> "Healing Link";
+ // Ave
+ case "divine_vindication" -> "Divine Vindication";
+ case "warding_wrath" -> "Warding Wrath";
+ case "greater_sacrality" -> "Greater Sacrality";
+ case "arm_of_the_almighty" -> "Arm of the Almighty";
+ case "zealous_mark" -> "Zealous Mark";
+ // Crus
+ case "blade_of_willpower" -> "Blade of Willpower";
+ case "seraphim_shield" -> "Seraphim Shield";
+ case "rallying_presence" -> "Rallying Presence";
+ case "sovereign_solitude" -> "Sovereign Solitude";
+ case "vigorous_infusion" -> "Vigorous Infusion";
+ // Prot
+ case "piercing_radiance" -> "Piercing Radiance";
+ case "lustrous_crown" -> "Lustrous Crown";
+ case "divine_effulgence" -> "Divine Effulgence";
+ case "lightspeed_infusion" -> "Lightspeed Infusion";
+ case "hammer_of_judgement" -> "Hammer of Judgement";
+ // Tlord
+ case "transistor" -> "Transistor";
+ case "galvanized_spark" -> "Galvanized Spark";
+ case "electromagnetic_chains" -> "Electromagnetic Chains";
+ case "eye_of_the_storm" -> "Eye of the Storm";
+ case "symphonic_windfury" -> "Symphonic Windfury";
+ // Spirit
+ case "wrath_of_the_fallen" -> "Wrath of the Fallen";
+ case "devils_debt" -> "Devil's Debt";
+ case "spiritual_deflection" -> "Spiritual Deflection";
+ case "permeating_link" -> "Permeating Link";
+ case "smotherin_soulbind" -> "Smothering Soulbind";
+ // Earth
+ case "megalithic_boulder" -> "Megalithic Boulder";
+ case "augmented_chains" -> "Augmented Chains";
+ case "earthbound_infusion" -> "Earthbound Infusion";
+ case "totemic_boon" -> "Totemic Boon";
+ case "accelerated_spike" -> "Accelerated Spike";
+ default -> null;
+ };
+ }
+
+ private static String getSpecBoostLevel(HypixelProfile hypixelProfile, String specBoostName) {
+ int boostLevel = hypixelProfile.getStat("Battleground." + specBoostName);
+ switch (boostLevel) {
+ case 1 -> {
+ return "II";
+ }
+ case 2 -> {
+ return "III";
+ }
+ case 3 -> {
+ return "IV";
+ }
+ case 4 -> {
+ return "V";
+ }
+ default -> {
+ return "I";
+ }
+ }
+ }
+}
diff --git a/src/main/java/me/stuffy/stuffybot/events/BaseEvent.java b/src/main/java/me/stuffy/stuffybot/events/BaseEvent.java
index 5eeaeee..f5632a1 100644
--- a/src/main/java/me/stuffy/stuffybot/events/BaseEvent.java
+++ b/src/main/java/me/stuffy/stuffybot/events/BaseEvent.java
@@ -21,7 +21,7 @@ public BaseEvent(String name, long interval, TimeUnit timeUnit) {
}
public void startFixedRateEvent() {
- scheduler.scheduleAtFixedRate(this::execute, 0, interval, timeUnit);
+ scheduler.scheduleAtFixedRate(this::execute, interval, interval, timeUnit);
}
protected abstract void execute();
diff --git a/src/main/java/me/stuffy/stuffybot/events/UpdateBotStatsEvent.java b/src/main/java/me/stuffy/stuffybot/events/UpdateBotStatsEvent.java
index 2c6ec7c..00e5076 100644
--- a/src/main/java/me/stuffy/stuffybot/events/UpdateBotStatsEvent.java
+++ b/src/main/java/me/stuffy/stuffybot/events/UpdateBotStatsEvent.java
@@ -1,23 +1,57 @@
package me.stuffy.stuffybot.events;
import me.stuffy.stuffybot.Bot;
+import me.stuffy.stuffybot.profiles.GlobalData;
import me.stuffy.stuffybot.utils.Logger;
+import net.dv8tion.jda.api.entities.Guild;
+import java.util.Map;
import java.util.concurrent.TimeUnit;
+import static me.stuffy.stuffybot.utils.APIUtils.updateBotStats;
+import static me.stuffy.stuffybot.utils.APIUtils.updateUsersStats;
+
public class UpdateBotStatsEvent extends BaseEvent{
public UpdateBotStatsEvent() {
- super("UpdateBotStats", 1, TimeUnit.HOURS);
+ super("UpdateBotStats", 2, TimeUnit.HOURS);
}
@Override
protected void execute() {
- // How many servers the bot is in
- // How many commands have been run
+ publicExecute();
+ }
+
+ public static void publicExecute() {
+ Logger.log(" Updating bot stats.");
Bot bot = Bot.getInstance();
- int totalServers = bot.getJDA().getGuilds().size();
- Logger.log(" Total servers: " + totalServers);
+ GlobalData globalData = Bot.getGlobalData();
+
+ Guild[] guilds = bot.getJDA().getGuilds().toArray(new Guild[0]);
+ int totalUsers = 0;
+ int totalServers = 0;
+ for (Guild guild : guilds) {
+ totalServers++;
+ totalUsers += guild.getMemberCount();
+ }
+
+ Map uniqueUsers = globalData.getSessionUniqueUsers();
+ Map commandsRun = globalData.getSessionCommandsRun();
+ Map userCommandsRun = globalData.getSessionUserCommandsRun();
+
+ if(commandsRun.isEmpty()) {
+ Logger.log(" No data to update.");
+ } else {
+ updateBotStats(totalServers, totalUsers, commandsRun);
+ Logger.log(" Updated bot stats.");
+ }
+ if(uniqueUsers.isEmpty()) {
+ Logger.log(" No unique users to update.");
+ } else {
+ updateUsersStats(uniqueUsers, userCommandsRun);
+ Logger.log(" Updated user stats.");
+ }
-// int totalCommandsRun = bot.getStatisticsManager().getTotalCommandsRun();
+ globalData.clearCommandsRun();
+ globalData.clearUniqueUsers();
}
}
diff --git a/src/main/java/me/stuffy/stuffybot/interactions/AutoCompleteHandler.java b/src/main/java/me/stuffy/stuffybot/interactions/AutoCompleteHandler.java
new file mode 100644
index 0000000..ec5e178
--- /dev/null
+++ b/src/main/java/me/stuffy/stuffybot/interactions/AutoCompleteHandler.java
@@ -0,0 +1,210 @@
+package me.stuffy.stuffybot.interactions;
+
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import net.dv8tion.jda.api.events.interaction.command.CommandAutoCompleteInteractionEvent;
+import net.dv8tion.jda.api.hooks.ListenerAdapter;
+import net.dv8tion.jda.api.interactions.commands.Command;
+
+import java.util.*;
+import java.util.stream.Stream;
+
+import static me.stuffy.stuffybot.utils.APIUtils.*;
+import static me.stuffy.stuffybot.utils.MiscUtils.autoCompleteAchGames;
+import static me.stuffy.stuffybot.utils.MiscUtils.toReadableName;
+
+public class AutoCompleteHandler extends ListenerAdapter {
+ private final Map tournamentMap = getTournamentMap();
+ private final List skinOptions = getSkinOptions();
+
+ @Override
+ public void onCommandAutoCompleteInteraction(CommandAutoCompleteInteractionEvent e) {
+ String commandName = e.getName();
+ String subcommandName = e.getSubcommandName();
+
+ String commandOption = e.getFocusedOption().getName();
+ String currentInput = e.getFocusedOption().getValue();
+ switch (commandName) {
+ case "megawalls" -> {
+ if (commandOption.equals("skins")) {
+ List options = new ArrayList<>();
+ for (String skin : skinOptions) {
+ if (skin.toLowerCase().contains(currentInput.toLowerCase())) {
+ options.add(skin);
+ }
+ }
+
+ String[] optionsArray = options.toArray(new String[0]);
+ if (optionsArray.length > 25) {
+ optionsArray = Arrays.copyOfRange(optionsArray, 0, 24);
+ }
+
+ List choices = Stream.of(optionsArray)
+ .map(option -> new Command.Choice(option, option.toLowerCase()))
+ .toList();
+
+ e.replyChoices(choices).queue();
+ }
+ }
+ case "tournament" -> {
+ if (commandOption.equals("tournament")) {
+ List choices = new ArrayList<>();
+ tournamentMap.forEach((name, id) -> {
+ if (name.toLowerCase().contains(currentInput.toLowerCase()) && choices.size() <= 25) {
+ choices.add(new Command.Choice(name, id));
+ }
+ });
+ e.replyChoices(choices).queue();
+ }
+ }
+ case "playcommand" -> {
+ if (commandOption.equals("game")) {
+ JsonElement gameData = getPlayCommands().getAsJsonObject().get("gameData");
+ if (gameData == null) {
+ e.replyChoices(Collections.emptyList()).queue();
+ break;
+ }
+
+ List choices = new ArrayList<>();
+ for (JsonElement entry : gameData.getAsJsonArray()) {
+ String gameName = entry.getAsJsonObject().get("name").getAsString();
+ JsonElement modes = entry.getAsJsonObject().get("modes");
+ if (modes == null) {
+ continue;
+ }
+
+ for (JsonElement modeEntry : modes.getAsJsonArray()) {
+ if (!modeEntry.getAsJsonObject().has("name") || !modeEntry.getAsJsonObject().has("identifier")) {
+ continue;
+ }
+ String modeName = modeEntry.getAsJsonObject().get("name").getAsString();
+ String fullGameName;
+ if (gameName.equals(modeName)) {
+ fullGameName = gameName;
+ } else {
+ fullGameName = gameName + ": " + modeName;
+ }
+ String identifier = modeEntry.getAsJsonObject().get("identifier").getAsString();
+ if (fullGameName.toLowerCase().contains(currentInput.toLowerCase())) {
+ choices.add(new Command.Choice(fullGameName, identifier));
+ }
+ }
+ }
+
+ if (choices.size() > 25) {
+ choices = choices.subList(0, 24);
+ }
+
+ e.replyChoices(choices).queue();
+ }
+ }
+ case "achievements" -> {
+ if (commandOption.equals("game")) {
+ Map gameData = autoCompleteAchGames();
+ List choices = new ArrayList<>();
+ for (Map.Entry entry : gameData.entrySet()) {
+ if (entry.getValue().toLowerCase().contains(currentInput.toLowerCase())) {
+ choices.add(new Command.Choice(entry.getValue(), entry.getValue()));
+ }
+ }
+
+ if (choices.size() > 25) {
+ choices = choices.subList(0, 24);
+ }
+
+ e.replyChoices(choices).queue();
+ }
+ }
+ case "search" -> {
+ assert subcommandName != null;
+ if (subcommandName.equals("achievement")) {
+ if (commandOption.equals("search")) {
+ int searchCount = 0;
+ JsonObject achievementsResources = getAchievementsResources().getAsJsonObject();
+ List choices = new ArrayList<>();
+
+ for (String game : achievementsResources.keySet()) {
+ if (searchCount == 25) {
+ break;
+ }
+ JsonObject gameAchievements = achievementsResources.get(game).getAsJsonObject();
+ JsonObject gameOneTime = gameAchievements.get("one_time").getAsJsonObject();
+ JsonObject gameTiered = gameAchievements.get("tiered").getAsJsonObject();
+ for (String oneTimeID : gameOneTime.keySet()) {
+ if (searchCount == 25) {
+ break;
+ }
+ JsonObject oneTimeAchievement = gameOneTime.get(oneTimeID).getAsJsonObject();
+ String achievementName = oneTimeAchievement.get("name").getAsString();
+ String achievementDescription = oneTimeAchievement.get("description").getAsString();
+
+ if (achievementName.toLowerCase().contains(currentInput.toLowerCase()) || achievementDescription.toLowerCase().contains(currentInput.toLowerCase())) {
+ choices.add(new Command.Choice(toReadableName(game) + ": " + achievementName, game.toUpperCase() + "_" + oneTimeID));
+ searchCount++;
+ }
+ }
+
+ for (String tieredID : gameTiered.keySet()) {
+ if (searchCount == 25) {
+ break;
+ }
+ JsonObject tieredAchievement = gameTiered.get(tieredID).getAsJsonObject();
+ String achievementName = tieredAchievement.get("name").getAsString();
+ String achievementDescription = tieredAchievement.get("description").getAsString();
+
+ if (achievementName.toLowerCase().contains(currentInput.toLowerCase()) || achievementDescription.toLowerCase().contains(currentInput.toLowerCase())) {
+ choices.add(new Command.Choice(toReadableName(game) + ": " + achievementName, game.toUpperCase() + "_" + tieredID));
+ searchCount++;
+ }
+ }
+ }
+ e.replyChoices(choices).queue();
+ }
+ } else if (subcommandName.equals("random")) {
+ if (commandOption.equals("game")) {
+ Map gameData = autoCompleteAchGames();
+ List choices = new ArrayList<>();
+ for (Map.Entry entry : gameData.entrySet()) {
+ if (entry.getValue().toLowerCase().contains(currentInput.toLowerCase())) {
+ choices.add(new Command.Choice(entry.getValue(), entry.getKey()));
+ }
+ }
+ choices.sort(Comparator.comparing(Command.Choice::getName));
+ if (choices.size() > 25) {
+ choices = choices.subList(0, 24);
+ }
+
+ e.replyChoices(choices).queue();
+ }
+ }
+ }
+ default -> {
+ e.replyChoices(Collections.emptyList()).queue();
+ }
+ }
+ }
+
+
+ @SuppressWarnings("SpellCheckingInspection")
+ private List getSkinOptions() {
+ return Arrays.asList("Legendary", "Angel", "Arcanist", "Assassin", "Automaton", "Blaze", "Cow", "Creeper", "Dragon",
+ "Dreadlord", "Enderman", "Golem", "Herobrine", "Hunter", "Moleman", "Phoenix", "Pigman", "Pirate", "Renegade",
+ "Shaman", "Shark", "Sheep", "Skeleton", "Snowman", "Spider", "Squid", "Werewolf", "Zombie");
+ }
+
+ private Map getTournamentMap() {
+ Map tournaments = new HashMap<>();
+ JsonObject tournamentData = getTournamentData();
+ assert tournamentData != null;
+ for (JsonElement entry : tournamentData.getAsJsonArray("tournaments")) {
+ JsonObject tournament = entry.getAsJsonObject();
+ int id = tournament.get("id").getAsInt();
+ String name = tournament.get("name").getAsString();
+ int iteration = tournament.get("iteration").getAsInt();
+ iteration++;
+
+ tournaments.put(name + " #" + iteration, id);
+ }
+ return tournaments;
+ }
+}
diff --git a/src/main/java/me/stuffy/stuffybot/interactions/InteractionHandler.java b/src/main/java/me/stuffy/stuffybot/interactions/InteractionHandler.java
index ae495fa..96f4859 100644
--- a/src/main/java/me/stuffy/stuffybot/interactions/InteractionHandler.java
+++ b/src/main/java/me/stuffy/stuffybot/interactions/InteractionHandler.java
@@ -1,20 +1,15 @@
package me.stuffy.stuffybot.interactions;
-import com.google.gson.JsonElement;
-import com.google.gson.JsonObject;
-import me.stuffy.stuffybot.utils.InteractionException;
-import me.stuffy.stuffybot.utils.Logger;
-import me.stuffy.stuffybot.utils.StatisticsManager;
-import net.dv8tion.jda.api.entities.Message;
+import me.stuffy.stuffybot.Bot;
+import me.stuffy.stuffybot.profiles.GlobalData;
+import me.stuffy.stuffybot.utils.*;
import net.dv8tion.jda.api.entities.MessageEmbed;
import net.dv8tion.jda.api.events.interaction.ModalInteractionEvent;
-import net.dv8tion.jda.api.events.interaction.command.CommandAutoCompleteInteractionEvent;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent;
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
import net.dv8tion.jda.api.hooks.ListenerAdapter;
import net.dv8tion.jda.api.interactions.InteractionHook;
-import net.dv8tion.jda.api.interactions.commands.Command;
import net.dv8tion.jda.api.interactions.commands.OptionMapping;
import net.dv8tion.jda.api.interactions.modals.ModalMapping;
import net.dv8tion.jda.api.utils.messages.MessageCreateBuilder;
@@ -22,43 +17,59 @@
import net.dv8tion.jda.api.utils.messages.MessageEditData;
import org.jetbrains.annotations.NotNull;
-import java.time.Instant;
import java.util.*;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
-import java.util.stream.Stream;
+import static me.stuffy.stuffybot.commands.SetupCommand.setupLinkingButton;
import static me.stuffy.stuffybot.interactions.InteractionManager.getResponse;
-import static me.stuffy.stuffybot.utils.APIUtils.getTournamentData;
import static me.stuffy.stuffybot.utils.DiscordUtils.*;
-import static me.stuffy.stuffybot.utils.MiscUtils.genBase64;
-import static me.stuffy.stuffybot.utils.MiscUtils.requiresIgn;
+import static me.stuffy.stuffybot.utils.MiscUtils.*;
+import static me.stuffy.stuffybot.utils.Verification.verifyModal;
public class InteractionHandler extends ListenerAdapter {
private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
private final Map> scheduledTasks = new HashMap<>();
- private final Map tournamentMap = getTournamentMap();
@Override
public void onSlashCommandInteraction(SlashCommandInteractionEvent event) {
String commandName = event.getName();
- String id = genBase64(3);
+ String subcommandName = event.getSubcommandName();
+ if (subcommandName == null) {
+ subcommandName = "";
+ }
+ String id = genBase64(5);
event.deferReply().queue();
ArrayList optionsArray = new ArrayList();
+ if (commandName.equals("setup")) {
+ String toSetup = event.getOption("toSetup").getAsString();
+ if (toSetup.equals("verify")) {
+ setupLinkingButton(event);
+ MessageEmbed successEmbed = makeEmbed("Verification Setup", "Successful setup", "The Verify Embed has been setup successfully.", 0x3d84a2);
+ event.getHook().setEphemeral(true).sendMessageEmbeds(successEmbed).queue();
+ return;
+ }
+ }
+
- if(requiresIgn(commandName) && event.getOption("ign") == null){
- String ign = getUsername(event);
+ if (event.getOption("ign") == null) {
+ String ign = null;
+ try {
+ ign = getUsername(event);
+ } catch (APIException e) {
+ event.getHook().sendMessageEmbeds(makeErrorEmbed(e.getAPIType() + " API Error", e.getMessage())).setEphemeral(true).queue();
+ }
optionsArray.add("ign=" + ign);
}
Pattern pattern = Pattern.compile("[,=:]");
for (OptionMapping option : event.getOptions()) {
String optionString = option.getAsString();
- if(pattern.matcher(optionString).find()){
+ if (pattern.matcher(optionString).find()) {
MessageEmbed errorEmbed = makeErrorEmbed("Slash Command Error", "An error occurred while processing your command.\n-# Invalid character in option `" + option.getName() + "`");
event.getHook().sendMessageEmbeds(errorEmbed).setEphemeral(true).queue();
return;
@@ -66,37 +77,52 @@ public void onSlashCommandInteraction(SlashCommandInteractionEvent event) {
optionsArray.add(option.getName() + "=" + optionString);
}
- InteractionId interactionId = new InteractionId(id, commandName, event.getUser().getId(), optionsArray);
+ InteractionId interactionId = new InteractionId(id, commandName, subcommandName, event.getUser().getId(), optionsArray);
+
+ if (event.getIntegrationOwners().isUserIntegration()) {
+ Logger.log(" @" + event.getUser().getName() + ": /" + commandName + " " + optionsArray);
+ } else {
+ Logger.log(" @" + event.getUser().getName() + ": /" + commandName + " " + optionsArray);
+ }
- Logger.log(" @" + event.getUser().getName() + ": /" + commandName + " " + optionsArray.toString());
+ GlobalData globalData = Bot.getGlobalData();
+ globalData.incrementCommandsRun(event.getUser().getId(), commandName);
+ globalData.addUniqueUser(event.getUser().getId(), event.getUser().getName());
- MessageCreateData response = null;
+ MessageCreateData response;
try {
- response = getResponse(interactionId);;
+ response = getResponse(interactionId);
} catch (InteractionException e) {
MessageEmbed errorEmbed = makeErrorEmbed("Slash Command Error", "An error occurred while processing your command.\n-# " + e.getMessage());
event.getHook().sendMessageEmbeds(errorEmbed).setEphemeral(true).queue();
return;
} catch (Exception e) {
MessageEmbed errorEmbed = makeErrorEmbed("Unknown Error", "Uh Oh! I have no idea what went wrong, report this.\n-# Everybody makes mistakes.");
- Logger.logError("Unknown error in command: " + commandName + " " + optionsArray.toString() + " " + e.getMessage());
+ Logger.logError("Unknown error in command: " + commandName + " " + optionsArray + " " + e.getMessage());
e.printStackTrace();
event.getHook().sendMessageEmbeds(errorEmbed).setEphemeral(true).queue();
return;
}
- if(response != null) {
+ if (response != null) {
event.getHook().sendMessage(response).queue();
}
StatisticsManager.incrementCommandUsage(commandName);
String uid = interactionId.getId();
InteractionHook hook = event.getHook();
- ScheduledFuture> scheduledFuture = scheduler.schedule(() -> {
- hook.editOriginalComponents().queue();
+ ScheduledFuture> removeComponents = scheduler.schedule(() -> {
+ try {
+ if(hook.retrieveOriginal().complete().getComponents().isEmpty()) {
+ return;
+ }
+ hook.editOriginalComponents().queue();
+ } catch (Exception e) {
+ Logger.logError("Unable to remove original components, the message may have been deleted.");
+ }
}, 30, TimeUnit.SECONDS);
- scheduledTasks.put(uid, scheduledFuture);
+ scheduledTasks.put(uid, removeComponents);
}
@Override
@@ -134,15 +160,35 @@ public void onButtonInteraction(@NotNull ButtonInteractionEvent event) {
} catch (InteractionException e) {
event.deferEdit().queue();
MessageEmbed errorEmbed = makeErrorEmbed("Invalid Button Ownership",
- "You can't use modify commands run by others.\n-# " + e.getMessage());
+ "You can't use buttons on commands run by others.\n-# " + e.getMessage());
event.getHook().sendMessageEmbeds(errorEmbed).setEphemeral(true).queue();
return;
}
}
- if (interactionId.getCommand().equals("verify")){
- verifyButton(event);
- return;
+ // Verify Button
+ switch (interactionId.getCommand()) {
+ case "verify" -> {
+ Verification.verifyButton(event);
+ return;
+ }
+
+
+ // Update Button
+ case "update" -> {
+ MessageCreateData data = new MessageCreateBuilder()
+ .setEmbeds(makeErrorEmbed("OOPS!", "This feature is currently unavailable in preparation for an overhaul.")).build();
+ event.reply(data).setEphemeral(true).queue();
+
+ return;
+ }
+
+
+ // Unverify Button
+ case "unverify" -> {
+ Verification.unverifyButton(event);
+ return;
+ }
}
event.deferEdit().queue();
@@ -182,105 +228,29 @@ public void onButtonInteraction(@NotNull ButtonInteractionEvent event) {
public void onModalInteraction(@NotNull ModalInteractionEvent event) {
String toLog = " @" + event.getUser().getName() + ": `" + event.getModalId() + "`";
for (ModalMapping mapping : event.getValues()) {
- toLog += " `" + mapping.getId() + "=" + mapping.getAsString() + "`";
+ toLog += " `" + mapping.getCustomId() + "=" + mapping.getAsString() + "`";
}
Logger.log(toLog);
- if(event.getModalId().equals("verify")) {
- String ign = Objects.requireNonNull(event.getValue("ign")).getAsString();
- String captcha = Objects.requireNonNull(event.getValue("captcha")).getAsString();
- if(!captcha.equals("stuffy")) {
- // TODO: Make this actually time out for 5 minutes
- MessageEmbed errorEmbed = makeErrorEmbed("Verification Error", "You entered the CAPTCHA incorrectly.\n-# Try again in " + discordTimeUnix(Instant.now().plusSeconds(300).toEpochMilli()));
- MessageCreateData data = new MessageCreateBuilder()
- .addEmbeds(errorEmbed)
- .build();
- event.reply(data).setEphemeral(true).queue();
- return;
- }
-
- event.reply("You got the captcha right, " + ign).setEphemeral(true).queue();
- }
-
- }
- @Override
- public void onCommandAutoCompleteInteraction(CommandAutoCompleteInteractionEvent e) {
- String commandName = e.getName();
- String commandOption = e.getFocusedOption().getName();
- String currentInput = e.getFocusedOption().getValue();
-
- switch (commandName) {
- case "megawalls" -> {
- if (commandOption.equals("skins")) {
- List options = new ArrayList<>();
- List skins = Arrays.asList("Legendary", "Angel", "Arcanist", "Assassin", "Automaton", "Blaze", "Cow", "Creeper", "Dragon",
- "Dreadlord", "Enderman", "Golem", "Herobrine", "Hunter", "Moleman", "Phoenix", "Pigman", "Pirate", "Renegade",
- "Shaman", "Shark", "Sheep", "Skeleton", "Snowman", "Spider", "Squid", "Werewolf", "Zombie");
- for (String skin : skins) {
- if (skin.toLowerCase().startsWith(currentInput.toLowerCase())) {
- options.add(skin);
- }
- }
-
- String[] optionsArray = options.toArray(new String[0]);
- if (optionsArray.length > 25) {
- optionsArray = Arrays.copyOfRange(optionsArray, 0, 24);
- }
-
- List choices = Stream.of(optionsArray)
- .map(option -> new Command.Choice(option, option.toLowerCase()))
- .toList();
-
- e.replyChoices(choices).queue();
- break;
- }
- }
- case "tournament" -> {
- if (commandOption.equals("tournament")) {
- List choices = new ArrayList<>();
- tournamentMap.forEach((name, id) -> {
- if (name.toLowerCase().startsWith(currentInput.toLowerCase()) && choices.size() <= 25){
- choices.add(new Command.Choice(name, id));
- }
- });
- e.replyChoices(choices).queue();
- break;
- }
- }
- default -> {
- e.replyChoices(Collections.emptyList()).queue();
- }
+ if (event.getModalId().equals("verify")) {
+ verifyModal(event);
}
}
- private Map getTournamentMap() {
- Map tournaments = new HashMap<>();
- JsonObject tournamentData = getTournamentData();
- for (JsonElement entry : tournamentData.getAsJsonArray("tournaments")) {
- JsonObject tournament = entry.getAsJsonObject();
- int id = tournament.get("id").getAsInt();
- String name = tournament.get("name").getAsString();
- int iteration = tournament.get("iteration").getAsInt();
- iteration++;
-
- tournaments.put(name + " #" + iteration, id);
- }
-
-
- return tournaments;
- }
-
@Override
public void onMessageReceived(@NotNull MessageReceivedEvent event) {
if (event.getAuthor().isBot()) {
return;
}
- Message.suppressContentIntentWarning();
+// String authorId = event.getAuthor().getId();
+// String authorName = event.getAuthor().getName();
+// Bot.getGlobalData().addUniqueUser(authorId, authorName);
+
String message = event.getMessage().getContentRaw();
if (message.toLowerCase().startsWith("ap!")) {
Logger.logError(" @" + event.getAuthor().getName() + ": " + message);
MessageCreateData data = new MessageCreateBuilder()
- .addEmbeds(makeErrorEmbed("Outdated Command", "We no longer support chat based commands,\nInstead try using slash commands.\n-# Join our [Discord](https://discord.gg/zqVkUrUmzN) for more info."))
+ .addEmbeds(makeErrorEmbed("Outdated Command", "We no longer support chat based commands,\nInstead try using slash commands.\n-# Join our [Discord](https://discord.gg/8jdmT5Db3Y) for more info."))
.build();
event.getMessage().reply(
data
diff --git a/src/main/java/me/stuffy/stuffybot/interactions/InteractionId.java b/src/main/java/me/stuffy/stuffybot/interactions/InteractionId.java
index d89bcd3..1f912b6 100644
--- a/src/main/java/me/stuffy/stuffybot/interactions/InteractionId.java
+++ b/src/main/java/me/stuffy/stuffybot/interactions/InteractionId.java
@@ -31,9 +31,9 @@ public InteractionId (String componentId) {
}
}
- public InteractionId(String id, String command, String userId, ArrayList options) {
+ public InteractionId(String id, String command, String subcommand, String userId, ArrayList options) {
this.id = id;
- this.command = command;
+ this.command = getCommandId(command, subcommand);
this.userId = userId;
this.options = new HashMap<>();
@@ -96,4 +96,11 @@ public InteractionId setOption(String key, Integer value) {
this.options.put(key, String.valueOf(value));
return this;
}
+
+ private String getCommandId(String command, String subcommand) {
+ if (subcommand.isEmpty()) {
+ return command;
+ }
+ return String.join("_", command, subcommand);
+ }
}
diff --git a/src/main/java/me/stuffy/stuffybot/interactions/InteractionManager.java b/src/main/java/me/stuffy/stuffybot/interactions/InteractionManager.java
index c9bc6ba..8671390 100644
--- a/src/main/java/me/stuffy/stuffybot/interactions/InteractionManager.java
+++ b/src/main/java/me/stuffy/stuffybot/interactions/InteractionManager.java
@@ -41,6 +41,16 @@ public static MessageCreateData getResponse(InteractionId interactionId) throws
case "blitz" -> BlitzCommand.blitz(interactionId);
case "megawalls" -> MegaWallsCommand.megawalls(interactionId);
case "tournament" -> TournamentCommand.tournament(interactionId);
+ case "achievements" -> AchievementsCommand.achievements(interactionId);
+ case "link" -> LinkCommand.link(interactionId);
+ case "playcommand" -> PlayCommandCommand.playCommand(interactionId);
+ case "help" -> HelpCommand.help();
+ case "search" -> SearchCommand.search(interactionId);
+ case "uuid" -> UuidCommand.uuid(interactionId);
+ case "warlords" -> WarlordsCommand.warlords(interactionId);
+ case "warlordsClasses" -> WarlordsCommand.warlordsClasses(interactionId);
+ case "warlordsWeapons" -> WarlordsCommand.warlordsWeapons(interactionId);
+ case "warlordsWeaponSummary" -> WarlordsCommand.warlordsWeaponSummary(interactionId);
default -> throw new InteractionException("Invalid command");
};
} catch (APIException e) {
diff --git a/src/main/java/me/stuffy/stuffybot/profiles/Achievement.java b/src/main/java/me/stuffy/stuffybot/profiles/Achievement.java
new file mode 100644
index 0000000..b92bdb1
--- /dev/null
+++ b/src/main/java/me/stuffy/stuffybot/profiles/Achievement.java
@@ -0,0 +1,43 @@
+package me.stuffy.stuffybot.profiles;
+
+import com.google.gson.JsonElement;
+
+public class Achievement {
+ private final String name;
+ private final String description;
+ private final int points;
+ private final boolean legacy;
+ private final boolean secret;
+ private final Type type;
+
+ private enum Type {
+ CHALLENGE,
+ TIERED
+ };
+
+ public Achievement(JsonElement achievementData) {
+ this.name = achievementData.getAsJsonObject().get("name").getAsString();
+ this.description = achievementData.getAsJsonObject().get("description").getAsString();
+ this.points = achievementData.getAsJsonObject().get("points").getAsInt();
+ this.legacy = achievementData.getAsJsonObject().get("legacy").getAsBoolean();
+ this.secret = achievementData.getAsJsonObject().get("secret").getAsBoolean();
+ this.type = Type.valueOf(achievementData.getAsJsonObject().get("type").getAsString());
+
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public int getPoints() {
+ return points;
+ }
+
+ public boolean isLegacy() {
+ return legacy;
+ }
+}
diff --git a/src/main/java/me/stuffy/stuffybot/profiles/GlobalData.java b/src/main/java/me/stuffy/stuffybot/profiles/GlobalData.java
new file mode 100644
index 0000000..7bd42dd
--- /dev/null
+++ b/src/main/java/me/stuffy/stuffybot/profiles/GlobalData.java
@@ -0,0 +1,105 @@
+package me.stuffy.stuffybot.profiles;
+
+import com.opencsv.CSVReader;
+
+import java.io.StringReader;
+import java.lang.reflect.Array;
+import java.util.*;
+
+import static me.stuffy.stuffybot.utils.APIUtils.*;
+
+public class GlobalData {
+ private final Map linkedAccounts;
+ private final Map sessionCommandsRun;
+ private final Map sessionUniqueUsers;
+ private final Map sessionUserCommandsRun;
+ private final ArrayList verifiedAccounts;
+
+ public GlobalData() {
+ String linkedContent = readFile(Objects.requireNonNull(getGitHubFile(getPrivateApiRepo(), "apis/linkeddb.csv")));
+
+ List csvData = new ArrayList<>();
+ try (CSVReader reader = new CSVReader(new StringReader(linkedContent))) {
+ csvData = reader.readAll();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ boolean firstRow = true;
+ Map linkedAccounts = new HashMap<>();
+ ArrayList verifiedAccounts = new ArrayList();
+ for (String[] row : csvData) {
+ if(firstRow) {
+ firstRow = false;
+ continue;
+ }
+ try {
+ if(Objects.equals(row[2], "")){
+ continue;
+ }
+ linkedAccounts.put(row[0], UUID.fromString(row[2]));
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ if(Objects.equals(row[4], "TRUE")){
+ verifiedAccounts.add(row[0]);
+ }
+ }
+
+ this.linkedAccounts = linkedAccounts;
+ this.verifiedAccounts = verifiedAccounts;
+ this.sessionCommandsRun = new HashMap<>();
+ this.sessionUniqueUsers = new HashMap<>();
+ this.sessionUserCommandsRun = new HashMap<>();
+ }
+
+ public Map getLinkedAccounts() {
+ return this.linkedAccounts;
+ }
+
+ public void addLinkedAccount(String discordId, UUID uuid) {
+ this.linkedAccounts.put(discordId, uuid);
+ }
+
+ public void incrementCommandsRun(String runnerId, String commandName) {
+ this.sessionCommandsRun.put(commandName, this.sessionCommandsRun.getOrDefault(commandName, 0) + 1);
+ this.sessionUserCommandsRun.put(runnerId, this.sessionUserCommandsRun.getOrDefault(runnerId, 0) + 1);
+ }
+
+ public Map getSessionCommandsRun() {
+ return this.sessionCommandsRun;
+ }
+
+ public void clearCommandsRun() {
+ this.sessionCommandsRun.clear();
+ this.sessionUserCommandsRun.clear();
+ }
+
+ public void addUniqueUser(String discordId, String discordName) {
+ this.sessionUniqueUsers.put(discordId, discordName);
+ }
+
+ public Map getSessionUniqueUsers() {
+ return this.sessionUniqueUsers;
+ }
+
+ public void clearUniqueUsers() {
+ this.sessionUniqueUsers.clear();
+ }
+
+ public Map getSessionUserCommandsRun() {
+ return this.sessionUserCommandsRun;
+ }
+
+ public ArrayList getVerifiedAccounts() {
+ return verifiedAccounts;
+ }
+
+ public void setVerifiedAccount(String discordId, boolean verified) {
+ if(verified) {
+ this.verifiedAccounts.add(discordId);
+ } else {
+ this.verifiedAccounts.remove(discordId);
+ }
+ }
+}
diff --git a/src/main/java/me/stuffy/stuffybot/profiles/HypixelProfile.java b/src/main/java/me/stuffy/stuffybot/profiles/HypixelProfile.java
index 1f6ca0d..40ee9bc 100644
--- a/src/main/java/me/stuffy/stuffybot/profiles/HypixelProfile.java
+++ b/src/main/java/me/stuffy/stuffybot/profiles/HypixelProfile.java
@@ -5,6 +5,7 @@
import com.google.gson.JsonObject;
import me.stuffy.stuffybot.utils.MiscUtils;
+import java.text.DecimalFormat;
import java.util.*;
import static me.stuffy.stuffybot.utils.APIUtils.getAchievementsResources;
@@ -12,16 +13,85 @@
import static me.stuffy.stuffybot.utils.MiscUtils.*;
public class HypixelProfile {
- private UUID uuid;
+ private final UUID uuid;
+ private final Rank rank;
+ private final JsonObject profile;
private String displayName;
- private Rank rank;
- private JsonObject profile;
+ private final int achievementPoints;
+ private int achievementsUnlocked;
+ private int legacyAchievementPoints;
+ private int legacyAchievementsUnlocked;
+ private String easiestChallenge;
+ private double easiestChallengeGlobalPercent;
public HypixelProfile(JsonObject profile) {
this.profile = profile.deepCopy();
this.uuid = MiscUtils.formatUUID(profile.get("uuid").getAsString());
this.displayName = profile.get("displayname").getAsString();
this.rank = determineRank(profile);
+ this.achievementPoints = getNestedJson(0, profile, "achievementPoints").getAsInt();
+
+ instantiateAchievements();
+ }
+
+ private void instantiateAchievements() {
+ int unlockCount = 0;
+ int unlockCountLegacy = 0;
+ int pointCountLegacy = 0;
+
+ String easiestChallenge = null;
+ double easiestChallengeGlobalPercent = 0;
+
+ JsonObject achievements = getAchievements();
+ List playerOneTime = achievements.get("achievementsOneTime").getAsJsonArray().asList();
+ List playerOneTimeString = new ArrayList<>();
+ for (JsonElement element : playerOneTime) {
+ try {
+ playerOneTimeString.add(element.getAsString());
+ } catch (Exception ignored) {
+ }
+ }
+ JsonObject playerTiered = achievements.get("achievementsTiered").getAsJsonObject();
+ JsonElement achievementsResources = getAchievementsResources();
+ for (String game : achievementsResources.getAsJsonObject().keySet()) {
+ for (String oneTime : getNestedJson(achievementsResources.getAsJsonObject(), game, "one_time").getAsJsonObject().keySet()) {
+ boolean isLegacy = getNestedJson(false, achievementsResources.getAsJsonObject(), game, "one_time", oneTime, "legacy").getAsBoolean();
+ if (playerOneTimeString.contains((game + "_" + oneTime.toLowerCase()))) {
+ if (!isLegacy) {
+ unlockCount++;
+ } else {
+ unlockCountLegacy++;
+ pointCountLegacy += getNestedJson(achievementsResources.getAsJsonObject(), game, "one_time", oneTime, "points").getAsInt();
+ }
+ } else {
+ double globalPercentUnlocked = getNestedJson(0.0, achievementsResources.getAsJsonObject(), game, "one_time", oneTime, "globalPercentUnlocked").getAsDouble();
+ if (globalPercentUnlocked > easiestChallengeGlobalPercent) {
+ easiestChallengeGlobalPercent = globalPercentUnlocked;
+ easiestChallenge = getNestedJson("Unknown", achievementsResources.getAsJsonObject(), game, "one_time", oneTime, "name").getAsString();
+ }
+ }
+ }
+
+ for (String tiered : getNestedJson(achievementsResources.getAsJsonObject(), game, "tiered").getAsJsonObject().keySet()) {
+ boolean isLegacy = getNestedJson(false, achievementsResources.getAsJsonObject(), game, "tiered", tiered, "legacy").getAsBoolean();
+ for (JsonElement tier : getNestedJson(achievementsResources.getAsJsonObject(), game, "tiered", tiered, "tiers").getAsJsonArray()) {
+ int tierAmount = tier.getAsJsonObject().get("amount").getAsInt();
+ if (getNestedJson(0, playerTiered, game + "_" + tiered.toLowerCase()).getAsInt() >= tierAmount) {
+ if (!isLegacy) {
+ unlockCount++;
+ } else {
+ unlockCountLegacy++;
+ pointCountLegacy += tier.getAsJsonObject().get("points").getAsInt();
+ }
+ }
+ }
+ }
+ }
+ this.achievementsUnlocked = unlockCount;
+ this.legacyAchievementsUnlocked = unlockCountLegacy;
+ this.legacyAchievementPoints = pointCountLegacy;
+ this.easiestChallenge = easiestChallenge;
+ this.easiestChallengeGlobalPercent = easiestChallengeGlobalPercent;
}
private static Rank determineRank(JsonObject profile) {
@@ -59,6 +129,11 @@ public UUID getUuid() {
return uuid;
}
+ public HypixelProfile setDisplayName(String displayName) {
+ this.displayName = displayName;
+ return this;
+ }
+
public String getDisplayName() {
return displayName;
}
@@ -71,14 +146,6 @@ public String getDiscord() {
return getNestedJson(profile, "socialMedia", "links", "DISCORD").getAsString();
}
- public String[] getMaxedGames() {
- // return an array of strings with the maxed games
- JsonElement allAchievements = getAchievementsResources();
- JsonElement achievements = getNestedJson(profile, "achievements");
-
- return new String[0];
- }
-
public JsonObject getProfile() {
return profile;
}
@@ -103,12 +170,8 @@ public Integer getKarma() {
return getNestedJson(profile, "karma").getAsInt();
}
- public Integer getAchievementPoints() {
- if (!profile.has("achievementPoints")) {
- return 0;
- }
-
- return getNestedJson(profile, "achievementPoints").getAsInt();
+ public int getAchievementPoints() {
+ return this.achievementPoints;
}
public String getOnlineStatus() {
@@ -180,7 +243,7 @@ public Integer getWins() {
"Arcade.sw_game_wins", "Arcade.wins_zombies", "Arcade.wins_hypixel_sports", "Arcade.wins_draw_their_thing",
"Arcade.wins_throw_out", "Arcade.wins_santa_simulator", "Arcade.wins_dragonwars2", "Arcade.wins_easter_simulator",
"Arcade.wins_scuba_simulator", "Arcade.wins_halloween_simulator", "Arcade.wins_grinch_simulator_v2",
- "Arcade.pixel_party.wins", "Arcade.woolhunt_participated_wins", "Arcade.dropper.wins",
+ "Arcade.pixel_party.wins", "Arcade.woolhunt_participated_wins", "Arcade.dropper.wins", "Arcade.disasters.stats.wins",
// Arena Brawl
"Arena.wins",
@@ -215,7 +278,7 @@ public Integer getWins() {
// Mega Walls
"Walls3.wins",
- // Turbo Kart Racers TODO: Reduce to golds?
+ // Turbo Kart Racers
"GingerBread.gold_trophy", "GingerBread.tourney_gingerbread_solo_1_gold_trophy",
// SkyWars
@@ -362,48 +425,6 @@ public JsonObject getAchievements() {
return combined;
}
- public Integer getLegacyAchievementPoints() {
- int legacyPoints = 0;
- JsonObject achievements = getAchievements();
- List playerOneTime = achievements.get("achievementsOneTime").getAsJsonArray().asList();
- List playerOneTimeString = new ArrayList<>();
- for (JsonElement element : playerOneTime) {
- try {
- playerOneTimeString.add(element.getAsString());
- } catch (Exception ignored) {
- }
- }
- JsonObject playerTiered = achievements.get("achievementsTiered").getAsJsonObject();
- JsonElement achievementsResources = getAchievementsResources();
- for (String game : achievementsResources.getAsJsonObject().keySet()) {
- for (String oneTime : getNestedJson(achievementsResources.getAsJsonObject(), game, "one_time").getAsJsonObject().keySet()) {
- boolean isLegacy = getNestedJson(false, achievementsResources.getAsJsonObject(), game, "one_time", oneTime, "legacy").getAsBoolean();
- if (!isLegacy) {
- continue;
- }
- if (playerOneTimeString.contains((game + "_" + oneTime.toLowerCase()))) {
- legacyPoints += getNestedJson(achievementsResources.getAsJsonObject(), game, "one_time", oneTime, "points").getAsInt();
- }
- }
-
- for (String tiered : getNestedJson(achievementsResources.getAsJsonObject(), game, "tiered").getAsJsonObject().keySet()) {
- boolean isLegacy = getNestedJson(false, achievementsResources.getAsJsonObject(), game, "tiered", tiered, "legacy").getAsBoolean();
- if (!isLegacy) {
- continue;
- }
-
-
- for (JsonElement tier : getNestedJson(0, achievementsResources.getAsJsonObject(), game, "tiered", tiered, "tiers").getAsJsonArray()) {
- int tierAmount = tier.getAsJsonObject().get("amount").getAsInt();
- if (getNestedJson(0, playerTiered, game + "_" + tiered.toLowerCase()).getAsInt() >= tierAmount) {
- legacyPoints += tier.getAsJsonObject().get("points").getAsInt();
- }
- }
- }
- }
- return legacyPoints;
- }
-
public Integer getPit(String stat) {
JsonObject pitStats = getNestedJson(profile, "stats", "Pit").getAsJsonObject();
try {
@@ -626,7 +647,7 @@ public Map getBlitzStats() {
public Integer getMegaWallsStat(String asString) {
try {
- return getNestedJson(0, profile, "stats", "Walls3", asString).getAsJsonObject().getAsInt();
+ return getNestedJson(0, profile, "stats", "Walls3", asString).getAsInt();
} catch (IllegalArgumentException e) {
return 0;
}
@@ -663,5 +684,60 @@ public Integer getStat(String field) {
return 0;
}
}
+
+ public String getStatFormatted(String field) {
+ try {
+ DecimalFormat df = new DecimalFormat("#,###");
+ return df.format(getStat(field));
+ } catch (IllegalArgumentException e) {
+ return "0";
+ }
+ }
+
+ public String getStatString(String field) {
+ try {
+ return getNestedJson("", profile, "stats", field).getAsString();
+ } catch (IllegalArgumentException | NullPointerException e) {
+ return "";
+ }
+ }
+
+ public JsonArray getStatArray(String field) {
+ try {
+ return getNestedJson(profile, "stats", field).getAsJsonArray();
+ } catch (IllegalArgumentException e) {
+ System.out.println(e.getMessage());
+ return new JsonArray();
+ }
+ }
+
+ public JsonObject getStatObject(String field) {
+ try {
+ return getNestedJson(profile, "stats", field).getAsJsonObject();
+ } catch (IllegalArgumentException e) {
+ System.out.println(e.getMessage());
+ return new JsonObject();
+ }
+ }
+
+ public int getAchievementsUnlocked() {
+ return this.achievementsUnlocked;
+ }
+
+ public int getLegacyAchievementsUnlocked() {
+ return this.legacyAchievementsUnlocked;
+ }
+
+ public int getLegacyAchievementPoints() {
+ return this.legacyAchievementPoints;
+ }
+
+ public String getEasiestChallenge() {
+ return this.easiestChallenge + " (" + String.format("%.2f", this.easiestChallengeGlobalPercent) + "%)";
+ }
+
+ public String getEasiestTiered() {
+ return "`Game: Close Tiered III` (97.78%)";
+ }
}
diff --git a/src/main/java/me/stuffy/stuffybot/profiles/games/warlords/WarlordsUtils.java b/src/main/java/me/stuffy/stuffybot/profiles/games/warlords/WarlordsUtils.java
new file mode 100644
index 0000000..16733b4
--- /dev/null
+++ b/src/main/java/me/stuffy/stuffybot/profiles/games/warlords/WarlordsUtils.java
@@ -0,0 +1,71 @@
+package me.stuffy.stuffybot.profiles.games.warlords;
+
+public class WarlordsUtils {
+ public static String getSpecName(String specNameRaw) {
+ return switch (specNameRaw.toLowerCase()) {
+ case "pyromancer" -> "Pyromancer";
+ case "cryomancer" -> "Cryomancer";
+ case "aquamancer" -> "Aquamancer";
+ case "berserker" -> "Berserker";
+ case "defender" -> "Defender";
+ case "revenant" -> "Revenant";
+ case "avenger" -> "Avenger";
+ case "crusader" -> "Crusader";
+ case "protector" -> "Protector";
+ case "thunderlord" -> "Thunderlord";
+ case "earthwarden" -> "Earthwarden";
+ case "spiritguard" -> "Spiritguard";
+ default -> "Unknown Spec";
+ };
+ }
+
+ public static String getSpecName(String classNameRaw, int specId) {
+ return switch (classNameRaw) {
+ case "mage" -> switch (specId) {
+ case 0 -> "Pyromancer";
+ case 1 -> "Cryomancer";
+ case 2 -> "Aquamancer";
+ default -> null;
+ };
+ case "warrior" -> switch (specId) {
+ case 0 -> "Berserker";
+ case 1 -> "Defender";
+ case 2 -> "Revenant";
+ default -> null;
+ };
+ case "paladin" -> switch (specId) {
+ case 0 -> "Avenger";
+ case 1 -> "Crusader";
+ case 2 -> "Protector";
+ default -> null;
+ };
+ case "shaman" -> switch (specId) {
+ case 0 -> "Thunderlord";
+ case 1 -> "Earthwarden";
+ case 2 -> "Spiritguard";
+ default -> null;
+ };
+ default -> null;
+ };
+ }
+
+ public static String getClassName(String classNameRaw) {
+ return switch (classNameRaw) {
+ case "mage" -> "Mage";
+ case "warrior" -> "Warrior";
+ case "paladin" -> "Paladin";
+ case "shaman" -> "Shaman";
+ default -> null;
+ };
+ }
+
+ public static String getClassRaw(int classId) {
+ return switch (classId) {
+ case 0 -> "mage";
+ case 1 -> "warrior";
+ case 2 -> "paladin";
+ case 3 -> "shaman";
+ default -> null;
+ };
+ }
+}
diff --git a/src/main/java/me/stuffy/stuffybot/profiles/games/warlords/Weapon.java b/src/main/java/me/stuffy/stuffybot/profiles/games/warlords/Weapon.java
new file mode 100644
index 0000000..728e433
--- /dev/null
+++ b/src/main/java/me/stuffy/stuffybot/profiles/games/warlords/Weapon.java
@@ -0,0 +1,321 @@
+package me.stuffy.stuffybot.profiles.games.warlords;
+
+import com.google.gson.JsonObject;
+
+import java.util.*;
+
+import static me.stuffy.stuffybot.profiles.games.warlords.WarlordsUtils.getClassRaw;
+import static me.stuffy.stuffybot.profiles.games.warlords.WarlordsUtils.getSpecName;
+
+public class Weapon {
+ private final String name;
+ private final double weaponScore;
+ private final String weaponId;
+ private final int weaponPoints;
+ private final WeaponRarity weaponRarity;
+ private final int weaponUpgradeLevel;
+ private final int weaponUpgradeMaxLevel;
+
+ public Weapon(JsonObject weaponJson) {
+ this.weaponId = getWeaponId(weaponJson);
+ this.weaponRarity = getWeaponRarity(weaponJson);
+ this.weaponUpgradeLevel = weaponJson.has("upgradeTimes") && !weaponJson.get("upgradeTimes").isJsonNull() ? weaponJson.get("upgradeTimes").getAsInt() : 0;
+ this.weaponUpgradeMaxLevel = weaponJson.has("upgradeMax") && !weaponJson.get("upgradeMax").isJsonNull() ? weaponJson.get("upgradeMax").getAsInt() : 0;
+ this.weaponScore = getWeaponScore(this, weaponJson);
+ this.weaponPoints = getWeaponPoints(this, weaponJson);
+ this.name = getWeaponName(this, weaponJson);
+ }
+
+
+
+ private enum WeaponStats {
+ DAMAGE,
+ CHANCE,
+ MULTIPLIER,
+ HEALTH,
+ ENERGY,
+ COOLDOWN,
+ MOVEMENT;
+ }
+
+
+ public enum WeaponRarity {
+ COMMON,
+ RARE,
+ EPIC,
+ LEGENDARY;
+ }
+ public record Range(int min, int max) {}
+
+ private static final Map> rarityStatRanges = new EnumMap<>(WeaponRarity.class);
+ // Rarity stat ranges by weapon rarity
+ static {
+ rarityStatRanges.put(WeaponRarity.COMMON, Map.of(
+ WeaponStats.DAMAGE, new Range(90, 100),
+ WeaponStats.CHANCE, new Range(10, 18),
+ WeaponStats.MULTIPLIER, new Range(150, 170),
+ WeaponStats.HEALTH, new Range(180, 220)
+ ));
+ rarityStatRanges.put(WeaponRarity.RARE, Map.of(
+ WeaponStats.DAMAGE, new Range(95, 105),
+ WeaponStats.CHANCE, new Range(12, 20),
+ WeaponStats.MULTIPLIER, new Range(160, 180),
+ WeaponStats.HEALTH, new Range(200, 250),
+ WeaponStats.ENERGY, new Range(10, 18)
+ ));
+ rarityStatRanges.put(WeaponRarity.EPIC, Map.of(
+ WeaponStats.DAMAGE, new Range(100, 110),
+ WeaponStats.CHANCE, new Range(15, 20),
+ WeaponStats.MULTIPLIER, new Range(160, 190),
+ WeaponStats.HEALTH, new Range(220, 275),
+ WeaponStats.ENERGY, new Range(15, 20),
+ WeaponStats.COOLDOWN, new Range(3, 5)
+ ));
+ rarityStatRanges.put(WeaponRarity.LEGENDARY, Map.of(
+ WeaponStats.DAMAGE, new Range(110, 120),
+ WeaponStats.CHANCE, new Range(15, 25),
+ WeaponStats.MULTIPLIER, new Range(180, 200),
+ WeaponStats.HEALTH, new Range(250, 400),
+ WeaponStats.ENERGY, new Range(20, 25),
+ WeaponStats.COOLDOWN, new Range(5, 10),
+ WeaponStats.MOVEMENT, new Range(5, 10)
+ ));
+ }
+ private static final Map> rarityStatRangeTotals = new EnumMap<>(WeaponRarity.class);
+
+ static {
+ for (WeaponRarity rarity : WeaponRarity.values()) {
+ int minTotal = 0;
+ int maxTotal = 0;
+ for (Range range : rarityStatRanges.get(rarity).values()) {
+ minTotal += range.min();
+ maxTotal += range.max();
+ }
+ rarityStatRangeTotals.put(rarity, new ArrayList<>(List.of(minTotal, maxTotal)));
+ }
+ }
+ private static final Map> rarityPrefixes = new EnumMap<>(WeaponRarity.class);
+
+ static {
+ rarityPrefixes.put(WeaponRarity.COMMON, new ArrayList<>(List.of("Crumbly", "Flimsy", "Rough", "Honed", "Refined", "Balanced")));
+ rarityPrefixes.put(WeaponRarity.RARE, new ArrayList<>(List.of("Savage", "Vicious", "Deadly", "Perfect")));
+ rarityPrefixes.put(WeaponRarity.EPIC, new ArrayList<>(List.of("Fierce", "Mighty", "Brutal", "Gladiator's")));
+ rarityPrefixes.put(WeaponRarity.LEGENDARY, new ArrayList<>(List.of("Vanquisher's", "Champion's", "Warlord's")));
+ }
+
+
+ private static String getWeaponName(Weapon weapon, JsonObject weaponObject) {
+ String weaponPrefix = getWeaponPrefix(weapon);
+
+ String material = weaponObject.get("material").getAsString();
+ String weaponBaseName = determineWeaponBaseName(material);
+
+ JsonObject specObject = weaponObject.get("spec").getAsJsonObject();
+ String specName = getSpecName(Objects.requireNonNull(getClassRaw(specObject.get("playerClass").getAsInt())), specObject.get("spec").getAsInt());
+
+ return weaponPrefix + " " + weaponBaseName + " of the " + specName;
+
+ }
+
+ private static String getWeaponId(JsonObject weaponObject) {
+ String weaponId = null;
+ if (weaponObject.has("id") && !weaponObject.get("id").isJsonNull()) {
+ weaponId = weaponObject.get("id").getAsString();
+ }
+ return weaponId;
+ }
+
+ private static String determineWeaponBaseName(String material) {
+ return switch (material) {
+ case "APPLE" -> "Enderfist";
+ case "BAKED_POTATO" -> "Broccomace";
+ case "BREAD" -> "Runic Axe";
+ case "CLOWNFISH" -> "Magmasword";
+ case "COD" -> "Frostbite";
+ case "COOKED_BEEF" -> "Armblade";
+ case "COOKED_COD" -> "Doubleaxe";
+ case "COOKED_CHICKEN" -> "Tenderizer";
+ case "COOKED_MUTTON" -> "Amaranth";
+ case "COOKED_PORKCHOP" -> "Gemini";
+ case "COOKED_RABBIT" -> "Cudgel";
+ case "COOKED_SALMON" -> "Felflame Blade";
+ case "DIAMOND_AXE" -> "Diamondspark";
+ case "DIAMOND_HOE" -> "Gem Axe";
+ case "DIAMOND_PICKAXE" -> "Void Twig";
+ case "DIAMOND_SPADE" -> "Gemcrusher";
+ case "GOLDEN_CARROT" -> "Void Edge";
+ case "GOLD_AXE" -> "Venomstrike";
+ case "GOLD_HOE" -> "Hatchet";
+ case "GOLD_PICKAXE" -> "Flameweaver";
+ case "GOLD_SPADE" -> "Stone Mallet";
+ case "GRILLED_PORK" -> "Gemini";
+ case "IRON_AXE" -> "Demonblade";
+ case "IRON_HOE" -> "Elven Greatsword";
+ case "IRON_PICKAXE" -> "World Tree Branch";
+ case "IRON_SPADE" -> "Hammer";
+ case "MELON" -> "Divine Reach";
+ case "MUSHROOM_STEW" -> "Lunar Relic";
+ case "MUTTON" -> "Claws";
+ case "POISONOUS_POTATO" -> "Ruby Thorn";
+ case "PORK" -> "Mandibles";
+ case "POTATO" -> "Halberd";
+ case "PUFFERFISH" -> "Golden Gladius";
+ case "PUMPKIN_PIE" -> "Orc Axe";
+ case "RABBIT_STEW" -> "Bludgeon";
+ case "RAW_BEEF" -> "Katar";
+ case "RAW_CHICKEN" -> "Nethersteel Katana";
+ case "ROTTEN_FLESH" -> "Pike";
+ case "SALMON" -> "Scimitar";
+ case "STONE_AXE" -> "Training Sword";
+ case "STONE_HOE" -> "Runeblade";
+ case "STONE_PICKAXE" -> "Walking Stick";
+ case "STONE_SPADE" -> "Drakefang";
+ case "STRING" -> "Hammer of Light";
+ case "WOOD_AXE" -> "Steel Sword";
+ case "WOOD_HOE" -> "Zweireaper";
+ case "WOOD_PICKAXE" -> "Abbadon";
+ case "WOOD_SPADE" -> "Nomegusta";
+ default -> "Unknown Weapon";
+ };
+ }
+
+ private static WeaponRarity getWeaponRarity(JsonObject weaponObject) {
+ String rarityString = weaponObject.get("category").getAsString();
+ return switch (rarityString) {
+ case "COMMON" -> WeaponRarity.COMMON;
+ case "RARE" -> WeaponRarity.RARE;
+ case "EPIC" -> WeaponRarity.EPIC;
+ case "LEGENDARY" -> WeaponRarity.LEGENDARY;
+ default -> null;
+ };
+ }
+
+ private static String getWeaponPrefix(Weapon weapon) {
+ ArrayList prefixes = rarityPrefixes.get(weapon.weaponRarity);
+ int weaponPoints = weapon.weaponPoints;
+ int maxPoints = 0;
+ int minPoints = 0;
+ for (Range range : rarityStatRanges.get(weapon.weaponRarity).values()) {
+ maxPoints += range.max();
+ minPoints += range.min();
+ }
+ // account for (bugged) removed stat
+ switch(weapon.weaponRarity) {
+ case COMMON -> {
+ minPoints += 4;
+ maxPoints += 4;
+ }
+ case RARE -> {
+ minPoints += 6;
+ maxPoints += 8;
+ }
+ case EPIC -> {
+ minPoints += 7;
+ maxPoints += 9;
+ }
+ case LEGENDARY -> {
+ minPoints += 10;
+ maxPoints += 15;
+ }
+ }
+ int reducedPoints = weaponPoints - minPoints;
+ int pointsRange = maxPoints - minPoints;
+ int prefixIndex = (int) ((double) reducedPoints / pointsRange * prefixes.size());
+ if (prefixIndex >= prefixes.size()) {
+ prefixIndex = prefixes.size() - 1;
+ } else if (prefixIndex < 0) {
+ prefixIndex = 0;
+ }
+ return prefixes.get(prefixIndex);
+ }
+
+ private static int getWeaponPoints(Weapon weapon, JsonObject weaponObject) {
+ int totalPoints = 0;
+ int weaponUpgradeLevel = weapon.weaponUpgradeLevel;
+ for (WeaponStats stat : rarityStatRanges.get(weapon.weaponRarity).keySet()) {
+ int statValue;
+ if (!weaponObject.has(stat.name().toLowerCase()) || weaponObject.get(stat.name().toLowerCase()).isJsonNull()) {
+ statValue = 0;
+ } else {
+ statValue = weaponObject.get(stat.name().toLowerCase()).getAsInt();
+ if(weaponUpgradeLevel > 0) {
+ statValue = upgradeStat(stat, statValue, weaponUpgradeLevel);
+ }
+ }
+ totalPoints += statValue;
+ }
+ return totalPoints;
+ }
+
+ private static int upgradeStat(WeaponStats stat, int baseValue, int upgradeLevel) {
+ double upgradeAmount = switch (stat) {
+ case HEALTH -> 0.25;
+ case ENERGY -> 0.1;
+ case DAMAGE, COOLDOWN, MOVEMENT -> 0.075;
+ default -> 0;
+ };
+ return (int) Math.ceil(baseValue * (1 + upgradeAmount * upgradeLevel));
+ }
+
+ private static double getWeaponScore(Weapon weapon, JsonObject weaponObject) {
+ WeaponRarity rarity = weapon.weaponRarity;
+ double totalWeaponScore = 0;
+ for (WeaponStats stat : rarityStatRanges.get(rarity).keySet()) {
+ double statValue;
+ if (!weaponObject.has(stat.name().toLowerCase()) || weaponObject.get(stat.name().toLowerCase()).isJsonNull()) {
+ statValue = 0;
+ } else {
+ statValue = weaponObject.get(stat.name().toLowerCase()).getAsInt();
+ }
+ Range statRange = rarityStatRanges.get(rarity).get(stat);
+ double statScore = (statValue - statRange.min()) / (statRange.max() - statRange.min());
+ totalWeaponScore += statScore;
+ }
+ return totalWeaponScore / rarityStatRanges.get(rarity).size();
+ }
+
+ // Getters
+
+ public String getWeaponId() {
+ return this.weaponId;
+ }
+ public double getWeaponScore() {
+ return this.weaponScore;
+ }
+
+ public String getName() {
+ return this.name;
+ }
+
+ public double getWeaponPoints() {
+ return this.weaponPoints;
+ }
+
+ public int getUpgradeLevel() {
+ return this.weaponUpgradeLevel;
+ }
+
+ public int getMaxUpgradeLevel() {
+ return this.weaponUpgradeMaxLevel;
+ }
+
+ public String getRarity() {
+ switch(this.weaponRarity.name()) {
+ case "COMMON" -> {
+ return "[C]";
+ }
+ case "RARE" -> {
+ return "[R]";
+ }
+ case "EPIC" -> {
+ return "[E]";
+ }
+ case "LEGENDARY" -> {
+ return "[L]";
+ }
+ default -> {
+ return "[?]";
+ }
+ }
+ }
+}
diff --git a/src/main/java/me/stuffy/stuffybot/utils/APIUtils.java b/src/main/java/me/stuffy/stuffybot/utils/APIUtils.java
index cf4c675..26b250e 100644
--- a/src/main/java/me/stuffy/stuffybot/utils/APIUtils.java
+++ b/src/main/java/me/stuffy/stuffybot/utils/APIUtils.java
@@ -3,32 +3,45 @@
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
+import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
+import com.opencsv.CSVReader;
+import com.opencsv.CSVWriter;
+import me.stuffy.stuffybot.Bot;
import me.stuffy.stuffybot.commands.TournamentCommand;
import me.stuffy.stuffybot.profiles.HypixelProfile;
import me.stuffy.stuffybot.profiles.MojangProfile;
import org.jetbrains.annotations.NotNull;
+import org.kohsuke.github.GHContent;
+import org.kohsuke.github.GitHub;
+import org.kohsuke.github.GitHubBuilder;
-import java.io.InputStream;
-import java.io.InputStreamReader;
+import java.io.*;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
-import java.util.UUID;
+import java.util.*;
import java.util.concurrent.TimeUnit;
+import static me.stuffy.stuffybot.utils.Logger.log;
import static me.stuffy.stuffybot.utils.Logger.logError;
public class APIUtils {
static String hypixelApiUrl = "https://api.hypixel.net/v2/";
static String mojangApiUrl = "https://api.mojang.com/";
+ static String mojangSessionApiUrl = "https://sessionserver.mojang.com/";
+ static String privateApiRepo = "stuffybot/PrivateAPI";
+ static String publicApiRepo = "stuffybot/PublicAPI";
+ static String stuffyApiUrl = "https://raw.githubusercontent.com/stuffybot/PublicAPI/main/";
+
public static HypixelProfile getHypixelProfile(String username) throws APIException {
MojangProfile profile = getMojangProfile(username);
- return getHypixelProfile(profile.getUuid());
+ String ign = profile.getUsername();
+ return getHypixelProfile(profile.getUuid()).setDisplayName(ign);
}
private static final LoadingCache hypixelProfileCache = CacheBuilder.newBuilder()
@@ -82,18 +95,22 @@ public static HypixelProfile fetchHypixelProfile(UUID uuid) throws APIException
return new HypixelProfile(object.get("player").getAsJsonObject());
}
case 400 -> {
+ logError(response.body());
logError("Hypixel API Error [Status Code: " + response.statusCode() + "] [UUID: " + uuid + "]");
throw new APIException("Hypixel", "A field is missing, this should never happen.");
}
case 403 -> {
+ logError(response.body());
logError("Hypixel API Error [Status Code: " + response.statusCode() + "] [UUID: " + uuid + "]");
throw new APIException("Hypixel", "Invalid API Key, contact the Stuffy immediately.");
}
case 429 -> {
+ logError(response.body());
logError("Hypixel API Error [Status Code: " + response.statusCode() + "] [UUID: " + uuid + "]");
throw new APIException("Hypixel", "Rate limited by Hypixel API, try again later.");
}
default -> {
+ logError(response.body());
logError("Unknown Hypixel API Error [Status Code: " + response.statusCode() + "] [UUID: " + uuid + "]");
throw new APIException("Hypixel", "I've never seen this error before.");
}
@@ -104,7 +121,7 @@ public static HypixelProfile fetchHypixelProfile(UUID uuid) throws APIException
.expireAfterWrite(1, TimeUnit.HOURS)
.build(
new CacheLoader() {
- public MojangProfile load(String username) {
+ public MojangProfile load(@NotNull String username) {
try{
return fetchMojangProfile(username);
} catch (APIException e){
@@ -164,6 +181,61 @@ public static MojangProfile fetchMojangProfile(String username) throws APIExcept
return profile;
}
+ private static final LoadingCache mojangProfileUUIDCache = CacheBuilder.newBuilder()
+ .expireAfterWrite(1, TimeUnit.HOURS)
+ .build(
+ new CacheLoader() {
+ public MojangProfile load(@NotNull UUID uuid) {
+ try{
+ return fetchMojangProfile(uuid);
+ } catch (APIException e){
+ throw new RuntimeException(e);
+ }
+ }
+ }
+ );
+
+ public static MojangProfile getMojangProfile(UUID uuid) throws APIException{
+ try{
+ return mojangProfileUUIDCache.getUnchecked(uuid);
+ } catch (RuntimeException e){
+ if (e.getCause().getCause() instanceof APIException) {
+ throw (APIException) e.getCause().getCause();
+ } else {
+ throw e;
+ }
+ }
+ }
+
+ public static MojangProfile fetchMojangProfile(UUID uuid) throws APIException {
+ HttpRequest getRequest = HttpRequest.newBuilder()
+ .uri(URI.create(mojangSessionApiUrl + "session/minecraft/profile/" + uuid.toString()))
+ .build();
+ HttpClient client = HttpClient.newHttpClient();
+ HttpResponse response = client.sendAsync(getRequest, HttpResponse.BodyHandlers.ofString()).join();
+ switch (response.statusCode()) {
+ case 200 -> {
+ JsonParser parser = new JsonParser();
+ JsonElement element = parser.parse(response.body());
+ JsonObject object = element.getAsJsonObject();
+ String name = object.get("name").getAsString();
+ return new MojangProfile(name, uuid);
+ }
+ case 204,404 -> {
+ logError("Mojang API Error [Status Code: " + response.statusCode() + "] [UUID: " + uuid + "]");
+ throw new APIException("Mojang", "There is no Minecraft account with that UUID.");
+ }
+ case 429 -> {
+ logError("Mojang API Error [Status Code: " + response.statusCode() + "] [UUID: " + uuid + "]");
+ throw new APIException("Mojang", "Rate limited by Mojang API, try again later.");
+ }
+ default -> {
+ logError("Unknown Mojang API Error [Status Code: " + response.statusCode() + "] [UUID: " + uuid + "]");
+ throw new APIException("Mojang", "I've never seen this error before.");
+ }
+ }
+ }
+
private static final LoadingCache achievementsCache = CacheBuilder.newBuilder()
.expireAfterWrite(1, TimeUnit.HOURS)
.build(
@@ -202,8 +274,45 @@ private static JsonElement fetchAchievementsResources() {
}
}
+ private static final LoadingCache playCommandCache = CacheBuilder.newBuilder()
+ .expireAfterWrite(1, TimeUnit.HOURS)
+ .build(
+ new CacheLoader<>() {
+ public JsonElement load(@NotNull String key) {
+ return fetchPlayCommands();
+ }
+ }
+ );
+
+ public static JsonElement getPlayCommands() {
+ return playCommandCache.getUnchecked("achievements");
+ }
+
+ private static JsonElement fetchPlayCommands() {
+ try {
+ HttpRequest getRequest = HttpRequest.newBuilder()
+ .uri(URI.create(stuffyApiUrl + "apis/playcommands.json"))
+ .build();
+ HttpClient client = HttpClient.newHttpClient();
+ HttpResponse response = client.send(getRequest, HttpResponse.BodyHandlers.ofString());
+
+ if (response.statusCode() == 200) {
+
+ JsonParser parser = new JsonParser();
+
+ return parser.parse(response.body());
+
+ } else {
+ throw new IllegalStateException("Unexpected response from Stuffy API: " + response.statusCode());
+ }
+ } catch (Exception e) {
+ throw new RuntimeException("Failed to fetch play commands from Stuffy API", e);
+ }
+ }
+
public static JsonObject getTournamentData() {
+ // # TODO: Switch to https://raw.githubusercontent.com/stuffybot/PublicAPI/main/apis/tournaments.json
try (InputStream inputStream = TournamentCommand.class.getResourceAsStream("/data/tournaments.json")) {
assert inputStream != null;
try (InputStreamReader reader = new InputStreamReader(inputStream, StandardCharsets.UTF_8)) {
@@ -214,4 +323,265 @@ public static JsonObject getTournamentData() {
return null;
}
}
+
+ public static GitHub connectToGitHub() {
+ try {
+ GitHub github = new GitHubBuilder().withOAuthToken(System.getenv("GITHUB_OAUTH")).build();
+ log(" Connected to GitHub as " + github.getMyself().getLogin() + " successfully.");
+ return github;
+ } catch (IOException e) {
+ logError(" Failed to connect to GitHub: " + e.getMessage());
+ return null;
+ }
+ }
+
+ public static void updateGitHubFile(String repo, String path, String content, String message) {
+ try {
+ Bot.getGitHub().getRepository(repo).createContent()
+ .path(path)
+ .content(content)
+ .message(message)
+ .sha(Bot.getGitHub().getRepository(repo).getFileContent(path).getSha())
+ .commit();
+ log(" Updated file " + path + " in " + repo + " successfully.");
+ } catch (IOException e) {
+ logError(" Failed to update file " + path + " in " + repo + ": " + e.getMessage());
+ }
+ }
+
+ public static void uploadGitHubFile(String repo, String path, String content, String message) {
+ try {
+ Bot.getGitHub().getRepository(repo).createContent()
+ .path(path)
+ .content(content)
+ .message(message)
+ .commit();
+ log(" Uploaded file " + path + " in " + repo + " successfully.");
+ } catch (IOException e) {
+ logError(" Failed to upload file " + path + " in " + repo + ": " + e.getMessage());
+ }
+ }
+
+ public static GHContent getGitHubFile(String repo, String path) {
+ try {
+ return Bot.getGitHub().getRepository(repo).getFileContent(path);
+ } catch (IOException e) {
+ logError(" Failed to get file " + path + " in " + repo + ": " + e.getMessage());
+ return null;
+ }
+ }
+
+ public static String getPrivateApiRepo() {
+ return privateApiRepo;
+ }
+
+
+ public static void updateBotStats(int totalServers, int totalUsers, Map commandsRun) {
+ GHContent botStats = getGitHubFile(privateApiRepo, "apis/bot.json");
+ if (botStats == null) throw new IllegalStateException("Failed to get bot.json from GitHub");
+
+ try {
+ // Read the JSON content
+ String botStatsContent = readFile(botStats);
+ JsonObject fullJson = JsonParser.parseString(botStatsContent).getAsJsonObject();
+
+ // Update the JSON content
+ fullJson.addProperty("lastUpdated", System.currentTimeMillis());
+ JsonArray allBots = fullJson.get("bots").getAsJsonArray();
+ String botId = Bot.getInstance().getJDA().getSelfUser().getId();
+
+ JsonObject bot = null;
+ for (JsonElement element : allBots) {
+ JsonObject currentBot = element.getAsJsonObject();
+ if (currentBot.get("id").getAsString().equals(botId)) {
+ bot = currentBot;
+ break;
+ }
+ }
+
+ if (bot == null) {
+ throw new IllegalStateException("Failed to find bot in bot.json");
+ }
+
+ bot.addProperty("servers", totalServers);
+ bot.addProperty("totalusers", totalUsers);
+
+ JsonObject commands = bot.get("commandsRun").getAsJsonObject();
+
+ for (Map.Entry entry : commandsRun.entrySet()) {
+ String command = entry.getKey();
+ int count = entry.getValue();
+ int currentCount = 0;
+ if(commands.has(command)) currentCount = commands.get(command).getAsInt();
+ commands.addProperty(command, currentCount + count);
+ }
+
+ fullJson.add("bots", allBots);
+
+ // Write the updated content back to the JSON file
+ updateGitHubFile(privateApiRepo, "apis/bot.json", fullJson.toString(), "Updated bot stats.");
+ log(" Updated bot stats for " + totalServers + " servers.");
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static void updateUsersStats(Map uniqueUsers, Map userCommandsRun) {
+ GHContent linkedDB = getGitHubFile(privateApiRepo, "apis/linkeddb.csv");
+ if (linkedDB == null) throw new IllegalStateException("Failed to get linkeddb.csv from GitHub");
+
+ try {
+ // Read the CSV content
+ String linkedDBContent = readFile(linkedDB);
+ List csvData;
+ try (CSVReader reader = new CSVReader(new StringReader(linkedDBContent))) {
+ csvData = reader.readAll();
+ }
+
+ int newUsers = 0;
+ int updatedUsers = 0;
+ int newCommandsRun = 0;
+ // Update the CSV content
+ for (String discordId : uniqueUsers.keySet()) {
+ String discordName = uniqueUsers.get(discordId);
+ int commandsRun = userCommandsRun.getOrDefault(discordId, 0);
+ newCommandsRun += commandsRun;
+ boolean updated = false;
+ for (String[] row : csvData) {
+ if (row[0].equals(discordId)) {
+ row[1] = discordName;
+ if (row[5].isEmpty()) row[5] = "0";
+ row[5] = String.valueOf(commandsRun + Integer.parseInt(row[5]));
+ updated = true;
+ updatedUsers++;
+ break;
+ }
+ }
+ if (!updated) {
+ newUsers++;
+ csvData.add(new String[]{discordId, discordName, "", "", "", String.valueOf(commandsRun)});
+ }
+ }
+
+ // Write the updated content back to the CSV file
+ StringWriter stringWriter = new StringWriter();
+ try (CSVWriter writer = new CSVWriter(stringWriter)) {
+ writer.writeAll(csvData);
+ }
+ String updatedContent = stringWriter.toString();
+ updateGitHubFile(privateApiRepo, "apis/linkeddb.csv", updatedContent,
+ "Added `" + newCommandsRun + "` new commands run by `" + newUsers + "` new users and `"
+ + updatedUsers + "` existing users.");
+ Logger.log(" Updated stats for " + newUsers + " new users and " + updatedUsers + " existing users.");
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static String readFile(GHContent content) {
+ try {
+ InputStream inputStream = content.read();
+ InputStreamReader reader = new InputStreamReader(inputStream);
+ StringBuilder file = new StringBuilder();
+ int character;
+ while ((character = reader.read()) != -1) {
+ file.append((char) character);
+ }
+ return file.toString();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static void setVerifiedStatus(String discordId, Boolean verified) {
+ GHContent linkedDB = getGitHubFile(privateApiRepo, "apis/linkeddb.csv");
+ if (linkedDB == null) throw new IllegalStateException("Failed to get linkeddb.csv from GitHub");
+
+ try {
+ // Read the CSV content
+ String linkedDBContent = readFile(linkedDB);
+ List csvData;
+ try (CSVReader reader = new CSVReader(new StringReader(linkedDBContent))) {
+ csvData = reader.readAll();
+ }
+
+ // Update the CSV content
+ for (String[] row : csvData) {
+ if (row[0].equals(discordId)) {
+ row[4] = verified ? "TRUE" : "FALSE";
+ break;
+ }
+ }
+
+ // Write the updated content back to the CSV file
+ StringWriter stringWriter = new StringWriter();
+ try (CSVWriter writer = new CSVWriter(stringWriter)) {
+ writer.writeAll(csvData);
+ }
+ String updatedContent = stringWriter.toString();
+ updateGitHubFile(privateApiRepo, "apis/linkeddb.csv", updatedContent, "`@" + Bot.getGlobalData().getSessionUniqueUsers().getOrDefault(discordId, "NULL") + "` verified status set to `" + verified + "`");
+ Bot.getGlobalData().setVerifiedAccount(discordId, verified);
+
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static void updateLinkedDB(String discordId, UUID uuid, String ign) {
+ String discordName = Bot.getGlobalData().getSessionUniqueUsers().getOrDefault(discordId, "NULL");
+
+ GHContent linkedDB = getGitHubFile(privateApiRepo, "apis/linkeddb.csv");
+ if (linkedDB == null) throw new IllegalStateException("Failed to get linkeddb.csv from GitHub");
+
+ try {
+ // Read the CSV content
+ String linkedDBContent = readFile(linkedDB);
+ List csvData;
+ try (CSVReader reader = new CSVReader(new StringReader(linkedDBContent))) {
+ csvData = reader.readAll();
+ }
+
+ // Update the CSV content
+ boolean updated = false;
+ for (String[] row : csvData) {
+ if (row[0].equals(discordId)) {
+ row[1] = discordName;
+ row[2] = uuid.toString();
+ row[3] = ign;
+ updated = true;
+ break;
+ }
+ }
+ if (!updated) {
+ csvData.add(new String[]{discordId, discordName, uuid.toString(), ign, "", ""});
+ }
+
+ // Write the updated content back to the CSV file
+ StringWriter stringWriter = new StringWriter();
+ try (CSVWriter writer = new CSVWriter(stringWriter)) {
+ writer.writeAll(csvData);
+ }
+ String updatedContent = stringWriter.toString();
+ updateGitHubFile(privateApiRepo, "apis/linkeddb.csv", updatedContent, "`@" + discordName + "` linked as `" + ign + "`");
+ Bot.getGlobalData().addLinkedAccount(discordId, uuid);
+
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static void uploadLogs() {
+ try {
+ // Read the logs content
+ String logsContent = String.join("\n", Logger.getLogs());
+
+ String logName = Logger.getLogName();
+
+ // Write the updated content back to the logs file
+ uploadGitHubFile(privateApiRepo, "apis/logs/" + Bot.getInstance().getJDA().getSelfUser().getAsTag() + "/" + logName + ".txt", logsContent, "Uploaded logs for " + logName + ".");
+ Logger.log(" Uploaded logs to GitHub.");
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
}
diff --git a/src/main/java/me/stuffy/stuffybot/utils/Config.java b/src/main/java/me/stuffy/stuffybot/utils/Config.java
new file mode 100644
index 0000000..b68132e
--- /dev/null
+++ b/src/main/java/me/stuffy/stuffybot/utils/Config.java
@@ -0,0 +1,45 @@
+package me.stuffy.stuffybot.utils;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Properties;
+
+public class Config {
+ private static final Properties properties = new Properties();
+
+ static {
+ try (InputStream input = Config.class.getClassLoader().getResourceAsStream("config.properties")) {
+ if (input == null) {
+ Logger.logError("Unable to find config.properties");
+ throw new RuntimeException("Unable to find config.properties");
+ }
+ properties.load(input);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ public static String getEnvironment() {
+ return properties.getProperty("environment", "development");
+ }
+
+ public static String getHomeGuildId() {
+ return properties.getProperty("homeGuildId");
+ }
+
+ public static String getVerifiedRoleId() {
+ return properties.getProperty("verifiedRoleId");
+ }
+
+ public static String getNotVerifiedRoleId() {
+ return properties.getProperty("notVerifiedRoleId");
+ }
+
+ public static String getLinkCommandId() {
+ return properties.getProperty("linkCommandId");
+ }
+
+ public static String getCustomStatus() {
+ return properties.getProperty("customStatus");
+ }
+}
diff --git a/src/main/java/me/stuffy/stuffybot/utils/DiscordUtils.java b/src/main/java/me/stuffy/stuffybot/utils/DiscordUtils.java
index 7eda52a..916804b 100644
--- a/src/main/java/me/stuffy/stuffybot/utils/DiscordUtils.java
+++ b/src/main/java/me/stuffy/stuffybot/utils/DiscordUtils.java
@@ -3,30 +3,20 @@
import me.stuffy.stuffybot.Bot;
import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.entities.MessageEmbed;
-import net.dv8tion.jda.api.entities.User;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
-import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent;
-import net.dv8tion.jda.api.interactions.components.ActionRow;
-import net.dv8tion.jda.api.interactions.components.text.TextInput;
-import net.dv8tion.jda.api.interactions.components.text.TextInputStyle;
-import net.dv8tion.jda.api.interactions.modals.Modal;
-import net.dv8tion.jda.api.utils.messages.MessageCreateBuilder;
-import net.dv8tion.jda.api.utils.messages.MessageCreateData;
-
-import java.util.Arrays;
-import java.util.Calendar;
-import java.util.Date;
-import java.util.List;
+import java.util.*;
+
+import static me.stuffy.stuffybot.utils.APIUtils.getMojangProfile;
import static me.stuffy.stuffybot.utils.MiscUtils.toSkillIssue;
public class DiscordUtils {
- public static MessageEmbed makeEmbed(String embedTitle, String embedSubtitle, String embedContent, int embedColor) {
+ public static MessageEmbed makeEmbed(String embedTitle, String embedSubtitle, String embedContent, int embedColor, Integer maxLines) {
EmbedBuilder embedBuilder = new EmbedBuilder();
embedBuilder.setTitle(embedTitle);
String[] lines = embedContent.split("\n");
int lineCount = lines.length;
- if (lineCount <= 15) {
+ if (lineCount <= maxLines) {
if(embedSubtitle == null)
embedBuilder.setDescription(embedContent);
else
@@ -53,16 +43,30 @@ public static MessageEmbed makeEmbed(String embedTitle, String embedSubtitle, St
return embedBuilder.build();
}
+ public static MessageEmbed makeEmbed(String embedTitle, String embedSubtitle, String embedContent, int embedColor) {
+ return makeEmbed(embedTitle, embedSubtitle, embedContent, embedColor, 15);
+ }
public static MessageEmbed makeErrorEmbed(String embedTitle, String embedContent) {
if (Calendar.getInstance().get(Calendar.MONTH) == Calendar.APRIL && Calendar.getInstance().get(Calendar.DAY_OF_MONTH) == 1){
embedTitle = toSkillIssue(embedTitle);
embedContent = toSkillIssue(embedContent);
}
- return makeEmbed(":no_entry: " + embedTitle, null, embedContent, 0xff0000);
+ return makeEmbed(":no_entry: " + embedTitle, null, embedContent, 0xC95353);
+ }
+
+ public static MessageEmbed makeEmbedWithImage(String embedTitle, String embedSubtitle, String embedContent, String imageUrl, int embedColor) {
+ EmbedBuilder embedBuilder = new EmbedBuilder();
+ embedBuilder.setTitle(embedTitle);
+ embedBuilder.setDescription("-# " + embedSubtitle + "\n" + embedContent);
+ embedBuilder.setColor(embedColor);
+ embedBuilder.setFooter("Stuffy Bot by @stuffy");
+ embedBuilder.setTimestamp(new Date().toInstant());
+ embedBuilder.setImage(imageUrl);
+ return embedBuilder.build();
}
public static MessageEmbed makeUpdateEmbed(String embedTitle, String embedContent) {
- return makeEmbed(":mega: " + embedTitle, null, embedContent, 0xffef14);
+ return makeEmbed(":mega: " + embedTitle, null, embedContent, 0xEBD773);
}
public static MessageEmbed makeStaffRankChangeEmbed(String ign, String oldRank, String newRank, String position) {
@@ -82,11 +86,11 @@ public static MessageEmbed makeStaffRankChangeEmbed(String ign, String oldRank,
}
public static MessageEmbed makeStatsEmbed(String embedTitle, String embedContent) {
- return makeEmbed(embedTitle, null, embedContent, 0xf7cb72);
+ return makeEmbed(embedTitle, null, embedContent, 0x6D8FCE);
}
public static MessageEmbed makeStatsEmbed(String embedTitle, String embedSubtitle, String embedContent) {
- return makeEmbed(embedTitle, embedSubtitle , embedContent, 0xf7cb72);
+ return makeEmbed(embedTitle, embedSubtitle , embedContent, 0x6D8FCE);
}
public static String getDiscordUsername(String id){
@@ -129,53 +133,15 @@ public static String discordTimeUnix(long timestamp) {
return discordTimeUnix(timestamp, "R");
}
- public static void verifyButton(ButtonInteractionEvent event) {
- // Look up the user in the database, and check if they have already verified/linked
- // If they are verified, verify them
- // If they are linked, attempt to verify them
- // If they are not linked, or the verification fails, prompt them to verify
-
- String userId = event.getUser().getId();
- if(isVerified(userId)){
- MessageCreateData data = new MessageCreateBuilder()
- .setEmbeds(makeErrorEmbed("Verification Error", "You have already verified your identity, silly goose.")).build();
- event.reply(data).setEphemeral(true).queue();
- return;
- }
-
-
- Modal modal = Modal.create("verify", "Verify your identity in Stuffy Discord")
- .addComponents(ActionRow.of(TextInput.create("ign", "Minecraft Username", TextInputStyle.SHORT)
- .setPlaceholder("Your Minecraft Username")
- .setMaxLength(16)
- .setMinLength(1)
- .setRequired(true)
- .build()),
- ActionRow.of(
- TextInput.create("captcha", "CAPTCHA", TextInputStyle.PARAGRAPH)
- .setPlaceholder("Enter the word 'stuffy'.\n" +
- "To prevent abuse, failing the CAPTCHA " +
- "will result in a short timeout.")
- .setRequired(false)
- .build()))
- .build();
- event.replyModal(modal).queue();
- }
-
- public static void updateRoles(User user, String ign, boolean announce) {
- Bot bot = Bot.getInstance();
- // bot.getHomeGuild().getMember(user).modifyNickname(ign).queue();
- }
-
- public static boolean isVerified(String userId) {
- return true;
- }
-
- public static String getUsername(SlashCommandInteractionEvent event) {
+ public static String getUsername(SlashCommandInteractionEvent event) throws APIException {
String username = event.getOption("ign") == null ? null : event.getOption("ign").getAsString();
if (username == null) {
- // TODO: First, check the database
- username = getDiscordUsername(event.getUser().getName());
+ if (Bot.getGlobalData().getLinkedAccounts().containsKey(event.getUser().getId())) {
+ UUID uuid = Bot.getGlobalData().getLinkedAccounts().get(event.getUser().getId());
+ username = getMojangProfile(uuid).getUsername();
+ } else {
+ username = getDiscordUsername(event.getUser().getName());
+ }
}
return username;
}
diff --git a/src/main/java/me/stuffy/stuffybot/utils/Logger.java b/src/main/java/me/stuffy/stuffybot/utils/Logger.java
index 40274b5..0cd9d67 100644
--- a/src/main/java/me/stuffy/stuffybot/utils/Logger.java
+++ b/src/main/java/me/stuffy/stuffybot/utils/Logger.java
@@ -1,13 +1,17 @@
package me.stuffy.stuffybot.utils;
+import java.util.ArrayList;
+import java.util.List;
+
public class Logger {
-// public Guild logGuild;
-// public Channel logChannel;
+ private static final List logs = new ArrayList<>();
+ private static String logName;
public static void log(String message) {
String date = java.time.LocalDate.now().toString();
String time = java.time.LocalTime.now().truncatedTo(java.time.temporal.ChronoUnit.SECONDS).toString();
message = "[" + date + " " + time + "] " + message;
+ logs.add(message);
System.out.println(message);
}
@@ -15,6 +19,19 @@ public static void logError(String message) {
String date = java.time.LocalDate.now().toString();
String time = java.time.LocalTime.now().truncatedTo(java.time.temporal.ChronoUnit.SECONDS).toString();
message = "[ERROR] [" + date + " " + time + "] " + message;
+ logs.add(message);
System.out.println(message);
}
+
+ public static List getLogs() {
+ return logs;
+ }
+
+ public static String getLogName() {
+ return logName;
+ }
+
+ public static void setLogName(String name) {
+ logName = name;
+ }
}
diff --git a/src/main/java/me/stuffy/stuffybot/utils/MiscUtils.java b/src/main/java/me/stuffy/stuffybot/utils/MiscUtils.java
index d6ff5d5..4c4c661 100644
--- a/src/main/java/me/stuffy/stuffybot/utils/MiscUtils.java
+++ b/src/main/java/me/stuffy/stuffybot/utils/MiscUtils.java
@@ -10,6 +10,8 @@
import java.util.Map;
import java.util.UUID;
+import static me.stuffy.stuffybot.utils.APIUtils.getAchievementsResources;
+
public class MiscUtils {
private static final Gson gson = new Gson();
public static UUID formatUUID(String uuid) {
@@ -43,6 +45,14 @@ public static JsonElement getNestedJson(Integer defaultValue, Object object, Str
}
}
+ public static JsonElement getNestedJson(Double defaultValue, Object object, String... keys) {
+ try {
+ return getNestedJson((JsonObject) object, keys);
+ } catch (IllegalArgumentException e) {
+ return stringToJson(defaultValue.toString());
+ }
+ }
+
public static JsonElement getNestedJson(Boolean defaultValue, Object object, String... keys) {
try {
return getNestedJson((JsonObject) object, keys);
@@ -157,8 +167,8 @@ public static String genBase64(Integer length){
return sb.toString();
}
- public static String toReadableName(String resourcesName) {
- Map resourceNames = new HashMap<>();
+ private static Map getResourceNames() {
+ Map resourceNames = new HashMap<>();
resourceNames.put("arcade", "Arcade");
resourceNames.put("arena", "Arena Brawl");
resourceNames.put("bedwars", "Bed Wars");
@@ -191,9 +201,23 @@ public static String toReadableName(String resourcesName) {
resourceNames.put("warlords", "Warlords");
resourceNames.put("woolgames", "Wool Games");
+ return resourceNames;
+ }
+
+ public static String toReadableName(String resourcesName) {
+ Map resourceNames = getResourceNames();
return resourceNames.getOrDefault(resourcesName, resourcesName);
}
+ public static String fromReadableName(String readableName) {
+ Map resourceNames = getResourceNames();
+ Map reverseMap = new HashMap<>();
+ for (Map.Entry entry : resourceNames.entrySet()) {
+ reverseMap.put(entry.getValue(), entry.getKey());
+ }
+ return reverseMap.getOrDefault(readableName, readableName);
+ }
+
public static String minutesFormatted(int minutes) {
int hours = minutes / 60;
int remainingMinutes = minutes % 60;
@@ -201,5 +225,50 @@ public static String minutesFormatted(int minutes) {
return remainingMinutes + "m";
return hours + "h " + remainingMinutes + "m";
}
+ public static int getMaxAchievements() {
+ JsonObject achievementData = getAchievementsResources().getAsJsonObject();
+ int total = 0;
+ for (String game : achievementData.keySet()) {
+ JsonObject gameData = achievementData.getAsJsonObject(game);
+ JsonObject oneTime = gameData.getAsJsonObject("one_time");
+ JsonObject tiered = gameData.getAsJsonObject("tiered");
+
+ for (String key : oneTime.keySet()) {
+ boolean isLegacy = getNestedJson(false, oneTime, key, "legacy").getAsBoolean();
+ if (!isLegacy) {
+ total++;
+ }
+ }
+
+ for (String key : tiered.keySet()) {
+ boolean isLegacy = getNestedJson(false, tiered, key, "legacy").getAsBoolean();
+ if (!isLegacy) {
+ total+= getNestedJson(tiered, key, "tiers").getAsJsonArray().size();
+ }
+ }
+ }
+ return total;
+ }
+
+ public static int getMaxAchievementPoints() {
+ JsonObject achievementData = getAchievementsResources().getAsJsonObject();
+ int total = 0;
+ for (String game : achievementData.keySet()) {
+ JsonObject gameData = achievementData.getAsJsonObject(game);
+
+ int totalPoints = getNestedJson(0, gameData, "total_points").getAsInt();
+ total += totalPoints;
+ }
+ return total;
+ }
+
+ public static Map autoCompleteAchGames() {
+ Map games = new HashMap<>();
+ JsonObject achievementData = getAchievementsResources().getAsJsonObject();
+ for (String game : achievementData.keySet()) {
+ games.put(game, toReadableName(game));
+ }
+ return games;
+ }
}
diff --git a/src/main/java/me/stuffy/stuffybot/utils/Verification.java b/src/main/java/me/stuffy/stuffybot/utils/Verification.java
new file mode 100644
index 0000000..fd110b4
--- /dev/null
+++ b/src/main/java/me/stuffy/stuffybot/utils/Verification.java
@@ -0,0 +1,174 @@
+package me.stuffy.stuffybot.utils;
+
+import me.stuffy.stuffybot.Bot;
+import me.stuffy.stuffybot.profiles.HypixelProfile;
+import net.dv8tion.jda.api.components.label.Label;
+import net.dv8tion.jda.api.entities.MessageEmbed;
+import net.dv8tion.jda.api.entities.User;
+import net.dv8tion.jda.api.events.interaction.ModalInteractionEvent;
+import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent;
+import net.dv8tion.jda.api.components.textinput.TextInput;
+import net.dv8tion.jda.api.components.textinput.TextInputStyle;
+import net.dv8tion.jda.api.modals.Modal;
+import net.dv8tion.jda.api.utils.messages.MessageCreateBuilder;
+import net.dv8tion.jda.api.utils.messages.MessageCreateData;
+
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+
+import static me.stuffy.stuffybot.utils.APIUtils.*;
+import static me.stuffy.stuffybot.utils.DiscordUtils.*;
+
+public class Verification {
+ private static final Map captchaTimeouts = new HashMap<>();
+
+ public static void verifyButton(ButtonInteractionEvent event) {
+ User user = event.getUser();
+ String userId = event.getUser().getId();
+
+ if(captchaTimeouts.containsKey(userId)){
+ if(captchaTimeouts.get(userId) > Instant.now().toEpochMilli()) {
+ MessageCreateData data = new MessageCreateBuilder()
+ .setEmbeds(makeErrorEmbed("Verification Error", "You have failed a CAPTCHA too recently. Please try again " + discordTimeUnix(captchaTimeouts.get(userId)) + ".")).build();
+ event.reply(data).setEphemeral(true).queue();
+ return;
+ }
+ else {
+ captchaTimeouts.remove(userId);
+ }
+ }
+
+ // #TODO if I am verified in linked db, update me according to info there
+ if(isVerified(userId)){
+ MessageCreateData data = new MessageCreateBuilder()
+ .setEmbeds(makeErrorEmbed("Verification Error", "You have already verified your identity, silly goose.")).build();
+ event.reply(data).setEphemeral(true).queue();
+ return;
+ }
+
+ TextInput ign = TextInput.create("ign", TextInputStyle.SHORT)
+ .setPlaceholder("Your Minecraft Username")
+ .setMaxLength(16)
+ .setMinLength(1)
+ .setRequired(true)
+ .build();
+
+ TextInput captcha = TextInput.create("captcha", TextInputStyle.PARAGRAPH)
+ .setPlaceholder("Enter the word 'stuffy'.\n" +
+ "To prevent abuse, failing the CAPTCHA " +
+ "will result in a short timeout.")
+ .setRequired(false)
+ .build();
+
+ Modal modal = Modal.create("verify", "Verify your identity in Stuffy Discord")
+ .addComponents(Label.of("Minecraft Username", ign), Label.of("CAPTCHA", captcha)).build();
+ event.replyModal(modal).queue();
+ }
+
+ public static boolean isVerified(String userID) {
+ ArrayList verifiedAccounts = Bot.getGlobalData().getVerifiedAccounts();
+ if (verifiedAccounts.contains(userID)) {
+ Logger.log("user " + userID + " is already verified");
+ return true;
+ }
+ Logger.log("user " + userID + " is not verified");
+ return false;
+ }
+
+ public static void verifyModal(ModalInteractionEvent event) {
+ String ign = Objects.requireNonNull(event.getValue("ign")).getAsString();
+ String captcha = Objects.requireNonNull(event.getValue("captcha")).getAsString();
+
+ if (!ign.matches("[a-zA-Z0-9_]+")) {
+ MessageEmbed errorEmbed = makeErrorEmbed("Verification Error", "Your Minecraft username may only contain letters, numbers, and underscores.");
+ MessageCreateData data = new MessageCreateBuilder()
+ .addEmbeds(errorEmbed)
+ .build();
+ event.reply(data).setEphemeral(true).queue();
+ return;
+ }
+
+ if (!captcha.equals("stuffy")) {
+ long timeout = Instant.now().plusSeconds(120).toEpochMilli();
+ captchaTimeouts.put(event.getUser().getId(), timeout);
+ MessageEmbed errorEmbed = makeErrorEmbed("Verification Error", "You entered the CAPTCHA incorrectly.\n-# Try again " + discordTimeUnix(timeout) + ".");
+ MessageCreateData data = new MessageCreateBuilder()
+ .addEmbeds(errorEmbed)
+ .build();
+ event.reply(data).setEphemeral(true).queue();
+ return;
+ }
+
+ HypixelProfile profile = null;
+ try {
+ profile = getHypixelProfile(ign);
+ } catch (APIException e) {
+ event.replyEmbeds(makeErrorEmbed("Hypixel API Error", e.errorMessage)).setEphemeral(true).queue();
+ return;
+ }
+ if ( profile == null) {
+ event.replyEmbeds(makeErrorEmbed("Hypixel API Error", "The user " + ign + " does not seem to exist. SpoOo00oky")).setEphemeral(true).queue();
+ return;
+ }
+
+ ign = profile.getDisplayName();
+
+ String linkedDiscord = null;
+ try {
+ linkedDiscord = profile.getDiscord();
+ } catch (Exception e) {
+ event.replyEmbeds(makeEmbedWithImage("Verification Error", "Check your linked discord in game","The user `" + ign + "` does not have a discord linked.\n\nFollow the steps below to correct this, and try again.", "https://i.imgur.com/HHs9nbZ.gif", 0xC95353)).setEphemeral(true).queue();
+ return;
+ }
+
+ String discordUsername = event.getUser().getName();
+ if(!linkedDiscord.equals(discordUsername)){
+ event.replyEmbeds(makeEmbedWithImage("Verification Error", "Check your linked discord in game", "The user `" + ign + "` has a different discord linked.\n In Game Linked Discord: `" + linkedDiscord + "`\n Your Discord Username: `" + discordUsername + "`\n\nFollow the steps below to correct this, and try again.", "https://i.imgur.com/HHs9nbZ.gif", 0xC95353)).setEphemeral(true).queue();
+ return;
+ }
+
+
+ // User is verified, whoop-whoop
+ updateLinkedDB(event.getUser().getId(), profile.getUuid(), ign);
+ setVerifiedStatus(event.getUser().getId(), true);
+
+ Bot bot = Bot.getInstance();
+ bot.getJDA().getGuildById(Config.getHomeGuildId()).addRoleToMember(event.getUser(), bot.getVerifiedRole()).queue();
+ bot.getJDA().getGuildById(Config.getHomeGuildId()).removeRoleFromMember(event.getUser(), bot.getNotVerifiedRole()).queue();
+
+ event.replyEmbeds(DiscordUtils.makeEmbed("Verification Successful", "You have been verified.", "You may now enjoy all of the perks that come with that. You may unverify at any time.", 0x3d84a2)).setEphemeral(true).queue();
+ }
+
+ public static void unverifyButton(ButtonInteractionEvent event) {
+ User user = event.getUser();
+ String userId = event.getUser().getId();
+ if(!isVerified(userId)){
+ MessageCreateData data = new MessageCreateBuilder()
+ .setEmbeds(makeErrorEmbed("Unverification Error", "You have not yet verified, there is nothing to undo!")).build();
+ event.reply(data).setEphemeral(true).queue();
+ return;
+ }
+
+ setVerifiedStatus(userId, false);
+
+ Bot bot = Bot.getInstance();
+ bot.getJDA().getGuildById(Config.getHomeGuildId()).removeRoleFromMember(event.getUser(), bot.getVerifiedRole()).queue();
+ bot.getJDA().getGuildById(Config.getHomeGuildId()).addRoleToMember(event.getUser(), bot.getNotVerifiedRole()).queue();
+
+ // TODO: Remove all roles that the player has already earned
+
+ MessageEmbed embed = DiscordUtils.makeEmbed("Unverify", "You have been unverified.", "You may verify again at any time.", 0x3d84a2);
+ event.replyEmbeds(embed).setEphemeral(true).queue();
+ }
+
+ public static void verifyUser(User user) {
+ // #TODO check if anyone already has the account verified, unverify them first
+ // #TODO add this username,uuid to verified in linked db
+ // #TODO remove unverified role, add verified role
+ // #TODO add all roles that the player has already earned
+ }
+
+}
diff --git a/src/main/resources/config.properties b/src/main/resources/config.properties
new file mode 100644
index 0000000..e737599
--- /dev/null
+++ b/src/main/resources/config.properties
@@ -0,0 +1,6 @@
+environment=development
+homeGuildId=795108903733952562
+verifiedRoleId=795118862940635216
+notVerifiedRoleId=1356442418354061503
+linkCommandId=1281037425032040491
+customStatus=working on something new
\ No newline at end of file
diff --git a/src/main/resources/data/mwclasses.json b/src/main/resources/data/mwclasses.json
index fe3e09e..f64e364 100644
--- a/src/main/resources/data/mwclasses.json
+++ b/src/main/resources/data/mwclasses.json
@@ -1,9 +1,9 @@
{
"lastUpdated": 0,
- "classes" : [
+ "classes": [
{
- "id" : "cow",
- "name" : "Cow",
+ "id": "cow",
+ "name": "Cow",
"skins": [
{
"name": "Moo Brawler",
@@ -625,8 +625,8 @@
]
},
{
- "id" : "skeleton",
- "name" : "Skeleton",
+ "id": "skeleton",
+ "name": "Skeleton",
"skins": [
{
"name": "Marksman",
diff --git a/src/main/resources/data/schemas/tournaments-schema.json b/src/main/resources/data/schemas/tournaments-schema.json
new file mode 100644
index 0000000..c35dabc
--- /dev/null
+++ b/src/main/resources/data/schemas/tournaments-schema.json
@@ -0,0 +1,133 @@
+{
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "title": "Tournaments Schema",
+ "description": "Schema for tournament data, including the name, iteration, duration, and data for navigating the api to find specific stats",
+ "type": "object",
+ "properties": {
+ "lastUpdated": {
+ "type": "integer",
+ "description": "Unix timestamp of the last time the data was updated"
+ },
+ "tournaments": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "description": "The id of the tournament, denoted by the index in which it happened"
+ },
+ "name": {
+ "type": "string",
+ "description": "The name of the tournament"
+ },
+ "icon": {
+ "type": "string",
+ "description": "The path to the icon of the tournament, located at https://hypixel.net/styles/hypixel-v2/images/game-icons/{icon}"
+ },
+ "iteration": {
+ "type": "integer",
+ "description": "The iteration of the tournament"
+ },
+ "duration": {
+ "type": "object",
+ "properties": {
+ "start": {
+ "type": "integer",
+ "description": "Unix timestamp of the start of the tournament"
+ },
+ "end": {
+ "type": "integer",
+ "description": "Unix timestamp of the end of the tournament"
+ }
+ },
+ "required": [
+ "start",
+ "end"
+ ]
+ },
+ "data": {
+ "type": "object",
+ "properties": {
+ "timeLimit": {
+ "type": "integer",
+ "description": "The maximum playtime for participants in minutes"
+ },
+ "gameLimit": {
+ "type": "integer",
+ "description": "The maximum amount of games a participant can play"
+ },
+ "tourneyField": {
+ "type": "string",
+ "description": "The field in the player's data that contains the tournament data"
+ },
+ "wins": {
+ "type": "string",
+ "description": "The field in the player's stats that contains the amount of wins"
+ },
+ "losses": {
+ "type": "string",
+ "description": "The field in the player's stats that contains the amount of losses"
+ },
+ "winstreak": {
+ "type": "string",
+ "description": "The field in the player's stats that contains the amount of wins in a row"
+ },
+ "kills": {
+ "type": "string",
+ "description": "The field in the player's stats that contains the amount of kills"
+ },
+ "deaths": {
+ "type": "string",
+ "description": "The field in the player's stats that contains the amount of deaths"
+ },
+ "killstreak": {
+ "type": "string",
+ "description": "The field in the player's stats that contains the amount of kills in a row"
+ },
+ "assists": {
+ "type": "string",
+ "description": "The field in the player's stats that contains the amount of assists"
+ },
+ "finalKills": {
+ "type": "string",
+ "description": "The field in the player's stats that contains the amount of final kills"
+ },
+ "finalDeaths": {
+ "type": "string",
+ "description": "The field in the player's stats that contains the amount of final deaths"
+ }
+ },
+ "oneOf": [
+ {
+ "required": [
+ "timeLimit"
+ ]
+ },
+ {
+ "required": [
+ "gameLimit"
+ ]
+ }
+ ],
+ "required": [
+ "tourneyField",
+ "wins"
+ ],
+ "additionalProperties": {
+ "type": "string"
+ }
+ }
+ },
+ "required": [
+ "id",
+ "name",
+ "icon",
+ "iteration",
+ "duration",
+ "data"
+ ]
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/resources/data/tournaments.json b/src/main/resources/data/tournaments.json
index cf9be7e..80724e3 100644
--- a/src/main/resources/data/tournaments.json
+++ b/src/main/resources/data/tournaments.json
@@ -1,4 +1,5 @@
{
+ "lastUpdated": 1725138732,
"tournaments": [
{
"id": 0,
@@ -10,16 +11,16 @@
"end": 1543186800
},
"data": {
- "timeLimit" : 1200,
- "tourneyField" : "bedwars4s_0",
- "wins" : "Bedwars.tourney_bedwars4s_0_wins_bedwars",
- "losses" : "Bedwars.tourney_bedwars4s_0_losses_bedwars",
- "finalKills" : "Bedwars.tourney_bedwars4s_0_final_kills_bedwars",
- "finalDeaths" : "Bedwars.tourney_bedwars4s_0_final_deaths_bedwars",
- "winstreak" : "Bedwars.tourney_bedwars4s_0_winstreak2",
- "killstreak" : "Bedwars.tourney_bedwars4s_0_killstreak_bedwars",
- "kills" : "Bedwars.tourney_bedwars4s_0_kills_bedwars",
- "deaths" : "Bedwars.tourney_bedwars4s_0_deaths_bedwars"
+ "timeLimit": 1200,
+ "tourneyField": "bedwars4s_0",
+ "wins": "Bedwars.tourney_bedwars4s_0_wins_bedwars",
+ "losses": "Bedwars.tourney_bedwars4s_0_losses_bedwars",
+ "finalKills": "Bedwars.tourney_bedwars4s_0_final_kills_bedwars",
+ "finalDeaths": "Bedwars.tourney_bedwars4s_0_final_deaths_bedwars",
+ "winstreak": "Bedwars.tourney_bedwars4s_0_winstreak2",
+ "killstreak": "Bedwars.tourney_bedwars4s_0_killstreak_bedwars",
+ "kills": "Bedwars.tourney_bedwars4s_0_kills_bedwars",
+ "deaths": "Bedwars.tourney_bedwars4s_0_deaths_bedwars"
}
},
{
@@ -32,11 +33,11 @@
"end": 1546815600
},
"data": {
- "timeLimit" : 720,
- "tourneyField" : "blitz_duo_0",
- "wins" : "HungerGames.tourney_blitz_duo_0_wins_teams",
- "kills" : "HungerGames.tourney_blitz_duo_0_kills",
- "deaths" : "HungerGames.tourney_blitz_duo_0_deaths"
+ "timeLimit": 720,
+ "tourneyField": "blitz_duo_0",
+ "wins": "HungerGames.tourney_blitz_duo_0_wins_teams",
+ "kills": "HungerGames.tourney_blitz_duo_0_kills",
+ "deaths": "HungerGames.tourney_blitz_duo_0_deaths"
}
},
{
@@ -49,14 +50,14 @@
"end": 1552860000
},
"data": {
- "timeLimit" : 480,
- "tourneyField" : "sw_crazy_solo_0",
- "wins" : "SkyWars.tourney_sw_crazy_solo_0_wins",
- "losses" : "SkyWars.tourney_sw_crazy_solo_0_losses",
- "winstreak" : "SkyWars.tourney_sw_crazy_solo_0_win_streak",
- "kills" : "SkyWars.tourney_sw_crazy_solo_0_kills",
- "deaths" : "SkyWars.tourney_sw_crazy_solo_0_deaths",
- "killstreak" : "SkyWars.tourney_sw_crazy_solo_0_killstreak"
+ "timeLimit": 480,
+ "tourneyField": "sw_crazy_solo_0",
+ "wins": "SkyWars.tourney_sw_crazy_solo_0_wins",
+ "losses": "SkyWars.tourney_sw_crazy_solo_0_losses",
+ "winstreak": "SkyWars.tourney_sw_crazy_solo_0_win_streak",
+ "kills": "SkyWars.tourney_sw_crazy_solo_0_kills",
+ "deaths": "SkyWars.tourney_sw_crazy_solo_0_deaths",
+ "killstreak": "SkyWars.tourney_sw_crazy_solo_0_killstreak"
}
},
{
@@ -69,11 +70,11 @@
"end": 1572213600
},
"data": {
- "timeLimit" : 480,
- "tourneyField" : "mcgo_defusal_0",
- "wins" : "MCGO.game_wins_tourney_mcgo_defusal_0",
- "kills" : "MCGO.kills_tourney_mcgo_defusal_0",
- "deaths" : "MCGO.deaths_tourney_mcgo_defusal_0"
+ "timeLimit": 480,
+ "tourneyField": "mcgo_defusal_0",
+ "wins": "MCGO.game_wins_tourney_mcgo_defusal_0",
+ "kills": "MCGO.kills_tourney_mcgo_defusal_0",
+ "deaths": "MCGO.deaths_tourney_mcgo_defusal_0"
}
},
{
@@ -86,16 +87,16 @@
"end": 1576450800
},
"data": {
- "gameLimit" : 75,
- "tourneyField" : "bedwars_two_four_0",
- "wins" : "Bedwars.tourney_bedwars_two_four_0_wins_bedwars",
- "losses" : "Bedwars.tourney_bedwars_two_four_0_losses_bedwars",
- "finalKills" : "Bedwars.tourney_bedwars_two_four_0_final_kills_bedwars",
- "finalDeaths" : "Bedwars.tourney_bedwars_two_four_0_final_deaths_bedwars",
- "winstreak" : "Bedwars.tourney_bedwars_two_four_0_winstreak2",
- "killstreak" : "Bedwars.tourney_bedwars_two_four_0_killstreak_bedwars",
- "kills" : "Bedwars.tourney_bedwars_two_four_0_kills_bedwars",
- "deaths" : "Bedwars.tourney_bedwars_two_four_0_deaths_bedwars"
+ "gameLimit": 75,
+ "tourneyField": "bedwars_two_four_0",
+ "wins": "Bedwars.tourney_bedwars_two_four_0_wins_bedwars",
+ "losses": "Bedwars.tourney_bedwars_two_four_0_losses_bedwars",
+ "finalKills": "Bedwars.tourney_bedwars_two_four_0_final_kills_bedwars",
+ "finalDeaths": "Bedwars.tourney_bedwars_two_four_0_final_deaths_bedwars",
+ "winstreak": "Bedwars.tourney_bedwars_two_four_0_winstreak2",
+ "killstreak": "Bedwars.tourney_bedwars_two_four_0_killstreak_bedwars",
+ "kills": "Bedwars.tourney_bedwars_two_four_0_kills_bedwars",
+ "deaths": "Bedwars.tourney_bedwars_two_four_0_deaths_bedwars"
}
},
{
@@ -108,11 +109,11 @@
"end": 1580684400
},
"data": {
- "gameLimit" : 250,
- "tourneyField" : "quake_solo2_0",
- "wins" : "Quake.wins_tourney_quake_solo2_1",
- "kills" : "Quake.kills_tourney_quake_solo2_1",
- "deaths" : "Quake.deaths_tourney_quake_solo2_1"
+ "gameLimit": 250,
+ "tourneyField": "quake_solo2_0",
+ "wins": "Quake.wins_tourney_quake_solo2_1",
+ "kills": "Quake.kills_tourney_quake_solo2_1",
+ "deaths": "Quake.deaths_tourney_quake_solo2_1"
}
},
{
@@ -125,14 +126,14 @@
"end": 1586124000
},
"data": {
- "gameLimit" : 120,
- "tourneyField" : "sw_insane_doubles_0",
- "wins" : "SkyWars.tourney_sw_insane_doubles_0_wins",
- "losses" : "SkyWars.tourney_sw_insane_doubles_0_losses",
- "winstreak" : "SkyWars.tourney_sw_insane_doubles_0_win_streak",
- "kills" : "SkyWars.tourney_sw_insane_doubles_0_kills",
- "deaths" : "SkyWars.tourney_sw_insane_doubles_0_deaths",
- "killstreak" : "SkyWars.tourney_sw_insane_doubles_0_killstreak"
+ "gameLimit": 120,
+ "tourneyField": "sw_insane_doubles_0",
+ "wins": "SkyWars.tourney_sw_insane_doubles_0_wins",
+ "losses": "SkyWars.tourney_sw_insane_doubles_0_losses",
+ "winstreak": "SkyWars.tourney_sw_insane_doubles_0_win_streak",
+ "kills": "SkyWars.tourney_sw_insane_doubles_0_kills",
+ "deaths": "SkyWars.tourney_sw_insane_doubles_0_deaths",
+ "killstreak": "SkyWars.tourney_sw_insane_doubles_0_killstreak"
}
},
{
@@ -145,16 +146,16 @@
"end": 1592172000
},
"data": {
- "gameLimit" : 80,
- "tourneyField" : "bedwars4s_1",
- "wins" : "Bedwars.tourney_bedwars4s_1_wins_bedwars",
- "losses" : "Bedwars.tourney_bedwars4s_1_losses_bedwars",
- "finalKills" : "Bedwars.tourney_bedwars4s_1_final_kills_bedwars",
- "finalDeaths" : "Bedwars.tourney_bedwars4s_1_final_deaths_bedwars",
- "winstreak" : "Bedwars.tourney_bedwars4s_1_winstreak2",
- "killstreak" : "Bedwars.tourney_bedwars4s_1_killstreak_bedwars",
- "kills" : "Bedwars.tourney_bedwars4s_1_kills_bedwars",
- "deaths" : "Bedwars.tourney_bedwars4s_1_deaths_bedwars"
+ "gameLimit": 80,
+ "tourneyField": "bedwars4s_1",
+ "wins": "Bedwars.tourney_bedwars4s_1_wins_bedwars",
+ "losses": "Bedwars.tourney_bedwars4s_1_losses_bedwars",
+ "finalKills": "Bedwars.tourney_bedwars4s_1_final_kills_bedwars",
+ "finalDeaths": "Bedwars.tourney_bedwars4s_1_final_deaths_bedwars",
+ "winstreak": "Bedwars.tourney_bedwars4s_1_winstreak2",
+ "killstreak": "Bedwars.tourney_bedwars4s_1_killstreak_bedwars",
+ "kills": "Bedwars.tourney_bedwars4s_1_kills_bedwars",
+ "deaths": "Bedwars.tourney_bedwars4s_1_deaths_bedwars"
}
},
{
@@ -167,9 +168,9 @@
"end": 1597010400
},
"data": {
- "gameLimit" : 72,
- "tourneyField" : "gingerbread_solo_0",
- "wins" : "GingerBread.tourney_gingerbread_solo_0_gold_trophy"
+ "gameLimit": 72,
+ "tourneyField": "gingerbread_solo_0",
+ "wins": "GingerBread.tourney_gingerbread_solo_0_gold_trophy"
}
},
{
@@ -182,11 +183,11 @@
"end": 1603058400
},
"data": {
- "gameLimit" : 70,
- "tourneyField" : "blitz_duo_1",
- "wins" : "HungerGames.tourney_blitz_duo_1_wins_teams",
- "kills" : "HungerGames.tourney_blitz_duo_1_kills",
- "deaths" : "HungerGames.tourney_blitz_duo_1_deaths"
+ "gameLimit": 70,
+ "tourneyField": "blitz_duo_1",
+ "wins": "HungerGames.tourney_blitz_duo_1_wins_teams",
+ "kills": "HungerGames.tourney_blitz_duo_1_kills",
+ "deaths": "HungerGames.tourney_blitz_duo_1_deaths"
}
},
{
@@ -199,11 +200,11 @@
"end": 1608505200
},
"data": {
- "gameLimit" : 60,
- "tourneyField" : "grinch_simulator_0",
- "wins" : "Arcade.wins_grinch_simulator_v2_tourney",
- "losses" : "Arcade.losses_grinch_simulator_v2_tourney",
- "grinch_gifts" : "Arcade.gifts_grinch_simulator_v2_tourney"
+ "gameLimit": 60,
+ "tourneyField": "grinch_simulator_0",
+ "wins": "Arcade.wins_grinch_simulator_v2_tourney",
+ "losses": "Arcade.losses_grinch_simulator_v2_tourney",
+ "grinch_gifts": "Arcade.gifts_grinch_simulator_v2_tourney"
}
},
{
@@ -216,11 +217,11 @@
"end": 1615158000
},
"data": {
- "gameLimit" : 40,
- "tourneyField" : "mcgo_defusal_1",
- "wins" : "MCGO.game_wins_tourney_mcgo_defusal_0",
- "kills" : "MCGO.kills_tourney_mcgo_defusal_0",
- "deaths" : "MCGO.deaths_tourney_mcgo_defusal_0"
+ "gameLimit": 40,
+ "tourneyField": "mcgo_defusal_1",
+ "wins": "MCGO.game_wins_tourney_mcgo_defusal_0",
+ "kills": "MCGO.kills_tourney_mcgo_defusal_0",
+ "deaths": "MCGO.deaths_tourney_mcgo_defusal_0"
}
},
{
@@ -233,10 +234,10 @@
"end": 1629064800
},
"data": {
- "gameLimit" : 120,
- "tourneyField" : "tnt_run_0",
- "wins" : "TNTGames.wins_tourney_tnt_run_0",
- "losses" : "TNTGames.deaths_tourney_tnt_run_0"
+ "gameLimit": 120,
+ "tourneyField": "tnt_run_0",
+ "wins": "TNTGames.wins_tourney_tnt_run_0",
+ "losses": "TNTGames.deaths_tourney_tnt_run_0"
}
},
{
@@ -249,14 +250,14 @@
"end": 1634508000
},
"data": {
- "gameLimit" : 120,
- "tourneyField" : "sw_insane_doubles_1",
- "wins" : "SkyWars.tourney_sw_insane_doubles_1_wins",
- "losses" : "SkyWars.tourney_sw_insane_doubles_1_losses",
- "winstreak" : "SkyWars.tourney_sw_insane_doubles_1_win_streak",
- "kills" : "SkyWars.tourney_sw_insane_doubles_1_kills",
- "deaths" : "SkyWars.tourney_sw_insane_doubles_1_deaths",
- "killstreak" : "SkyWars.tourney_sw_insane_doubles_1_killstreak"
+ "gameLimit": 120,
+ "tourneyField": "sw_insane_doubles_1",
+ "wins": "SkyWars.tourney_sw_insane_doubles_1_wins",
+ "losses": "SkyWars.tourney_sw_insane_doubles_1_losses",
+ "winstreak": "SkyWars.tourney_sw_insane_doubles_1_win_streak",
+ "kills": "SkyWars.tourney_sw_insane_doubles_1_kills",
+ "deaths": "SkyWars.tourney_sw_insane_doubles_1_deaths",
+ "killstreak": "SkyWars.tourney_sw_insane_doubles_1_killstreak"
}
},
{
@@ -269,11 +270,11 @@
"end": 1639954800
},
"data": {
- "gameLimit" : 200,
- "tourneyField" : "quake_solo2_1",
- "wins" : "Quake.wins_tourney_quake_solo2_1",
- "kills" : "Quake.kills_tourney_quake_solo2_1",
- "deaths" : "Quake.deaths_tourney_quake_solo2_1"
+ "gameLimit": 200,
+ "tourneyField": "quake_solo2_1",
+ "wins": "Quake.wins_tourney_quake_solo2_1",
+ "kills": "Quake.kills_tourney_quake_solo2_1",
+ "deaths": "Quake.deaths_tourney_quake_solo2_1"
}
},
{
@@ -286,16 +287,16 @@
"end": 1647813600
},
"data": {
- "gameLimit" : 40,
- "tourneyField" : "bedwars_two_four_0",
- "wins" : "Bedwars.tourney_bedwars_two_four_0_wins_bedwars",
- "losses" : "Bedwars.tourney_bedwars_two_four_0_losses_bedwars",
- "finalKills" : "Bedwars.tourney_bedwars_two_four_0_final_kills_bedwars",
- "finalDeaths" : "Bedwars.tourney_bedwars_two_four_0_final_deaths_bedwars",
- "winstreak" : "Bedwars.tourney_bedwars_two_four_0_winstreak2",
- "killstreak" : "Bedwars.tourney_bedwars_two_four_0_killstreak_bedwars",
- "kills" : "Bedwars.tourney_bedwars_two_four_0_kills_bedwars",
- "deaths" : "Bedwars.tourney_bedwars_two_four_0_deaths_bedwars"
+ "gameLimit": 40,
+ "tourneyField": "bedwars_two_four_0",
+ "wins": "Bedwars.tourney_bedwars_two_four_0_wins_bedwars",
+ "losses": "Bedwars.tourney_bedwars_two_four_0_losses_bedwars",
+ "finalKills": "Bedwars.tourney_bedwars_two_four_0_final_kills_bedwars",
+ "finalDeaths": "Bedwars.tourney_bedwars_two_four_0_final_deaths_bedwars",
+ "winstreak": "Bedwars.tourney_bedwars_two_four_0_winstreak2",
+ "killstreak": "Bedwars.tourney_bedwars_two_four_0_killstreak_bedwars",
+ "kills": "Bedwars.tourney_bedwars_two_four_0_kills_bedwars",
+ "deaths": "Bedwars.tourney_bedwars_two_four_0_deaths_bedwars"
}
},
{
@@ -308,12 +309,12 @@
"end": 1660514400
},
"data": {
- "gameLimit" : 50,
- "tourneyField" : "mini_walls_0",
- "wins" : "Arcade.wins_tourney_mini_walls_0",
- "deaths" : "Arcade.deaths_tourney_mini_walls_0",
- "kills" : "Arcade.kills_tourney_mini_walls_0",
- "finalKills" : "Arcade.final_kills_tourney_mini_walls_0"
+ "gameLimit": 50,
+ "tourneyField": "mini_walls_0",
+ "wins": "Arcade.wins_tourney_mini_walls_0",
+ "deaths": "Arcade.deaths_tourney_mini_walls_0",
+ "kills": "Arcade.kills_tourney_mini_walls_0",
+ "finalKills": "Arcade.final_kills_tourney_mini_walls_0"
}
},
{
@@ -326,9 +327,9 @@
"end": 1667167200
},
"data": {
- "gameLimit" : 72,
- "tourneyField" : "gingerbread_solo_1",
- "wins" : "GingerBread.tourney_gingerbread_solo_1_gold_trophy"
+ "gameLimit": 72,
+ "tourneyField": "gingerbread_solo_1",
+ "wins": "GingerBread.tourney_gingerbread_solo_1_gold_trophy"
}
},
{
@@ -341,11 +342,11 @@
"end": 1679868000
},
"data": {
- "gameLimit" : 70,
- "tourneyField" : "blitz_duo_2",
- "wins" : "HungerGames.tourney_blitz_duo_2_wins_teams",
- "kills" : "HungerGames.tourney_blitz_duo_2_kills",
- "deaths" : "HungerGames.tourney_blitz_duo_2_deaths"
+ "gameLimit": 70,
+ "tourneyField": "blitz_duo_2",
+ "wins": "HungerGames.tourney_blitz_duo_2_wins_teams",
+ "kills": "HungerGames.tourney_blitz_duo_2_kills",
+ "deaths": "HungerGames.tourney_blitz_duo_2_deaths"
}
},
{
@@ -358,11 +359,11 @@
"end": 1693163760
},
"data": {
- "gameLimit" : 100,
- "tourneyField" : "wool_wars_0",
- "wins" : "WoolGames.wool_wars.stats.tourney.wool_wars_0.wins",
- "kills" : "WoolGames.wool_wars.stats.tourney.wool_wars_0.kills",
- "deaths" : "WoolGames.wool_wars.stats.tourney.wool_wars_0.deaths"
+ "gameLimit": 100,
+ "tourneyField": "wool_wars_0",
+ "wins": "WoolGames.wool_wars.stats.tourney.wool_wars_0.wins",
+ "kills": "WoolGames.wool_wars.stats.tourney.wool_wars_0.kills",
+ "deaths": "WoolGames.wool_wars.stats.tourney.wool_wars_0.deaths"
}
},
{
@@ -375,11 +376,11 @@
"end": 1694383200
},
"data": {
- "gameLimit" : 40,
- "tourneyField" : "wool_wars_1",
- "wins" : "WoolGames.wool_wars.stats.tourney.wool_wars_1.wins",
- "kills" : "WoolGames.wool_wars.stats.tourney.wool_wars_1.kills",
- "deaths" : "WoolGames.wool_wars.stats.tourney.wool_wars_1.deaths"
+ "gameLimit": 40,
+ "tourneyField": "wool_wars_1",
+ "wins": "WoolGames.wool_wars.stats.tourney.wool_wars_1.wins",
+ "kills": "WoolGames.wool_wars.stats.tourney.wool_wars_1.kills",
+ "deaths": "WoolGames.wool_wars.stats.tourney.wool_wars_1.deaths"
}
},
{
@@ -392,16 +393,16 @@
"end": 1698012000
},
"data": {
- "gameLimit" : 40,
- "tourneyField" : "bedwars_two_four_1",
- "wins" : "Bedwars.tourney_bedwars_two_four_1_wins_bedwars",
- "losses" : "Bedwars.tourney_bedwars_two_four_1_losses_bedwars",
- "finalKills" : "Bedwars.tourney_bedwars_two_four_1_final_kills_bedwars",
- "finalDeaths" : "Bedwars.tourney_bedwars_two_four_1_final_deaths_bedwars",
- "winstreak" : "Bedwars.tourney_bedwars_two_four_1_winstreak2",
- "killstreak" : "Bedwars.tourney_bedwars_two_four_1_killstreak_bedwars",
- "kills" : "Bedwars.tourney_bedwars_two_four_1_kills_bedwars",
- "deaths" : "Bedwars.tourney_bedwars_two_four_1_deaths_bedwars"
+ "gameLimit": 40,
+ "tourneyField": "bedwars_two_four_1",
+ "wins": "Bedwars.tourney_bedwars_two_four_1_wins_bedwars",
+ "losses": "Bedwars.tourney_bedwars_two_four_1_losses_bedwars",
+ "finalKills": "Bedwars.tourney_bedwars_two_four_1_final_kills_bedwars",
+ "finalDeaths": "Bedwars.tourney_bedwars_two_four_1_final_deaths_bedwars",
+ "winstreak": "Bedwars.tourney_bedwars_two_four_1_winstreak2",
+ "killstreak": "Bedwars.tourney_bedwars_two_four_1_killstreak_bedwars",
+ "kills": "Bedwars.tourney_bedwars_two_four_1_kills_bedwars",
+ "deaths": "Bedwars.tourney_bedwars_two_four_1_deaths_bedwars"
}
},
{
@@ -414,11 +415,11 @@
"end": 1702854000
},
"data": {
- "gameLimit" : 60,
- "tourneyField" : "grinch_simulator_1",
- "wins" : "Arcade.wins_grinch_simulator_v2_tourney_grinch_simulator_1",
- "losses" : "Arcade.losses_grinch_simulator_v2_tourney_grinch_simulator_1",
- "grinch_gifts" : "Arcade.gifts_grinch_simulator_v2_tourney_grinch_simulator_1"
+ "gameLimit": 60,
+ "tourneyField": "grinch_simulator_1",
+ "wins": "Arcade.wins_grinch_simulator_v2_tourney_grinch_simulator_1",
+ "losses": "Arcade.losses_grinch_simulator_v2_tourney_grinch_simulator_1",
+ "grinch_gifts": "Arcade.gifts_grinch_simulator_v2_tourney_grinch_simulator_1"
}
},
{
@@ -431,10 +432,10 @@
"end": 1710712800
},
"data": {
- "gameLimit" : 80,
- "tourneyField" : "tnt_run_1",
- "wins" : "TNTGames.wins_tourney_tnt_run_1",
- "losses" : "TNTGames.deaths_tourney_tnt_run_1"
+ "gameLimit": 80,
+ "tourneyField": "tnt_run_1",
+ "wins": "TNTGames.wins_tourney_tnt_run_1",
+ "losses": "TNTGames.deaths_tourney_tnt_run_1"
}
},
{
@@ -447,15 +448,15 @@
"end": 1719180000
},
"data": {
- "gameLimit" : 120,
- "tourneyField" : "sw_normal_doubles_0",
- "wins" : "SkyWars.tourney_sw_normal_doubles_0_wins",
- "losses" : "SkyWars.tourney_sw_normal_doubles_0_losses",
- "kills" : "SkyWars.tourney_sw_normal_doubles_0_kills",
- "deaths" : "SkyWars.tourney_sw_normal_doubles_0_deaths",
- "assists" : "SkyWars.tourney_sw_normal_doubles_0_assists",
- "winstreak" : "SkyWars.tourney_sw_normal_doubles_0_win_streak",
- "killstreak" : "SkyWars.tourney_sw_normal_doubles_0_killstreak"
+ "gameLimit": 120,
+ "tourneyField": "sw_normal_doubles_0",
+ "wins": "SkyWars.tourney_sw_normal_doubles_0_wins",
+ "losses": "SkyWars.tourney_sw_normal_doubles_0_losses",
+ "kills": "SkyWars.tourney_sw_normal_doubles_0_kills",
+ "deaths": "SkyWars.tourney_sw_normal_doubles_0_deaths",
+ "assists": "SkyWars.tourney_sw_normal_doubles_0_assists",
+ "winstreak": "SkyWars.tourney_sw_normal_doubles_0_win_streak",
+ "killstreak": "SkyWars.tourney_sw_normal_doubles_0_killstreak"
}
}
]
diff --git a/target/classes/data/mwclasses.json b/target/classes/data/mwclasses.json
index fe3e09e..f64e364 100644
--- a/target/classes/data/mwclasses.json
+++ b/target/classes/data/mwclasses.json
@@ -1,9 +1,9 @@
{
"lastUpdated": 0,
- "classes" : [
+ "classes": [
{
- "id" : "cow",
- "name" : "Cow",
+ "id": "cow",
+ "name": "Cow",
"skins": [
{
"name": "Moo Brawler",
@@ -625,8 +625,8 @@
]
},
{
- "id" : "skeleton",
- "name" : "Skeleton",
+ "id": "skeleton",
+ "name": "Skeleton",
"skins": [
{
"name": "Marksman",
diff --git a/target/classes/data/schemas/tournaments-schema.json b/target/classes/data/schemas/tournaments-schema.json
new file mode 100644
index 0000000..c35dabc
--- /dev/null
+++ b/target/classes/data/schemas/tournaments-schema.json
@@ -0,0 +1,133 @@
+{
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "title": "Tournaments Schema",
+ "description": "Schema for tournament data, including the name, iteration, duration, and data for navigating the api to find specific stats",
+ "type": "object",
+ "properties": {
+ "lastUpdated": {
+ "type": "integer",
+ "description": "Unix timestamp of the last time the data was updated"
+ },
+ "tournaments": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "integer",
+ "description": "The id of the tournament, denoted by the index in which it happened"
+ },
+ "name": {
+ "type": "string",
+ "description": "The name of the tournament"
+ },
+ "icon": {
+ "type": "string",
+ "description": "The path to the icon of the tournament, located at https://hypixel.net/styles/hypixel-v2/images/game-icons/{icon}"
+ },
+ "iteration": {
+ "type": "integer",
+ "description": "The iteration of the tournament"
+ },
+ "duration": {
+ "type": "object",
+ "properties": {
+ "start": {
+ "type": "integer",
+ "description": "Unix timestamp of the start of the tournament"
+ },
+ "end": {
+ "type": "integer",
+ "description": "Unix timestamp of the end of the tournament"
+ }
+ },
+ "required": [
+ "start",
+ "end"
+ ]
+ },
+ "data": {
+ "type": "object",
+ "properties": {
+ "timeLimit": {
+ "type": "integer",
+ "description": "The maximum playtime for participants in minutes"
+ },
+ "gameLimit": {
+ "type": "integer",
+ "description": "The maximum amount of games a participant can play"
+ },
+ "tourneyField": {
+ "type": "string",
+ "description": "The field in the player's data that contains the tournament data"
+ },
+ "wins": {
+ "type": "string",
+ "description": "The field in the player's stats that contains the amount of wins"
+ },
+ "losses": {
+ "type": "string",
+ "description": "The field in the player's stats that contains the amount of losses"
+ },
+ "winstreak": {
+ "type": "string",
+ "description": "The field in the player's stats that contains the amount of wins in a row"
+ },
+ "kills": {
+ "type": "string",
+ "description": "The field in the player's stats that contains the amount of kills"
+ },
+ "deaths": {
+ "type": "string",
+ "description": "The field in the player's stats that contains the amount of deaths"
+ },
+ "killstreak": {
+ "type": "string",
+ "description": "The field in the player's stats that contains the amount of kills in a row"
+ },
+ "assists": {
+ "type": "string",
+ "description": "The field in the player's stats that contains the amount of assists"
+ },
+ "finalKills": {
+ "type": "string",
+ "description": "The field in the player's stats that contains the amount of final kills"
+ },
+ "finalDeaths": {
+ "type": "string",
+ "description": "The field in the player's stats that contains the amount of final deaths"
+ }
+ },
+ "oneOf": [
+ {
+ "required": [
+ "timeLimit"
+ ]
+ },
+ {
+ "required": [
+ "gameLimit"
+ ]
+ }
+ ],
+ "required": [
+ "tourneyField",
+ "wins"
+ ],
+ "additionalProperties": {
+ "type": "string"
+ }
+ }
+ },
+ "required": [
+ "id",
+ "name",
+ "icon",
+ "iteration",
+ "duration",
+ "data"
+ ]
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/target/classes/data/tournaments.json b/target/classes/data/tournaments.json
index cf9be7e..80724e3 100644
--- a/target/classes/data/tournaments.json
+++ b/target/classes/data/tournaments.json
@@ -1,4 +1,5 @@
{
+ "lastUpdated": 1725138732,
"tournaments": [
{
"id": 0,
@@ -10,16 +11,16 @@
"end": 1543186800
},
"data": {
- "timeLimit" : 1200,
- "tourneyField" : "bedwars4s_0",
- "wins" : "Bedwars.tourney_bedwars4s_0_wins_bedwars",
- "losses" : "Bedwars.tourney_bedwars4s_0_losses_bedwars",
- "finalKills" : "Bedwars.tourney_bedwars4s_0_final_kills_bedwars",
- "finalDeaths" : "Bedwars.tourney_bedwars4s_0_final_deaths_bedwars",
- "winstreak" : "Bedwars.tourney_bedwars4s_0_winstreak2",
- "killstreak" : "Bedwars.tourney_bedwars4s_0_killstreak_bedwars",
- "kills" : "Bedwars.tourney_bedwars4s_0_kills_bedwars",
- "deaths" : "Bedwars.tourney_bedwars4s_0_deaths_bedwars"
+ "timeLimit": 1200,
+ "tourneyField": "bedwars4s_0",
+ "wins": "Bedwars.tourney_bedwars4s_0_wins_bedwars",
+ "losses": "Bedwars.tourney_bedwars4s_0_losses_bedwars",
+ "finalKills": "Bedwars.tourney_bedwars4s_0_final_kills_bedwars",
+ "finalDeaths": "Bedwars.tourney_bedwars4s_0_final_deaths_bedwars",
+ "winstreak": "Bedwars.tourney_bedwars4s_0_winstreak2",
+ "killstreak": "Bedwars.tourney_bedwars4s_0_killstreak_bedwars",
+ "kills": "Bedwars.tourney_bedwars4s_0_kills_bedwars",
+ "deaths": "Bedwars.tourney_bedwars4s_0_deaths_bedwars"
}
},
{
@@ -32,11 +33,11 @@
"end": 1546815600
},
"data": {
- "timeLimit" : 720,
- "tourneyField" : "blitz_duo_0",
- "wins" : "HungerGames.tourney_blitz_duo_0_wins_teams",
- "kills" : "HungerGames.tourney_blitz_duo_0_kills",
- "deaths" : "HungerGames.tourney_blitz_duo_0_deaths"
+ "timeLimit": 720,
+ "tourneyField": "blitz_duo_0",
+ "wins": "HungerGames.tourney_blitz_duo_0_wins_teams",
+ "kills": "HungerGames.tourney_blitz_duo_0_kills",
+ "deaths": "HungerGames.tourney_blitz_duo_0_deaths"
}
},
{
@@ -49,14 +50,14 @@
"end": 1552860000
},
"data": {
- "timeLimit" : 480,
- "tourneyField" : "sw_crazy_solo_0",
- "wins" : "SkyWars.tourney_sw_crazy_solo_0_wins",
- "losses" : "SkyWars.tourney_sw_crazy_solo_0_losses",
- "winstreak" : "SkyWars.tourney_sw_crazy_solo_0_win_streak",
- "kills" : "SkyWars.tourney_sw_crazy_solo_0_kills",
- "deaths" : "SkyWars.tourney_sw_crazy_solo_0_deaths",
- "killstreak" : "SkyWars.tourney_sw_crazy_solo_0_killstreak"
+ "timeLimit": 480,
+ "tourneyField": "sw_crazy_solo_0",
+ "wins": "SkyWars.tourney_sw_crazy_solo_0_wins",
+ "losses": "SkyWars.tourney_sw_crazy_solo_0_losses",
+ "winstreak": "SkyWars.tourney_sw_crazy_solo_0_win_streak",
+ "kills": "SkyWars.tourney_sw_crazy_solo_0_kills",
+ "deaths": "SkyWars.tourney_sw_crazy_solo_0_deaths",
+ "killstreak": "SkyWars.tourney_sw_crazy_solo_0_killstreak"
}
},
{
@@ -69,11 +70,11 @@
"end": 1572213600
},
"data": {
- "timeLimit" : 480,
- "tourneyField" : "mcgo_defusal_0",
- "wins" : "MCGO.game_wins_tourney_mcgo_defusal_0",
- "kills" : "MCGO.kills_tourney_mcgo_defusal_0",
- "deaths" : "MCGO.deaths_tourney_mcgo_defusal_0"
+ "timeLimit": 480,
+ "tourneyField": "mcgo_defusal_0",
+ "wins": "MCGO.game_wins_tourney_mcgo_defusal_0",
+ "kills": "MCGO.kills_tourney_mcgo_defusal_0",
+ "deaths": "MCGO.deaths_tourney_mcgo_defusal_0"
}
},
{
@@ -86,16 +87,16 @@
"end": 1576450800
},
"data": {
- "gameLimit" : 75,
- "tourneyField" : "bedwars_two_four_0",
- "wins" : "Bedwars.tourney_bedwars_two_four_0_wins_bedwars",
- "losses" : "Bedwars.tourney_bedwars_two_four_0_losses_bedwars",
- "finalKills" : "Bedwars.tourney_bedwars_two_four_0_final_kills_bedwars",
- "finalDeaths" : "Bedwars.tourney_bedwars_two_four_0_final_deaths_bedwars",
- "winstreak" : "Bedwars.tourney_bedwars_two_four_0_winstreak2",
- "killstreak" : "Bedwars.tourney_bedwars_two_four_0_killstreak_bedwars",
- "kills" : "Bedwars.tourney_bedwars_two_four_0_kills_bedwars",
- "deaths" : "Bedwars.tourney_bedwars_two_four_0_deaths_bedwars"
+ "gameLimit": 75,
+ "tourneyField": "bedwars_two_four_0",
+ "wins": "Bedwars.tourney_bedwars_two_four_0_wins_bedwars",
+ "losses": "Bedwars.tourney_bedwars_two_four_0_losses_bedwars",
+ "finalKills": "Bedwars.tourney_bedwars_two_four_0_final_kills_bedwars",
+ "finalDeaths": "Bedwars.tourney_bedwars_two_four_0_final_deaths_bedwars",
+ "winstreak": "Bedwars.tourney_bedwars_two_four_0_winstreak2",
+ "killstreak": "Bedwars.tourney_bedwars_two_four_0_killstreak_bedwars",
+ "kills": "Bedwars.tourney_bedwars_two_four_0_kills_bedwars",
+ "deaths": "Bedwars.tourney_bedwars_two_four_0_deaths_bedwars"
}
},
{
@@ -108,11 +109,11 @@
"end": 1580684400
},
"data": {
- "gameLimit" : 250,
- "tourneyField" : "quake_solo2_0",
- "wins" : "Quake.wins_tourney_quake_solo2_1",
- "kills" : "Quake.kills_tourney_quake_solo2_1",
- "deaths" : "Quake.deaths_tourney_quake_solo2_1"
+ "gameLimit": 250,
+ "tourneyField": "quake_solo2_0",
+ "wins": "Quake.wins_tourney_quake_solo2_1",
+ "kills": "Quake.kills_tourney_quake_solo2_1",
+ "deaths": "Quake.deaths_tourney_quake_solo2_1"
}
},
{
@@ -125,14 +126,14 @@
"end": 1586124000
},
"data": {
- "gameLimit" : 120,
- "tourneyField" : "sw_insane_doubles_0",
- "wins" : "SkyWars.tourney_sw_insane_doubles_0_wins",
- "losses" : "SkyWars.tourney_sw_insane_doubles_0_losses",
- "winstreak" : "SkyWars.tourney_sw_insane_doubles_0_win_streak",
- "kills" : "SkyWars.tourney_sw_insane_doubles_0_kills",
- "deaths" : "SkyWars.tourney_sw_insane_doubles_0_deaths",
- "killstreak" : "SkyWars.tourney_sw_insane_doubles_0_killstreak"
+ "gameLimit": 120,
+ "tourneyField": "sw_insane_doubles_0",
+ "wins": "SkyWars.tourney_sw_insane_doubles_0_wins",
+ "losses": "SkyWars.tourney_sw_insane_doubles_0_losses",
+ "winstreak": "SkyWars.tourney_sw_insane_doubles_0_win_streak",
+ "kills": "SkyWars.tourney_sw_insane_doubles_0_kills",
+ "deaths": "SkyWars.tourney_sw_insane_doubles_0_deaths",
+ "killstreak": "SkyWars.tourney_sw_insane_doubles_0_killstreak"
}
},
{
@@ -145,16 +146,16 @@
"end": 1592172000
},
"data": {
- "gameLimit" : 80,
- "tourneyField" : "bedwars4s_1",
- "wins" : "Bedwars.tourney_bedwars4s_1_wins_bedwars",
- "losses" : "Bedwars.tourney_bedwars4s_1_losses_bedwars",
- "finalKills" : "Bedwars.tourney_bedwars4s_1_final_kills_bedwars",
- "finalDeaths" : "Bedwars.tourney_bedwars4s_1_final_deaths_bedwars",
- "winstreak" : "Bedwars.tourney_bedwars4s_1_winstreak2",
- "killstreak" : "Bedwars.tourney_bedwars4s_1_killstreak_bedwars",
- "kills" : "Bedwars.tourney_bedwars4s_1_kills_bedwars",
- "deaths" : "Bedwars.tourney_bedwars4s_1_deaths_bedwars"
+ "gameLimit": 80,
+ "tourneyField": "bedwars4s_1",
+ "wins": "Bedwars.tourney_bedwars4s_1_wins_bedwars",
+ "losses": "Bedwars.tourney_bedwars4s_1_losses_bedwars",
+ "finalKills": "Bedwars.tourney_bedwars4s_1_final_kills_bedwars",
+ "finalDeaths": "Bedwars.tourney_bedwars4s_1_final_deaths_bedwars",
+ "winstreak": "Bedwars.tourney_bedwars4s_1_winstreak2",
+ "killstreak": "Bedwars.tourney_bedwars4s_1_killstreak_bedwars",
+ "kills": "Bedwars.tourney_bedwars4s_1_kills_bedwars",
+ "deaths": "Bedwars.tourney_bedwars4s_1_deaths_bedwars"
}
},
{
@@ -167,9 +168,9 @@
"end": 1597010400
},
"data": {
- "gameLimit" : 72,
- "tourneyField" : "gingerbread_solo_0",
- "wins" : "GingerBread.tourney_gingerbread_solo_0_gold_trophy"
+ "gameLimit": 72,
+ "tourneyField": "gingerbread_solo_0",
+ "wins": "GingerBread.tourney_gingerbread_solo_0_gold_trophy"
}
},
{
@@ -182,11 +183,11 @@
"end": 1603058400
},
"data": {
- "gameLimit" : 70,
- "tourneyField" : "blitz_duo_1",
- "wins" : "HungerGames.tourney_blitz_duo_1_wins_teams",
- "kills" : "HungerGames.tourney_blitz_duo_1_kills",
- "deaths" : "HungerGames.tourney_blitz_duo_1_deaths"
+ "gameLimit": 70,
+ "tourneyField": "blitz_duo_1",
+ "wins": "HungerGames.tourney_blitz_duo_1_wins_teams",
+ "kills": "HungerGames.tourney_blitz_duo_1_kills",
+ "deaths": "HungerGames.tourney_blitz_duo_1_deaths"
}
},
{
@@ -199,11 +200,11 @@
"end": 1608505200
},
"data": {
- "gameLimit" : 60,
- "tourneyField" : "grinch_simulator_0",
- "wins" : "Arcade.wins_grinch_simulator_v2_tourney",
- "losses" : "Arcade.losses_grinch_simulator_v2_tourney",
- "grinch_gifts" : "Arcade.gifts_grinch_simulator_v2_tourney"
+ "gameLimit": 60,
+ "tourneyField": "grinch_simulator_0",
+ "wins": "Arcade.wins_grinch_simulator_v2_tourney",
+ "losses": "Arcade.losses_grinch_simulator_v2_tourney",
+ "grinch_gifts": "Arcade.gifts_grinch_simulator_v2_tourney"
}
},
{
@@ -216,11 +217,11 @@
"end": 1615158000
},
"data": {
- "gameLimit" : 40,
- "tourneyField" : "mcgo_defusal_1",
- "wins" : "MCGO.game_wins_tourney_mcgo_defusal_0",
- "kills" : "MCGO.kills_tourney_mcgo_defusal_0",
- "deaths" : "MCGO.deaths_tourney_mcgo_defusal_0"
+ "gameLimit": 40,
+ "tourneyField": "mcgo_defusal_1",
+ "wins": "MCGO.game_wins_tourney_mcgo_defusal_0",
+ "kills": "MCGO.kills_tourney_mcgo_defusal_0",
+ "deaths": "MCGO.deaths_tourney_mcgo_defusal_0"
}
},
{
@@ -233,10 +234,10 @@
"end": 1629064800
},
"data": {
- "gameLimit" : 120,
- "tourneyField" : "tnt_run_0",
- "wins" : "TNTGames.wins_tourney_tnt_run_0",
- "losses" : "TNTGames.deaths_tourney_tnt_run_0"
+ "gameLimit": 120,
+ "tourneyField": "tnt_run_0",
+ "wins": "TNTGames.wins_tourney_tnt_run_0",
+ "losses": "TNTGames.deaths_tourney_tnt_run_0"
}
},
{
@@ -249,14 +250,14 @@
"end": 1634508000
},
"data": {
- "gameLimit" : 120,
- "tourneyField" : "sw_insane_doubles_1",
- "wins" : "SkyWars.tourney_sw_insane_doubles_1_wins",
- "losses" : "SkyWars.tourney_sw_insane_doubles_1_losses",
- "winstreak" : "SkyWars.tourney_sw_insane_doubles_1_win_streak",
- "kills" : "SkyWars.tourney_sw_insane_doubles_1_kills",
- "deaths" : "SkyWars.tourney_sw_insane_doubles_1_deaths",
- "killstreak" : "SkyWars.tourney_sw_insane_doubles_1_killstreak"
+ "gameLimit": 120,
+ "tourneyField": "sw_insane_doubles_1",
+ "wins": "SkyWars.tourney_sw_insane_doubles_1_wins",
+ "losses": "SkyWars.tourney_sw_insane_doubles_1_losses",
+ "winstreak": "SkyWars.tourney_sw_insane_doubles_1_win_streak",
+ "kills": "SkyWars.tourney_sw_insane_doubles_1_kills",
+ "deaths": "SkyWars.tourney_sw_insane_doubles_1_deaths",
+ "killstreak": "SkyWars.tourney_sw_insane_doubles_1_killstreak"
}
},
{
@@ -269,11 +270,11 @@
"end": 1639954800
},
"data": {
- "gameLimit" : 200,
- "tourneyField" : "quake_solo2_1",
- "wins" : "Quake.wins_tourney_quake_solo2_1",
- "kills" : "Quake.kills_tourney_quake_solo2_1",
- "deaths" : "Quake.deaths_tourney_quake_solo2_1"
+ "gameLimit": 200,
+ "tourneyField": "quake_solo2_1",
+ "wins": "Quake.wins_tourney_quake_solo2_1",
+ "kills": "Quake.kills_tourney_quake_solo2_1",
+ "deaths": "Quake.deaths_tourney_quake_solo2_1"
}
},
{
@@ -286,16 +287,16 @@
"end": 1647813600
},
"data": {
- "gameLimit" : 40,
- "tourneyField" : "bedwars_two_four_0",
- "wins" : "Bedwars.tourney_bedwars_two_four_0_wins_bedwars",
- "losses" : "Bedwars.tourney_bedwars_two_four_0_losses_bedwars",
- "finalKills" : "Bedwars.tourney_bedwars_two_four_0_final_kills_bedwars",
- "finalDeaths" : "Bedwars.tourney_bedwars_two_four_0_final_deaths_bedwars",
- "winstreak" : "Bedwars.tourney_bedwars_two_four_0_winstreak2",
- "killstreak" : "Bedwars.tourney_bedwars_two_four_0_killstreak_bedwars",
- "kills" : "Bedwars.tourney_bedwars_two_four_0_kills_bedwars",
- "deaths" : "Bedwars.tourney_bedwars_two_four_0_deaths_bedwars"
+ "gameLimit": 40,
+ "tourneyField": "bedwars_two_four_0",
+ "wins": "Bedwars.tourney_bedwars_two_four_0_wins_bedwars",
+ "losses": "Bedwars.tourney_bedwars_two_four_0_losses_bedwars",
+ "finalKills": "Bedwars.tourney_bedwars_two_four_0_final_kills_bedwars",
+ "finalDeaths": "Bedwars.tourney_bedwars_two_four_0_final_deaths_bedwars",
+ "winstreak": "Bedwars.tourney_bedwars_two_four_0_winstreak2",
+ "killstreak": "Bedwars.tourney_bedwars_two_four_0_killstreak_bedwars",
+ "kills": "Bedwars.tourney_bedwars_two_four_0_kills_bedwars",
+ "deaths": "Bedwars.tourney_bedwars_two_four_0_deaths_bedwars"
}
},
{
@@ -308,12 +309,12 @@
"end": 1660514400
},
"data": {
- "gameLimit" : 50,
- "tourneyField" : "mini_walls_0",
- "wins" : "Arcade.wins_tourney_mini_walls_0",
- "deaths" : "Arcade.deaths_tourney_mini_walls_0",
- "kills" : "Arcade.kills_tourney_mini_walls_0",
- "finalKills" : "Arcade.final_kills_tourney_mini_walls_0"
+ "gameLimit": 50,
+ "tourneyField": "mini_walls_0",
+ "wins": "Arcade.wins_tourney_mini_walls_0",
+ "deaths": "Arcade.deaths_tourney_mini_walls_0",
+ "kills": "Arcade.kills_tourney_mini_walls_0",
+ "finalKills": "Arcade.final_kills_tourney_mini_walls_0"
}
},
{
@@ -326,9 +327,9 @@
"end": 1667167200
},
"data": {
- "gameLimit" : 72,
- "tourneyField" : "gingerbread_solo_1",
- "wins" : "GingerBread.tourney_gingerbread_solo_1_gold_trophy"
+ "gameLimit": 72,
+ "tourneyField": "gingerbread_solo_1",
+ "wins": "GingerBread.tourney_gingerbread_solo_1_gold_trophy"
}
},
{
@@ -341,11 +342,11 @@
"end": 1679868000
},
"data": {
- "gameLimit" : 70,
- "tourneyField" : "blitz_duo_2",
- "wins" : "HungerGames.tourney_blitz_duo_2_wins_teams",
- "kills" : "HungerGames.tourney_blitz_duo_2_kills",
- "deaths" : "HungerGames.tourney_blitz_duo_2_deaths"
+ "gameLimit": 70,
+ "tourneyField": "blitz_duo_2",
+ "wins": "HungerGames.tourney_blitz_duo_2_wins_teams",
+ "kills": "HungerGames.tourney_blitz_duo_2_kills",
+ "deaths": "HungerGames.tourney_blitz_duo_2_deaths"
}
},
{
@@ -358,11 +359,11 @@
"end": 1693163760
},
"data": {
- "gameLimit" : 100,
- "tourneyField" : "wool_wars_0",
- "wins" : "WoolGames.wool_wars.stats.tourney.wool_wars_0.wins",
- "kills" : "WoolGames.wool_wars.stats.tourney.wool_wars_0.kills",
- "deaths" : "WoolGames.wool_wars.stats.tourney.wool_wars_0.deaths"
+ "gameLimit": 100,
+ "tourneyField": "wool_wars_0",
+ "wins": "WoolGames.wool_wars.stats.tourney.wool_wars_0.wins",
+ "kills": "WoolGames.wool_wars.stats.tourney.wool_wars_0.kills",
+ "deaths": "WoolGames.wool_wars.stats.tourney.wool_wars_0.deaths"
}
},
{
@@ -375,11 +376,11 @@
"end": 1694383200
},
"data": {
- "gameLimit" : 40,
- "tourneyField" : "wool_wars_1",
- "wins" : "WoolGames.wool_wars.stats.tourney.wool_wars_1.wins",
- "kills" : "WoolGames.wool_wars.stats.tourney.wool_wars_1.kills",
- "deaths" : "WoolGames.wool_wars.stats.tourney.wool_wars_1.deaths"
+ "gameLimit": 40,
+ "tourneyField": "wool_wars_1",
+ "wins": "WoolGames.wool_wars.stats.tourney.wool_wars_1.wins",
+ "kills": "WoolGames.wool_wars.stats.tourney.wool_wars_1.kills",
+ "deaths": "WoolGames.wool_wars.stats.tourney.wool_wars_1.deaths"
}
},
{
@@ -392,16 +393,16 @@
"end": 1698012000
},
"data": {
- "gameLimit" : 40,
- "tourneyField" : "bedwars_two_four_1",
- "wins" : "Bedwars.tourney_bedwars_two_four_1_wins_bedwars",
- "losses" : "Bedwars.tourney_bedwars_two_four_1_losses_bedwars",
- "finalKills" : "Bedwars.tourney_bedwars_two_four_1_final_kills_bedwars",
- "finalDeaths" : "Bedwars.tourney_bedwars_two_four_1_final_deaths_bedwars",
- "winstreak" : "Bedwars.tourney_bedwars_two_four_1_winstreak2",
- "killstreak" : "Bedwars.tourney_bedwars_two_four_1_killstreak_bedwars",
- "kills" : "Bedwars.tourney_bedwars_two_four_1_kills_bedwars",
- "deaths" : "Bedwars.tourney_bedwars_two_four_1_deaths_bedwars"
+ "gameLimit": 40,
+ "tourneyField": "bedwars_two_four_1",
+ "wins": "Bedwars.tourney_bedwars_two_four_1_wins_bedwars",
+ "losses": "Bedwars.tourney_bedwars_two_four_1_losses_bedwars",
+ "finalKills": "Bedwars.tourney_bedwars_two_four_1_final_kills_bedwars",
+ "finalDeaths": "Bedwars.tourney_bedwars_two_four_1_final_deaths_bedwars",
+ "winstreak": "Bedwars.tourney_bedwars_two_four_1_winstreak2",
+ "killstreak": "Bedwars.tourney_bedwars_two_four_1_killstreak_bedwars",
+ "kills": "Bedwars.tourney_bedwars_two_four_1_kills_bedwars",
+ "deaths": "Bedwars.tourney_bedwars_two_four_1_deaths_bedwars"
}
},
{
@@ -414,11 +415,11 @@
"end": 1702854000
},
"data": {
- "gameLimit" : 60,
- "tourneyField" : "grinch_simulator_1",
- "wins" : "Arcade.wins_grinch_simulator_v2_tourney_grinch_simulator_1",
- "losses" : "Arcade.losses_grinch_simulator_v2_tourney_grinch_simulator_1",
- "grinch_gifts" : "Arcade.gifts_grinch_simulator_v2_tourney_grinch_simulator_1"
+ "gameLimit": 60,
+ "tourneyField": "grinch_simulator_1",
+ "wins": "Arcade.wins_grinch_simulator_v2_tourney_grinch_simulator_1",
+ "losses": "Arcade.losses_grinch_simulator_v2_tourney_grinch_simulator_1",
+ "grinch_gifts": "Arcade.gifts_grinch_simulator_v2_tourney_grinch_simulator_1"
}
},
{
@@ -431,10 +432,10 @@
"end": 1710712800
},
"data": {
- "gameLimit" : 80,
- "tourneyField" : "tnt_run_1",
- "wins" : "TNTGames.wins_tourney_tnt_run_1",
- "losses" : "TNTGames.deaths_tourney_tnt_run_1"
+ "gameLimit": 80,
+ "tourneyField": "tnt_run_1",
+ "wins": "TNTGames.wins_tourney_tnt_run_1",
+ "losses": "TNTGames.deaths_tourney_tnt_run_1"
}
},
{
@@ -447,15 +448,15 @@
"end": 1719180000
},
"data": {
- "gameLimit" : 120,
- "tourneyField" : "sw_normal_doubles_0",
- "wins" : "SkyWars.tourney_sw_normal_doubles_0_wins",
- "losses" : "SkyWars.tourney_sw_normal_doubles_0_losses",
- "kills" : "SkyWars.tourney_sw_normal_doubles_0_kills",
- "deaths" : "SkyWars.tourney_sw_normal_doubles_0_deaths",
- "assists" : "SkyWars.tourney_sw_normal_doubles_0_assists",
- "winstreak" : "SkyWars.tourney_sw_normal_doubles_0_win_streak",
- "killstreak" : "SkyWars.tourney_sw_normal_doubles_0_killstreak"
+ "gameLimit": 120,
+ "tourneyField": "sw_normal_doubles_0",
+ "wins": "SkyWars.tourney_sw_normal_doubles_0_wins",
+ "losses": "SkyWars.tourney_sw_normal_doubles_0_losses",
+ "kills": "SkyWars.tourney_sw_normal_doubles_0_kills",
+ "deaths": "SkyWars.tourney_sw_normal_doubles_0_deaths",
+ "assists": "SkyWars.tourney_sw_normal_doubles_0_assists",
+ "winstreak": "SkyWars.tourney_sw_normal_doubles_0_win_streak",
+ "killstreak": "SkyWars.tourney_sw_normal_doubles_0_killstreak"
}
}
]
diff --git a/target/maven-archiver/pom.properties b/target/maven-archiver/pom.properties
index 2cceb68..3f162e5 100644
--- a/target/maven-archiver/pom.properties
+++ b/target/maven-archiver/pom.properties
@@ -1,5 +1,3 @@
-#Generated by Maven
-#Wed Jun 28 17:50:57 EDT 2023
-groupId=me.stuffy
artifactId=stuffybot-java
+groupId=me.stuffy
version=1.0-SNAPSHOT
diff --git a/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst b/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst
index f310943..47d8e20 100644
--- a/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst
+++ b/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst
@@ -1 +1,42 @@
+me\stuffy\stuffybot\profiles\Rank.class
+me\stuffy\stuffybot\utils\MiscUtils.class
+me\stuffy\stuffybot\commands\MaxesCommand.class
+me\stuffy\stuffybot\events\UpdateBotStatsEvent.class
+me\stuffy\stuffybot\commands\MegaWallsCommand.class
+me\stuffy\stuffybot\commands\SetupCommand.class
+me\stuffy\stuffybot\profiles\GlobalData.class
+me\stuffy\stuffybot\utils\DiscordUtils.class
+me\stuffy\stuffybot\profiles\MojangProfile.class
+me\stuffy\stuffybot\events\BaseEvent.class
+me\stuffy\stuffybot\profiles\Achievement.class
+me\stuffy\stuffybot\utils\APIUtils$3.class
+me\stuffy\stuffybot\utils\Config.class
+me\stuffy\stuffybot\profiles\HypixelProfile.class
+me\stuffy\stuffybot\interactions\InteractionHandler.class
+me\stuffy\stuffybot\events\ActiveEvents.class
+me\stuffy\stuffybot\commands\BlitzCommand.class
+me\stuffy\stuffybot\commands\PlayCommandCommand.class
+me\stuffy\stuffybot\utils\InvalidOptionException.class
+me\stuffy\stuffybot\utils\APIUtils$2.class
+me\stuffy\stuffybot\interactions\InteractionManager.class
+me\stuffy\stuffybot\utils\Verification.class
+me\stuffy\stuffybot\profiles\DiscordUser.class
+me\stuffy\stuffybot\utils\APIUtils$1.class
+me\stuffy\stuffybot\commands\PitCommand.class
+me\stuffy\stuffybot\commands\TkrCommand.class
+me\stuffy\stuffybot\utils\InteractionException.class
+me\stuffy\stuffybot\commands\StatsCommand.class
+me\stuffy\stuffybot\utils\APIUtils$5.class
me\stuffy\stuffybot\Bot.class
+me\stuffy\stuffybot\utils\APIException.class
+me\stuffy\stuffybot\commands\HelpCommand.class
+me\stuffy\stuffybot\commands\LinkCommand.class
+me\stuffy\stuffybot\commands\SearchCommand.class
+me\stuffy\stuffybot\commands\AchievementsCommand.class
+me\stuffy\stuffybot\utils\APIUtils.class
+me\stuffy\stuffybot\interactions\InteractionId.class
+me\stuffy\stuffybot\utils\Logger.class
+me\stuffy\stuffybot\profiles\Achievement$Type.class
+me\stuffy\stuffybot\commands\TournamentCommand.class
+me\stuffy\stuffybot\utils\StatisticsManager.class
+me\stuffy\stuffybot\utils\APIUtils$4.class
diff --git a/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst b/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst
index ff9febf..4e74a4c 100644
--- a/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst
+++ b/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst
@@ -1,14 +1,36 @@
-C:\Users\mattd\IdeaProjects\stuffybot-java\src\main\java\me\stuffy\stuffybot\events\AdminForumPost.java
-C:\Users\mattd\IdeaProjects\stuffybot-java\src\main\java\me\stuffy\stuffybot\events\GameResources.java
-C:\Users\mattd\IdeaProjects\stuffybot-java\src\main\java\me\stuffy\stuffybot\utils\HypixelApiUtils.java
-C:\Users\mattd\IdeaProjects\stuffybot-java\src\main\java\me\stuffy\stuffybot\commands\PingCommand.java
-C:\Users\mattd\IdeaProjects\stuffybot-java\src\main\java\me\stuffy\stuffybot\commands\StuffyCommand.java
-C:\Users\mattd\IdeaProjects\stuffybot-java\src\main\java\me\stuffy\stuffybot\events\UpdateStatistics.java
-C:\Users\mattd\IdeaProjects\stuffybot-java\src\main\java\me\stuffy\stuffybot\events\HypixelStatusUpdate.java
-C:\Users\mattd\IdeaProjects\stuffybot-java\src\main\java\me\stuffy\stuffybot\events\BaseEvent.java
-C:\Users\mattd\IdeaProjects\stuffybot-java\src\main\java\me\stuffy\stuffybot\utils\MojangApiUtils.java
-C:\Users\mattd\IdeaProjects\stuffybot-java\src\main\java\me\stuffy\stuffybot\commands\VerifyCommand.java
C:\Users\mattd\IdeaProjects\stuffybot-java\src\main\java\me\stuffy\stuffybot\Bot.java
-C:\Users\mattd\IdeaProjects\stuffybot-java\src\main\java\me\stuffy\stuffybot\events\StaffRankChanges.java
-C:\Users\mattd\IdeaProjects\stuffybot-java\src\main\java\me\stuffy\stuffybot\commands\BaseCommand.java
-C:\Users\mattd\IdeaProjects\stuffybot-java\src\main\java\me\stuffy\stuffybot\utils\TimeUtils.java
+C:\Users\mattd\IdeaProjects\stuffybot-java\src\main\java\me\stuffy\stuffybot\commands\AchievementsCommand.java
+C:\Users\mattd\IdeaProjects\stuffybot-java\src\main\java\me\stuffy\stuffybot\commands\BlitzCommand.java
+C:\Users\mattd\IdeaProjects\stuffybot-java\src\main\java\me\stuffy\stuffybot\commands\HelpCommand.java
+C:\Users\mattd\IdeaProjects\stuffybot-java\src\main\java\me\stuffy\stuffybot\commands\LinkCommand.java
+C:\Users\mattd\IdeaProjects\stuffybot-java\src\main\java\me\stuffy\stuffybot\commands\MaxesCommand.java
+C:\Users\mattd\IdeaProjects\stuffybot-java\src\main\java\me\stuffy\stuffybot\commands\MegaWallsCommand.java
+C:\Users\mattd\IdeaProjects\stuffybot-java\src\main\java\me\stuffy\stuffybot\commands\PitCommand.java
+C:\Users\mattd\IdeaProjects\stuffybot-java\src\main\java\me\stuffy\stuffybot\commands\PlayCommandCommand.java
+C:\Users\mattd\IdeaProjects\stuffybot-java\src\main\java\me\stuffy\stuffybot\commands\SearchCommand.java
+C:\Users\mattd\IdeaProjects\stuffybot-java\src\main\java\me\stuffy\stuffybot\commands\SetupCommand.java
+C:\Users\mattd\IdeaProjects\stuffybot-java\src\main\java\me\stuffy\stuffybot\commands\StatsCommand.java
+C:\Users\mattd\IdeaProjects\stuffybot-java\src\main\java\me\stuffy\stuffybot\commands\TkrCommand.java
+C:\Users\mattd\IdeaProjects\stuffybot-java\src\main\java\me\stuffy\stuffybot\commands\TournamentCommand.java
+C:\Users\mattd\IdeaProjects\stuffybot-java\src\main\java\me\stuffy\stuffybot\events\ActiveEvents.java
+C:\Users\mattd\IdeaProjects\stuffybot-java\src\main\java\me\stuffy\stuffybot\events\BaseEvent.java
+C:\Users\mattd\IdeaProjects\stuffybot-java\src\main\java\me\stuffy\stuffybot\events\UpdateBotStatsEvent.java
+C:\Users\mattd\IdeaProjects\stuffybot-java\src\main\java\me\stuffy\stuffybot\interactions\InteractionHandler.java
+C:\Users\mattd\IdeaProjects\stuffybot-java\src\main\java\me\stuffy\stuffybot\interactions\InteractionId.java
+C:\Users\mattd\IdeaProjects\stuffybot-java\src\main\java\me\stuffy\stuffybot\interactions\InteractionManager.java
+C:\Users\mattd\IdeaProjects\stuffybot-java\src\main\java\me\stuffy\stuffybot\profiles\Achievement.java
+C:\Users\mattd\IdeaProjects\stuffybot-java\src\main\java\me\stuffy\stuffybot\profiles\DiscordUser.java
+C:\Users\mattd\IdeaProjects\stuffybot-java\src\main\java\me\stuffy\stuffybot\profiles\GlobalData.java
+C:\Users\mattd\IdeaProjects\stuffybot-java\src\main\java\me\stuffy\stuffybot\profiles\HypixelProfile.java
+C:\Users\mattd\IdeaProjects\stuffybot-java\src\main\java\me\stuffy\stuffybot\profiles\MojangProfile.java
+C:\Users\mattd\IdeaProjects\stuffybot-java\src\main\java\me\stuffy\stuffybot\profiles\Rank.java
+C:\Users\mattd\IdeaProjects\stuffybot-java\src\main\java\me\stuffy\stuffybot\utils\APIException.java
+C:\Users\mattd\IdeaProjects\stuffybot-java\src\main\java\me\stuffy\stuffybot\utils\APIUtils.java
+C:\Users\mattd\IdeaProjects\stuffybot-java\src\main\java\me\stuffy\stuffybot\utils\Config.java
+C:\Users\mattd\IdeaProjects\stuffybot-java\src\main\java\me\stuffy\stuffybot\utils\DiscordUtils.java
+C:\Users\mattd\IdeaProjects\stuffybot-java\src\main\java\me\stuffy\stuffybot\utils\InteractionException.java
+C:\Users\mattd\IdeaProjects\stuffybot-java\src\main\java\me\stuffy\stuffybot\utils\InvalidOptionException.java
+C:\Users\mattd\IdeaProjects\stuffybot-java\src\main\java\me\stuffy\stuffybot\utils\Logger.java
+C:\Users\mattd\IdeaProjects\stuffybot-java\src\main\java\me\stuffy\stuffybot\utils\MiscUtils.java
+C:\Users\mattd\IdeaProjects\stuffybot-java\src\main\java\me\stuffy\stuffybot\utils\StatisticsManager.java
+C:\Users\mattd\IdeaProjects\stuffybot-java\src\main\java\me\stuffy\stuffybot\utils\Verification.java