aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCow <cow@volloeko.de>2021-08-13 10:48:19 +0200
committerCow <cow@volloeko.de>2021-08-13 10:48:19 +0200
commit141a45a8d23237bf23b3b7a14d447440a40e71ea (patch)
tree2d5e5881b304e76881e1eddd0b467c79be60e7b9
parent589b94d1c7a1a648402d62c6c097b2394bff3d1d (diff)
downloadCowlection-141a45a8d23237bf23b3b7a14d447440a40e71ea.tar.gz
Cowlection-141a45a8d23237bf23b3b7a14d447440a40e71ea.tar.bz2
Cowlection-141a45a8d23237bf23b3b7a14d447440a40e71ea.zip
Improved error messages for API errors
-rw-r--r--CHANGELOG.md2
-rw-r--r--src/main/java/de/cowtipper/cowlection/command/MooCommand.java31
-rw-r--r--src/main/java/de/cowtipper/cowlection/config/CredentialStorage.java4
-rw-r--r--src/main/java/de/cowtipper/cowlection/data/HyApiKey.java27
-rw-r--r--src/main/java/de/cowtipper/cowlection/error/ApiAskPolitelyErrorEvent.java (renamed from src/main/java/de/cowtipper/cowlection/event/ApiErrorEvent.java)6
-rw-r--r--src/main/java/de/cowtipper/cowlection/error/ApiHttpErrorEvent.java30
-rw-r--r--src/main/java/de/cowtipper/cowlection/error/ApiHttpErrorException.java16
-rw-r--r--src/main/java/de/cowtipper/cowlection/listener/ChatListener.java4
-rw-r--r--src/main/java/de/cowtipper/cowlection/listener/PlayerListener.java21
-rw-r--r--src/main/java/de/cowtipper/cowlection/util/ApiUtils.java37
10 files changed, 150 insertions, 28 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4b944cd..813ec8b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -36,6 +36,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- MC Log file search now skips large files to prevent huge log files from blocking the search
- Dungeon Party Finder: Each dungeon class can now also be blocked or blocked if duplicated (= red party background)
- Disabled dungeon tooltip cleaner inside dungeons (+ fixed a rare crash)
+- Improved error messages for API errors
+ - also added new sub-command `/moo apikey check` to see API key usage statistics
### Fixed
- 'Show Dungeon item base stats' feature now works with HPB'd items and master stars
diff --git a/src/main/java/de/cowtipper/cowlection/command/MooCommand.java b/src/main/java/de/cowtipper/cowlection/command/MooCommand.java
index 43a1335..7a67a0d 100644
--- a/src/main/java/de/cowtipper/cowlection/command/MooCommand.java
+++ b/src/main/java/de/cowtipper/cowlection/command/MooCommand.java
@@ -1060,22 +1060,41 @@ public class MooCommand extends CommandBase {
EnumChatFormatting color;
EnumChatFormatting colorSecondary;
if (CredentialStorage.isMooValid) {
- firstSentence = "You already set your Hypixel API key.";
+ firstSentence = "[" + Cowlection.MODNAME + "] You already set your Hypixel API key. Run " + EnumChatFormatting.DARK_GREEN + "/" + getCommandName() + " apikey check " + EnumChatFormatting.GREEN + "to see usage statistics.";
color = EnumChatFormatting.GREEN;
colorSecondary = EnumChatFormatting.DARK_GREEN;
} else {
- firstSentence = "You haven't set your Hypixel API key yet or the API key is invalid.";
+ firstSentence = "[" + Cowlection.MODNAME + "] You haven't set your Hypixel API key yet or the API key is invalid.";
color = EnumChatFormatting.RED;
colorSecondary = EnumChatFormatting.DARK_RED;
}
main.getChatHelper().sendMessage(color, firstSentence + " Use " + colorSecondary + "/api new" + color + " to request a new API key from Hypixel or use " + colorSecondary + "/" + this.getCommandName() + " apikey <key>" + color + " to manually set your existing API key.");
} else {
String key = args[1];
- if (Utils.isValidUuid(key)) {
- main.getChatHelper().sendMessage(EnumChatFormatting.YELLOW, "Validating API key...");
+ if ("check".equalsIgnoreCase(key)) {
+ if (StringUtils.isNotEmpty(CredentialStorage.moo)) {
+ ApiUtils.fetchApiKeyInfo(CredentialStorage.moo, hyApiKey -> {
+ if (hyApiKey != null && hyApiKey.isSuccess()) {
+ HyApiKey.Record apiKeyRecord = hyApiKey.getRecord();
+ if (apiKeyRecord != null) {
+ main.getChatHelper().sendMessage(EnumChatFormatting.GREEN, "[" + Cowlection.MODNAME + "] Your Hypixel API key was used to execute a total of " + EnumChatFormatting.DARK_GREEN + Utils.formatNumber(apiKeyRecord.getTotalQueries())
+ + EnumChatFormatting.GREEN + " API requests. In the last minute, " + EnumChatFormatting.DARK_GREEN + apiKeyRecord.getQueriesInPastMin() + EnumChatFormatting.GREEN + " out of maximum " + EnumChatFormatting.DARK_GREEN + apiKeyRecord.getLimit() + EnumChatFormatting.GREEN + " allowed requests were made.");
+ } else {
+ main.getChatHelper().sendMessage(EnumChatFormatting.GREEN, "[" + Cowlection.MODNAME + "] Your Hypixel API key seems to be valid, but processing usage statistics failed.");
+ }
+ } else {
+ String cause = hyApiKey != null ? hyApiKey.getCause() : null;
+ Cowlection.getInstance().getChatHelper().sendMessage(EnumChatFormatting.RED, "[" + Cowlection.MODNAME + "] Failed to check API key usage statistics: " + (cause != null ? cause : "unknown cause :c"));
+ }
+ });
+ } else {
+ throw new MooCommandException("You haven't set your Hypixel API key yet. Use " + EnumChatFormatting.DARK_RED + "/api new" + EnumChatFormatting.RED + " to request a new API key from Hypixel or use " + EnumChatFormatting.DARK_RED + "/" + this.getCommandName() + " apikey <key>" + EnumChatFormatting.RED + " to manually set your existing API key.");
+ }
+ } else if (Utils.isValidUuid(key)) {
+ main.getChatHelper().sendMessage(EnumChatFormatting.YELLOW, "[" + Cowlection.MODNAME + "] Validating API key...");
main.getMoo().setMooIfValid(key, true);
} else {
- throw new SyntaxErrorException("That doesn't look like a valid API key...");
+ throw new SyntaxErrorException("[" + Cowlection.MODNAME + "] That doesn't look like a valid API key... Did you want check your API key usage statistics? Run /" + getCommandName() + " apikey check");
}
}
}
@@ -1193,6 +1212,8 @@ public class MooCommand extends CommandBase {
return getListOfStringsMatchingLastWord(args, "off", "on", "disable", "enable");
} else if (args.length == 2 && (args[0].equalsIgnoreCase("chestAnalyzer") || args[0].equalsIgnoreCase("chestAnalyser") || args[0].equalsIgnoreCase("analyzeChests") || args[0].equalsIgnoreCase("analyseChests"))) {
return getListOfStringsMatchingLastWord(args, "stop");
+ } else if (args.length == 2 && args[0].equalsIgnoreCase("apikey")) {
+ return getListOfStringsMatchingLastWord(args, "check");
}
String commandArg = args[0].toLowerCase();
if (args.length == 2 && (commandArg.equals("s") || commandArg.equals("ss") || commandArg.equals("namechangecheck") || commandArg.contains("stalk") || commandArg.contains("askpolitely"))) { // stalk & stalkskyblock + namechangecheck
diff --git a/src/main/java/de/cowtipper/cowlection/config/CredentialStorage.java b/src/main/java/de/cowtipper/cowlection/config/CredentialStorage.java
index c7cf35f..f4e64f6 100644
--- a/src/main/java/de/cowtipper/cowlection/config/CredentialStorage.java
+++ b/src/main/java/de/cowtipper/cowlection/config/CredentialStorage.java
@@ -45,12 +45,12 @@ public class CredentialStorage {
// api key is valid!
Cowlection.getInstance().getMoo().setMoo(moo);
if (commandTriggered) {
- Cowlection.getInstance().getChatHelper().sendMessage(EnumChatFormatting.GREEN, "Successfully verified API key ✔");
+ Cowlection.getInstance().getChatHelper().sendMessage(EnumChatFormatting.GREEN, "[" + Cowlection.MODNAME + "] Successfully verified API key ✔");
}
} else if (commandTriggered) {
// api key is invalid
String cause = hyApiKey != null ? hyApiKey.getCause() : null;
- Cowlection.getInstance().getChatHelper().sendMessage(EnumChatFormatting.RED, "Failed to verify API key: " + (cause != null ? cause : "unknown cause :c"));
+ Cowlection.getInstance().getChatHelper().sendMessage(EnumChatFormatting.RED, "[" + Cowlection.MODNAME + "] Failed to verify API key: " + (cause != null ? cause : "unknown cause :c"));
}
});
}
diff --git a/src/main/java/de/cowtipper/cowlection/data/HyApiKey.java b/src/main/java/de/cowtipper/cowlection/data/HyApiKey.java
index ca7152c..9552948 100644
--- a/src/main/java/de/cowtipper/cowlection/data/HyApiKey.java
+++ b/src/main/java/de/cowtipper/cowlection/data/HyApiKey.java
@@ -1,11 +1,12 @@
package de.cowtipper.cowlection.data;
+@SuppressWarnings("unused")
public class HyApiKey {
- @SuppressWarnings("unused")
private boolean success;
- @SuppressWarnings("unused")
private String cause;
+ private Record record;
+
/**
* No-args constructor for GSON
*/
@@ -19,4 +20,26 @@ public class HyApiKey {
public String getCause() {
return cause;
}
+
+ public Record getRecord() {
+ return record;
+ }
+
+ public class Record {
+ private int queriesInPastMin;
+ private int limit;
+ private long totalQueries;
+
+ public int getQueriesInPastMin() {
+ return queriesInPastMin;
+ }
+
+ public int getLimit() {
+ return limit;
+ }
+
+ public long getTotalQueries() {
+ return totalQueries;
+ }
+ }
}
diff --git a/src/main/java/de/cowtipper/cowlection/event/ApiErrorEvent.java b/src/main/java/de/cowtipper/cowlection/error/ApiAskPolitelyErrorEvent.java
index ccfc4d3..5da2dfb 100644
--- a/src/main/java/de/cowtipper/cowlection/event/ApiErrorEvent.java
+++ b/src/main/java/de/cowtipper/cowlection/error/ApiAskPolitelyErrorEvent.java
@@ -1,11 +1,11 @@
-package de.cowtipper.cowlection.event;
+package de.cowtipper.cowlection.error;
import net.minecraftforge.fml.common.eventhandler.Event;
-public class ApiErrorEvent extends Event {
+public class ApiAskPolitelyErrorEvent extends Event {
private final String playerName;
- public ApiErrorEvent(String playerName) {
+ public ApiAskPolitelyErrorEvent(String playerName) {
this.playerName = playerName;
}
diff --git a/src/main/java/de/cowtipper/cowlection/error/ApiHttpErrorEvent.java b/src/main/java/de/cowtipper/cowlection/error/ApiHttpErrorEvent.java
new file mode 100644
index 0000000..4b66bb6
--- /dev/null
+++ b/src/main/java/de/cowtipper/cowlection/error/ApiHttpErrorEvent.java
@@ -0,0 +1,30 @@
+package de.cowtipper.cowlection.error;
+
+import net.minecraftforge.fml.common.eventhandler.Event;
+
+public class ApiHttpErrorEvent extends Event {
+ private final String message;
+ private final String url;
+
+ public ApiHttpErrorEvent(String message, String url) {
+ this.message = message;
+ this.url = url;
+ }
+
+ public String getMessage() {
+ return message;
+ }
+
+ public String getUrl() {
+ return url;
+ }
+
+ public String getBaseUrl() {
+ int queryParamStart = url.indexOf('?', 10);
+ return queryParamStart > 0 ? url.substring(0, queryParamStart) : url;
+ }
+
+ public boolean hasUrlKey() {
+ return url.contains("key=");
+ }
+}
diff --git a/src/main/java/de/cowtipper/cowlection/error/ApiHttpErrorException.java b/src/main/java/de/cowtipper/cowlection/error/ApiHttpErrorException.java
new file mode 100644
index 0000000..ac0a6a4
--- /dev/null
+++ b/src/main/java/de/cowtipper/cowlection/error/ApiHttpErrorException.java
@@ -0,0 +1,16 @@
+package de.cowtipper.cowlection.error;
+
+import java.io.IOException;
+
+public class ApiHttpErrorException extends IOException {
+ private final String url;
+
+ public ApiHttpErrorException(String message, String url) {
+ super(message);
+ this.url = url;
+ }
+
+ public String getUrl() {
+ return url;
+ }
+}
diff --git a/src/main/java/de/cowtipper/cowlection/listener/ChatListener.java b/src/main/java/de/cowtipper/cowlection/listener/ChatListener.java
index 00f9649..3b5a168 100644
--- a/src/main/java/de/cowtipper/cowlection/listener/ChatListener.java
+++ b/src/main/java/de/cowtipper/cowlection/listener/ChatListener.java
@@ -96,7 +96,7 @@ public class ChatListener {
// Your new API key is 00000000-0000-0000-0000-000000000000
String moo = text.substring(20, 56);
if (Utils.isValidUuid(moo)) {
- main.getChatHelper().sendMessage(EnumChatFormatting.YELLOW, "Verifying the new API key...");
+ main.getChatHelper().sendMessage(EnumChatFormatting.YELLOW, "[" + Cowlection.MODNAME + "] Verifying the new API key...");
main.getMoo().setMooIfValid(moo, true);
}
}
@@ -254,7 +254,7 @@ public class ChatListener {
String foundDungeonsSecrets = "";
if (hyPlayerData != null) {
int foundSecrets = hyPlayerData.getAchievement("skyblock_treasure_hunter");
- foundDungeonsSecrets = "\n" + (outputAsChatMessages ? " " : "") + EnumChatFormatting.GRAY + "Found secrets: " + EnumChatFormatting.GOLD + foundSecrets;
+ foundDungeonsSecrets = "\n" + (outputAsChatMessages ? " " : "") + EnumChatFormatting.GRAY + "Found secrets: " + EnumChatFormatting.GOLD + Utils.formatNumber(foundSecrets);
String averageSecretsPerCompletion = null;
if (foundSecrets > 0 && totalDungeonCompletions > 0) {
diff --git a/src/main/java/de/cowtipper/cowlection/listener/PlayerListener.java b/src/main/java/de/cowtipper/cowlection/listener/PlayerListener.java
index cafa4a3..ff29669 100644
--- a/src/main/java/de/cowtipper/cowlection/listener/PlayerListener.java
+++ b/src/main/java/de/cowtipper/cowlection/listener/PlayerListener.java
@@ -3,7 +3,8 @@ package de.cowtipper.cowlection.listener;
import de.cowtipper.cowlection.Cowlection;
import de.cowtipper.cowlection.config.CredentialStorage;
import de.cowtipper.cowlection.config.MooConfig;
-import de.cowtipper.cowlection.event.ApiErrorEvent;
+import de.cowtipper.cowlection.error.ApiAskPolitelyErrorEvent;
+import de.cowtipper.cowlection.error.ApiHttpErrorEvent;
import de.cowtipper.cowlection.listener.skyblock.DungeonsListener;
import de.cowtipper.cowlection.listener.skyblock.SkyBlockListener;
import de.cowtipper.cowlection.util.*;
@@ -41,6 +42,7 @@ public class PlayerListener {
private boolean isPlayerJoiningServer;
private boolean isOnSkyBlock;
private AbortableRunnable checkScoreboard;
+ private long nextApiErrorMessage;
public PlayerListener(Cowlection main) {
this.main = main;
@@ -123,11 +125,26 @@ public class PlayerListener {
}
@SubscribeEvent
- public void onApiError(ApiErrorEvent e) {
+ public void onApiAskPolitelyError(ApiAskPolitelyErrorEvent e) {
main.getFriendsHandler().addErroredApiRequest(e.getPlayerName());
}
@SubscribeEvent
+ public void onApiHttpError(ApiHttpErrorEvent e) {
+ if (nextApiErrorMessage < System.currentTimeMillis() && Minecraft.getMinecraft().thePlayer != null) {
+ this.nextApiErrorMessage = System.currentTimeMillis() + 3000;
+ MooChatComponent hoverComponent = new MooChatComponent.KeyValueTooltipComponent("Click to visit", e.getBaseUrl());
+ if (e.hasUrlKey()) {
+ String eyeCatcher = "" + EnumChatFormatting.LIGHT_PURPLE + EnumChatFormatting.OBFUSCATED + "#" + EnumChatFormatting.RESET + EnumChatFormatting.RED;
+ hoverComponent.appendFreshSibling(new MooChatComponent(eyeCatcher + " Warning! " + eyeCatcher + " If you're streaming or sharing your screen:").red()
+ .appendFreshSibling(new MooChatComponent("Clicking this will reveal your Hypixel " + EnumChatFormatting.DARK_RED + "API key" + EnumChatFormatting.RED + "!").red()));
+ }
+ main.getChatHelper().sendMessage(new MooChatComponent(e.getMessage()).red()
+ .setUrl(e.getUrl(), hoverComponent));
+ }
+ }
+
+ @SubscribeEvent
public void onWorldEnter(PlayerSetSpawnEvent e) {
isPlayerJoiningServer = false;
diff --git a/src/main/java/de/cowtipper/cowlection/util/ApiUtils.java b/src/main/java/de/cowtipper/cowlection/util/ApiUtils.java
index 293c6ad..3a303e5 100644
--- a/src/main/java/de/cowtipper/cowlection/util/ApiUtils.java
+++ b/src/main/java/de/cowtipper/cowlection/util/ApiUtils.java
@@ -10,7 +10,9 @@ import de.cowtipper.cowlection.chesttracker.LowestBinsCache;
import de.cowtipper.cowlection.command.exception.ThrowingConsumer;
import de.cowtipper.cowlection.config.CredentialStorage;
import de.cowtipper.cowlection.data.*;
-import de.cowtipper.cowlection.event.ApiErrorEvent;
+import de.cowtipper.cowlection.error.ApiAskPolitelyErrorEvent;
+import de.cowtipper.cowlection.error.ApiHttpErrorEvent;
+import de.cowtipper.cowlection.error.ApiHttpErrorException;
import net.minecraftforge.common.MinecraftForge;
import org.apache.http.HttpStatus;
@@ -50,7 +52,7 @@ public class ApiUtils {
return GsonUtils.fromJson(reader, Friend.class);
}
} catch (IOException | JsonSyntaxException e) {
- e.printStackTrace();
+ handleApiException(e);
}
return null;
}
@@ -70,7 +72,7 @@ public class ApiUtils {
}
}
} catch (IOException | JsonSyntaxException e) {
- e.printStackTrace();
+ handleApiException(e);
}
return null;
}
@@ -85,7 +87,7 @@ public class ApiUtils {
return GsonUtils.fromJson(reader, HyStalkingData.class);
}
} catch (IOException | JsonSyntaxException e) {
- e.printStackTrace();
+ handleApiException(e);
}
return null;
}
@@ -100,7 +102,7 @@ public class ApiUtils {
return GsonUtils.fromJson(reader, HySkyBlockStats.class);
}
} catch (IOException | JsonSyntaxException e) {
- e.printStackTrace();
+ handleApiException(e);
}
return null;
}
@@ -115,7 +117,7 @@ public class ApiUtils {
return GsonUtils.fromJson(reader, HyBazaarData.class);
}
} catch (IOException | JsonSyntaxException e) {
- e.printStackTrace();
+ handleApiException(e);
}
return null;
}
@@ -130,7 +132,7 @@ public class ApiUtils {
return GsonUtils.fromJson(reader, LowestBinsCache.class);
}
} catch (IOException | JsonSyntaxException e) {
- e.printStackTrace();
+ handleApiException(e);
}
return new LowestBinsCache();
}
@@ -145,9 +147,9 @@ public class ApiUtils {
return GsonUtils.fromJson(reader, HyPlayerData.class);
}
} catch (IOException | JsonSyntaxException e) {
- e.printStackTrace();
- ApiErrorEvent event = new ApiErrorEvent(stalkedPlayer.getName());
+ ApiAskPolitelyErrorEvent event = new ApiAskPolitelyErrorEvent(stalkedPlayer.getName());
MinecraftForge.EVENT_BUS.post(event);
+ handleApiException(e);
}
return null;
}
@@ -162,11 +164,18 @@ public class ApiUtils {
return GsonUtils.fromJson(reader, HyApiKey.class);
}
} catch (IOException | JsonSyntaxException e) {
- e.printStackTrace();
+ handleApiException(e);
}
return null;
}
+ private static void handleApiException(Exception e) {
+ e.printStackTrace();
+ if (e instanceof ApiHttpErrorException) {
+ MinecraftForge.EVENT_BUS.post(new ApiHttpErrorEvent(e.getMessage(), ((ApiHttpErrorException) e).getUrl()));
+ }
+ }
+
private static BufferedReader makeApiCall(String url) throws IOException {
HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
connection.setConnectTimeout(5000);
@@ -177,9 +186,13 @@ public class ApiUtils {
if (connection.getResponseCode() == HttpStatus.SC_NO_CONTENT) { // http status 204
return null;
} else if (connection.getResponseCode() == HttpStatus.SC_BAD_GATEWAY && url.startsWith("https://api.hypixel.net/")) { // http status 502 (cloudflare)
- throw new IOException("Couldn't contact Hypixel API (502 Bad Gateway). API might be down, check https://status.hypixel.net for info.");
+ throw new ApiHttpErrorException("Couldn't contact Hypixel API (502 Bad Gateway). API might be down, check https://status.hypixel.net for info.", "https://status.hypixel.net");
+ } else if (connection.getResponseCode() == HttpStatus.SC_SERVICE_UNAVAILABLE) { // http status 503 Service Unavailable
+ int queryParamStart = url.indexOf('?', 10);
+ String baseUrl = queryParamStart > 0 ? url.substring(0, queryParamStart) : url;
+ throw new ApiHttpErrorException("Couldn't contact the API (503 Service unavailable). API might be down, or you might be blocked by Cloudflare, check if you can reach: " + baseUrl, url);
} else if (connection.getResponseCode() == HttpStatus.SC_BAD_GATEWAY && url.startsWith("https://moulberry.codes/")) { // http status 502 (cloudflare)
- throw new IOException("Couldn't contact Moulberry's API (502 Bad Gateway). API might be down, check if " + LOWEST_BINS + " is reachable.");
+ throw new ApiHttpErrorException("Couldn't contact Moulberry's API (502 Bad Gateway). API might be down, check if " + LOWEST_BINS + " is reachable.", LOWEST_BINS);
} else {
BufferedReader reader;
InputStream errorStream = connection.getErrorStream();