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..fe809a4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -22,3 +22,6 @@
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
replay_pid*
+
+# Configuration files
+*.properties
\ 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..4d3acd6 100644
--- a/src/main/java/me/stuffy/stuffybot/Bot.java
+++ b/src/main/java/me/stuffy/stuffybot/Bot.java
@@ -4,7 +4,8 @@
import me.stuffy.stuffybot.events.ActiveEvents;
import me.stuffy.stuffybot.events.UpdateBotStatsEvent;
import me.stuffy.stuffybot.interactions.InteractionHandler;
-import me.stuffy.stuffybot.utils.DiscordUtils;
+import me.stuffy.stuffybot.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,38 +15,61 @@
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.SlashCommandData;
+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(
@@ -53,23 +77,65 @@ public Bot() throws InterruptedException {
);
// 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 +159,53 @@ 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));
if (scope.equals("local")) {
- //clearLocalCommands();
+ jda.updateCommands().queue();
this.homeGuild.updateCommands().addCommands(
commandList
).queue();
@@ -130,15 +218,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/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/InteractionHandler.java b/src/main/java/me/stuffy/stuffybot/interactions/InteractionHandler.java
index ae495fa..8d74a7f 100644
--- a/src/main/java/me/stuffy/stuffybot/interactions/InteractionHandler.java
+++ b/src/main/java/me/stuffy/stuffybot/interactions/InteractionHandler.java
@@ -2,10 +2,9 @@
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;
@@ -22,7 +21,6 @@
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;
@@ -31,11 +29,12 @@
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.APIUtils.*;
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);
@@ -49,16 +48,31 @@ public void onSlashCommandInteraction(SlashCommandInteractionEvent event) {
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;
@@ -68,24 +82,32 @@ public void onSlashCommandInteraction(SlashCommandInteractionEvent event) {
InteractionId interactionId = new InteractionId(id, commandName, event.getUser().getId(), optionsArray);
- Logger.log(" @" + event.getUser().getName() + ": /" + commandName + " " + optionsArray.toString());
+ if (event.getIntegrationOwners().isUserIntegration()) {
+ Logger.log(" @" + event.getUser().getName() + ": /" + commandName + " " + optionsArray);
+ } else {
+ Logger.log(" @" + event.getUser().getName() + ": /" + commandName + " " + optionsArray);
+ }
+
+ 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();
}
@@ -93,7 +115,11 @@ public void onSlashCommandInteraction(SlashCommandInteractionEvent event) {
String uid = interactionId.getId();
InteractionHook hook = event.getHook();
ScheduledFuture> scheduledFuture = scheduler.schedule(() -> {
- hook.editOriginalComponents().queue();
+ try {
+ 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);
@@ -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,25 +228,13 @@ 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();
+ if (event.getModalId().equals("verify")) {
+ verifyModal(event);
}
-
}
@Override
@@ -208,16 +242,15 @@ public void onCommandAutoCompleteInteraction(CommandAutoCompleteInteractionEvent
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");
+ "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())) {
+ if (skin.toLowerCase().contains(currentInput.toLowerCase())) {
options.add(skin);
}
}
@@ -232,19 +265,114 @@ public void onCommandAutoCompleteInteraction(CommandAutoCompleteInteractionEvent
.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){
+ if (name.toLowerCase().contains(currentInput.toLowerCase()) && choices.size() <= 25) {
choices.add(new Command.Choice(name, id));
}
});
e.replyChoices(choices).queue();
- break;
+ }
+ }
+ 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" -> {
+ 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();
}
}
default -> {
@@ -275,12 +403,15 @@ 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/InteractionManager.java b/src/main/java/me/stuffy/stuffybot/interactions/InteractionManager.java
index c9bc6ba..80f33bb 100644
--- a/src/main/java/me/stuffy/stuffybot/interactions/InteractionManager.java
+++ b/src/main/java/me/stuffy/stuffybot/interactions/InteractionManager.java
@@ -41,6 +41,12 @@ 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);
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..0c360d9 100644
--- a/src/main/java/me/stuffy/stuffybot/profiles/HypixelProfile.java
+++ b/src/main/java/me/stuffy/stuffybot/profiles/HypixelProfile.java
@@ -12,16 +12,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 +128,11 @@ public UUID getUuid() {
return uuid;
}
+ public HypixelProfile setDisplayName(String displayName) {
+ this.displayName = displayName;
+ return this;
+ }
+
public String getDisplayName() {
return displayName;
}
@@ -71,14 +145,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 +169,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() {
@@ -215,7 +277,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 +424,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 +646,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 +683,25 @@ public Integer getStat(String field) {
return 0;
}
}
+
+ 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/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..cc069b6
--- /dev/null
+++ b/src/main/resources/config.properties
@@ -0,0 +1,6 @@
+environment=production
+homeGuildId=818238263110008863
+verifiedRoleId=818283784763473952
+notVerifiedRoleId=1269849137982083215
+linkCommandId=1281037425032040491
+customStatus=Use anywhere!
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