aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/io/github/moulberry/notenoughupdates/util
diff options
context:
space:
mode:
authorRoman / Linnea Gräf <roman.graef@gmail.com>2022-09-29 17:25:37 +0200
committerGitHub <noreply@github.com>2022-09-29 17:25:37 +0200
commit2dd4a2b36211c380c0bf4e231859dfafd3c04a5b (patch)
tree20cde26c05f32699ccb91cda1d37b67f4eea6c62 /src/main/java/io/github/moulberry/notenoughupdates/util
parentcbcc4c3b4004cbf3f86aeab515ea94a93b4efd1e (diff)
downloadnotenoughupdates-2dd4a2b36211c380c0bf4e231859dfafd3c04a5b.tar.gz
notenoughupdates-2dd4a2b36211c380c0bf4e231859dfafd3c04a5b.tar.bz2
notenoughupdates-2dd4a2b36211c380c0bf4e231859dfafd3c04a5b.zip
Add custom keystore and refactor HypixelApi.java (#318)
* Add custom keystore and refactor HypixelApi.java * Fix inputstream leak (+ less console spam) * Fix HOTM crash * Use api selected variable to find best profile * Number formatting * Make old profiles show again Co-authored-by: nopo <nopotheemail@gmail.com>
Diffstat (limited to 'src/main/java/io/github/moulberry/notenoughupdates/util')
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/util/ApiUtil.java203
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/util/HotmInformation.java12
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/util/HypixelApi.java174
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/util/PronounDB.java81
-rw-r--r--src/main/java/io/github/moulberry/notenoughupdates/util/SBInfo.java11
5 files changed, 231 insertions, 250 deletions
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/util/ApiUtil.java b/src/main/java/io/github/moulberry/notenoughupdates/util/ApiUtil.java
new file mode 100644
index 00000000..8c594911
--- /dev/null
+++ b/src/main/java/io/github/moulberry/notenoughupdates/util/ApiUtil.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright (C) 2022 NotEnoughUpdates contributors
+ *
+ * This file is part of NotEnoughUpdates.
+ *
+ * NotEnoughUpdates is free software: you can redistribute it
+ * and/or modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * NotEnoughUpdates is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with NotEnoughUpdates. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package io.github.moulberry.notenoughupdates.util;
+
+import com.google.gson.Gson;
+import com.google.gson.JsonObject;
+import io.github.moulberry.notenoughupdates.NotEnoughUpdates;
+import org.apache.commons.io.IOUtils;
+import org.apache.http.NameValuePair;
+import org.apache.http.client.utils.URIBuilder;
+import org.apache.http.message.BasicNameValuePair;
+
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.TrustManagerFactory;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.nio.charset.StandardCharsets;
+import java.security.KeyManagementException;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.UnrecoverableKeyException;
+import java.security.cert.CertificateException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.zip.GZIPInputStream;
+
+public class ApiUtil {
+ private static final Gson gson = new Gson();
+ private static final ExecutorService executorService = Executors.newFixedThreadPool(3);
+ private static final String USER_AGENT = "NotEnoughUpdates/" + NotEnoughUpdates.VERSION;
+ private static SSLContext ctx;
+
+ static {
+ try {
+ KeyStore letsEncryptStore = KeyStore.getInstance("JKS");
+ letsEncryptStore.load(ApiUtil.class.getResourceAsStream("/neukeystore.jks"), "neuneu".toCharArray());
+ ctx = SSLContext.getInstance("TLS");
+ KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
+ TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
+ kmf.init(letsEncryptStore, null);
+ tmf.init(letsEncryptStore);
+ ctx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
+ } catch (KeyStoreException | NoSuchAlgorithmException | KeyManagementException | UnrecoverableKeyException |
+ IOException | CertificateException e) {
+ System.out.println("Failed to load NEU keystore. A lot of API requests won't work");
+ e.printStackTrace();
+ }
+ }
+
+ public static class Request {
+
+ private final List<NameValuePair> queryArguments = new ArrayList<>();
+ private String baseUrl = null;
+ private boolean shouldGunzip = false;
+ private String method = "GET";
+
+ public Request method(String method) {
+ this.method = method;
+ return this;
+ }
+
+ public Request url(String baseUrl) {
+ this.baseUrl = baseUrl;
+ return this;
+ }
+
+ public Request queryArgument(String key, String value) {
+ queryArguments.add(new BasicNameValuePair(key, value));
+ return this;
+ }
+
+ public Request queryArguments(Collection<NameValuePair> queryArguments) {
+ this.queryArguments.addAll(queryArguments);
+ return this;
+ }
+
+ public Request gunzip() {
+ shouldGunzip = true;
+ return this;
+ }
+
+ private CompletableFuture<URL> buildUrl() {
+ CompletableFuture<URL> fut = new CompletableFuture<>();
+ try {
+ fut.complete(new URIBuilder(baseUrl)
+ .addParameters(queryArguments)
+ .build()
+ .toURL());
+ } catch (URISyntaxException |
+ MalformedURLException |
+ NullPointerException e) { // Using CompletableFuture as an exception monad, isn't that exiting?
+ fut.completeExceptionally(e);
+ }
+ return fut;
+ }
+
+ public CompletableFuture<String> requestString() {
+ return buildUrl().thenApplyAsync(url -> {
+ try {
+ InputStream inputStream = null;
+ URLConnection conn = null;
+ try {
+ conn = url.openConnection();
+ if (conn instanceof HttpsURLConnection && ctx != null) {
+ HttpsURLConnection sslConn = (HttpsURLConnection) conn;
+ sslConn.setSSLSocketFactory(ctx.getSocketFactory());
+ }
+ if (conn instanceof HttpURLConnection) {
+ ((HttpURLConnection) conn).setRequestMethod(method);
+ }
+ conn.setConnectTimeout(10000);
+ conn.setReadTimeout(10000);
+ conn.setRequestProperty("User-Agent", USER_AGENT);
+
+ inputStream = conn.getInputStream();
+
+ if (shouldGunzip) {
+ inputStream = new GZIPInputStream(inputStream);
+ }
+
+ // While the assumption of UTF8 isn't always true; it *should* always be true.
+ // Not in the sense that this will hold in most cases (although that as well),
+ // but in the sense that any violation of this better have a good reason.
+ return IOUtils.toString(inputStream, StandardCharsets.UTF_8);
+ } finally {
+ try {
+ if (inputStream != null) {
+ inputStream.close();
+ }
+ } finally {
+ if (conn instanceof HttpURLConnection) {
+ ((HttpURLConnection) conn).disconnect();
+ }
+ }
+ }
+ } catch (IOException e) {
+ throw new RuntimeException(e); // We can rethrow, since supplyAsync catches exceptions.
+ }
+ }, executorService);
+ }
+
+ public CompletableFuture<JsonObject> requestJson() {
+ return requestJson(JsonObject.class);
+ }
+
+ public <T> CompletableFuture<T> requestJson(Class<? extends T> clazz) {
+ return requestString().thenApply(str -> gson.fromJson(str, clazz));
+ }
+
+ }
+
+ public Request request() {
+ return new Request();
+ }
+
+ public Request newHypixelApiRequest(String apiPath) {
+ return newAnonymousHypixelApiRequest(apiPath)
+ .queryArgument("key", NotEnoughUpdates.INSTANCE.config.apiData.apiKey);
+ }
+
+ public Request newAnonymousHypixelApiRequest(String apiPath) {
+ return new Request()
+ .url("https://api.hypixel.net/" + apiPath);
+ }
+
+ public Request newMoulberryRequest(String path) {
+ return new Request()
+ .url(getMyApiURL() + path);
+ }
+
+ private String getMyApiURL() {
+ return String.format("https://%s/", NotEnoughUpdates.INSTANCE.config.apiData.moulberryCodesApi);
+ }
+}
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/util/HotmInformation.java b/src/main/java/io/github/moulberry/notenoughupdates/util/HotmInformation.java
index c97a37d9..1968b51e 100644
--- a/src/main/java/io/github/moulberry/notenoughupdates/util/HotmInformation.java
+++ b/src/main/java/io/github/moulberry/notenoughupdates/util/HotmInformation.java
@@ -159,13 +159,11 @@ public class HotmInformation {
public synchronized void requestUpdate(boolean force) {
if (updateTask.isDone() || force) {
- updateTask = neu.manager.hypixelApi.getHypixelApiAsync(
- neu.config.apiData.apiKey,
- "skyblock/profiles",
- new HashMap<String, String>() {{
- put("uuid", Minecraft.getMinecraft().thePlayer.getUniqueID().toString().replace("-", ""));
- }}
- ).thenAccept(this::updateInformation);
+ updateTask = neu.manager.apiUtils
+ .newHypixelApiRequest("skyblock/profiles")
+ .queryArgument("uuid", Minecraft.getMinecraft().thePlayer.getUniqueID().toString().replace("-", ""))
+ .requestJson()
+ .thenAccept(this::updateInformation);
}
}
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/util/HypixelApi.java b/src/main/java/io/github/moulberry/notenoughupdates/util/HypixelApi.java
deleted file mode 100644
index 3e38fd2e..00000000
--- a/src/main/java/io/github/moulberry/notenoughupdates/util/HypixelApi.java
+++ /dev/null
@@ -1,174 +0,0 @@
-/*
- * Copyright (C) 2022 NotEnoughUpdates contributors
- *
- * This file is part of NotEnoughUpdates.
- *
- * NotEnoughUpdates is free software: you can redistribute it
- * and/or modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation, either
- * version 3 of the License, or (at your option) any later version.
- *
- * NotEnoughUpdates is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with NotEnoughUpdates. If not, see <https://www.gnu.org/licenses/>.
- */
-
-package io.github.moulberry.notenoughupdates.util;
-
-import com.google.gson.Gson;
-import com.google.gson.JsonObject;
-import io.github.moulberry.notenoughupdates.NotEnoughUpdates;
-import org.apache.commons.io.IOUtils;
-
-import java.io.IOException;
-import java.io.UnsupportedEncodingException;
-import java.net.ConnectException;
-import java.net.URL;
-import java.net.URLConnection;
-import java.net.URLEncoder;
-import java.nio.charset.StandardCharsets;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.function.Consumer;
-import java.util.zip.GZIPInputStream;
-
-public class HypixelApi {
- private final Gson gson = new Gson();
- private final ExecutorService es = Executors.newFixedThreadPool(3);
-
- public CompletableFuture<JsonObject> getHypixelApiAsync(String apiKey, String method, HashMap<String, String> args) {
- return getApiAsync(generateApiUrl(apiKey, method, args));
- }
-
- public void getHypixelApiAsync(
- String apiKey,
- String method,
- HashMap<String, String> args,
- Consumer<JsonObject> consumer
- ) {
- getHypixelApiAsync(apiKey, method, args, consumer, () -> {
- });
- }
-
- public void getHypixelApiAsync(
- String apiKey,
- String method,
- HashMap<String, String> args,
- Consumer<JsonObject> consumer,
- Runnable error
- ) {
- getApiAsync(generateApiUrl(apiKey, method, args), consumer, error);
- }
-
- private String getMyApiURL() {
- return String.format("https://%s/", NotEnoughUpdates.INSTANCE.config.apiData.moulberryCodesApi);
- }
-
- public CompletableFuture<JsonObject> getApiAsync(String urlS) {
- CompletableFuture<JsonObject> result = new CompletableFuture<>();
- es.submit(() -> {
- try {
- result.complete(getApiSync(urlS));
- } catch (Exception e) {
- result.completeExceptionally(e);
- }
- });
- return result;
- }
-
- public void getApiAsync(String urlS, Consumer<JsonObject> consumer, Runnable error) {
- es.submit(() -> {
- try {
- consumer.accept(getApiSync(urlS));
- } catch (Exception e) {
- error.run();
- }
- });
- }
-
- public void getMyApiAsync(String urlS, Consumer<JsonObject> consumer, Runnable error) {
- es.submit(() -> {
- try {
- consumer.accept(getApiSync(getMyApiURL() + urlS));
- } catch (Exception e) {
- if (NotEnoughUpdates.INSTANCE.config.hidden.dev) {
- e.printStackTrace();
- }
- error.run();
- }
- });
- }
-
- public void getMyApiGZIPAsync(String urlS, Consumer<JsonObject> consumer, Runnable error) {
- es.submit(() -> {
- try {
- consumer.accept(getApiGZIPSync(getMyApiURL() + urlS));
- } catch (Exception e) {
- error.run();
- }
- });
- }
-
- public void getApiGZIPAsync(String urlS, Consumer<JsonObject> consumer, Runnable error) {
- es.submit(() -> {
- try {
- consumer.accept(getApiGZIPSync(urlS));
- } catch (Exception e) {
- error.run();
- }
- });
- }
-
- public JsonObject getApiSync(String urlS) throws IOException {
- URL url = new URL(urlS);
- URLConnection connection = url.openConnection();
- connection.setConnectTimeout(10000);
- connection.setReadTimeout(10000);
-
- String response = IOUtils.toString(connection.getInputStream(), StandardCharsets.UTF_8);
-
- JsonObject json = gson.fromJson(response, JsonObject.class);
- if (json == null) throw new ConnectException("Invalid JSON");
- return json;
- }
-
- public JsonObject getApiGZIPSync(String urlS) throws IOException {
- URL url = new URL(urlS);
- URLConnection connection = url.openConnection();
- connection.setConnectTimeout(10000);
- connection.setReadTimeout(10000);
-
- String response = IOUtils.toString(new GZIPInputStream(connection.getInputStream()), StandardCharsets.UTF_8);
-
- JsonObject json = gson.fromJson(response, JsonObject.class);
- return json;
- }
-
- public String generateApiUrl(String apiKey, String method, HashMap<String, String> args) {
- if (apiKey != null)
- args.put("key", apiKey.trim());
- StringBuilder url = new StringBuilder("https://api.hypixel.net/" + method);
- boolean first = true;
- for (Map.Entry<String, String> entry : args.entrySet()) {
- if (first) {
- url.append("?");
- first = false;
- } else {
- url.append("&");
- }
- try {
- url.append(URLEncoder.encode(entry.getKey(), StandardCharsets.UTF_8.name())).append("=")
- .append(URLEncoder.encode(entry.getValue(), StandardCharsets.UTF_8.name()));
- } catch (UnsupportedEncodingException e) {
- }
- }
- return url.toString();
- }
-}
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/util/PronounDB.java b/src/main/java/io/github/moulberry/notenoughupdates/util/PronounDB.java
index e7286721..625f92f7 100644
--- a/src/main/java/io/github/moulberry/notenoughupdates/util/PronounDB.java
+++ b/src/main/java/io/github/moulberry/notenoughupdates/util/PronounDB.java
@@ -21,50 +21,17 @@ package io.github.moulberry.notenoughupdates.util;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
-import com.google.gson.JsonParseException;
import io.github.moulberry.notenoughupdates.NotEnoughUpdates;
-import io.github.moulberry.notenoughupdates.core.util.StringUtils;
-
-import javax.net.ssl.HttpsURLConnection;
-import javax.net.ssl.KeyManagerFactory;
-import javax.net.ssl.SSLContext;
-import javax.net.ssl.TrustManagerFactory;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.net.URL;
-import java.security.KeyManagementException;
-import java.security.KeyStore;
-import java.security.KeyStoreException;
-import java.security.NoSuchAlgorithmException;
-import java.security.UnrecoverableKeyException;
-import java.security.cert.CertificateException;
+
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
public class PronounDB {
- static SSLContext ctx;
-
- static {
- try {
- KeyStore ks = KeyStore.getInstance("JKS");
- ks.load(PronounDB.class.getResourceAsStream("/pronoundb.jks"), "pronoundb".toCharArray());
- ctx = SSLContext.getInstance("TLS");
- KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
- TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
- kmf.init(ks, null);
- tmf.init(ks);
- ctx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
- } catch (KeyStoreException | NoSuchAlgorithmException | KeyManagementException | UnrecoverableKeyException |
- IOException | CertificateException e) {
- System.out.println("Failed to load keystore. PronounDB requests will probably not work");
- e.printStackTrace();
- }
- }
-
private static boolean isDisabled() {
JsonObject disabled = Constants.DISABLE;
return disabled != null && disabled.has("pronoundb");
@@ -73,22 +40,15 @@ public class PronounDB {
/**
* Returns an Optional, since JVMs can be *very* funky with KeyStore loading
*/
- public static Optional<JsonObject> performPronouning(String platform, String id) {
- if (isDisabled()) return Optional.empty();
- try {
- URL url = new URL("https://pronoundb.org/api/v1/lookup" +
- "?platform=" + StringUtils.urlEncode(platform) +
- "&id=" + StringUtils.urlEncode(id));
- HttpsURLConnection urlConnection = (HttpsURLConnection) url.openConnection();
- urlConnection.setSSLSocketFactory(ctx.getSocketFactory());
- return Optional.of(NotEnoughUpdates.INSTANCE.manager.gson.fromJson(
- new InputStreamReader(urlConnection.getInputStream()),
- JsonObject.class
- ));
- } catch (ClassCastException | IOException | JsonParseException e) {
- System.out.println("Failed to contact PronounDB: " + e);
- return Optional.empty();
- }
+ public static CompletableFuture<Optional<JsonObject>> performPronouning(String platform, String id) {
+ if (isDisabled()) return CompletableFuture.completedFuture(Optional.empty());
+ return NotEnoughUpdates.INSTANCE.manager.apiUtils
+ .request()
+ .url("https://pronoundb.org/api/v1/lookup")
+ .queryArgument("platform", platform)
+ .queryArgument("id", id)
+ .requestJson()
+ .handle((result, ex) -> Optional.ofNullable(result));
}
public enum Pronoun {
@@ -199,23 +159,20 @@ public class PronounDB {
return Optional.empty();
}
- public static Optional<PronounChoice> getPronounsFor(String platform, String name) {
- return performPronouning(platform, name).flatMap(PronounDB::parsePronouns);
+ public static CompletableFuture<Optional<PronounChoice>> getPronounsFor(String platform, String name) {
+ return performPronouning(platform, name).thenApply(it -> it.flatMap(PronounDB::parsePronouns));
}
- public static Optional<PronounChoice> getPronounsFor(UUID minecraftPlayer) {
- return performPronouning("minecraft", minecraftPlayer.toString() /* dashed UUID */)
- .flatMap(PronounDB::parsePronouns);
+ public static CompletableFuture<Optional<PronounChoice>> getPronounsFor(UUID minecraftPlayer) {
+ return getPronounsFor("minecraft", minecraftPlayer.toString() /* dashed UUID */);
}
public static void test() {
- try {
- System.out.println("Pronouning...");
- PronounChoice pronounsFor = getPronounsFor(UUID.fromString("842204e6-6880-487b-ae5a-0595394f9948")).get();
+ System.out.println("Pronouning...");
+ getPronounsFor(UUID.fromString("842204e6-6880-487b-ae5a-0595394f9948")).thenAccept(it -> {
+ PronounChoice pronounsFor = it.get();
pronounsFor.render().forEach(System.out::println);
- } catch (Exception e) {
- e.printStackTrace();
- }
+ });
}
}
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/util/SBInfo.java b/src/main/java/io/github/moulberry/notenoughupdates/util/SBInfo.java
index 4e920aea..f3a09fcc 100644
--- a/src/main/java/io/github/moulberry/notenoughupdates/util/SBInfo.java
+++ b/src/main/java/io/github/moulberry/notenoughupdates/util/SBInfo.java
@@ -61,8 +61,6 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.ExecutionException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -431,11 +429,10 @@ public class SBInfo {
}
public void updateMayor() {
- NotEnoughUpdates.INSTANCE.manager.hypixelApi.getHypixelApiAsync(
- NotEnoughUpdates.INSTANCE.config.apiData.apiKey,
- "resources/skyblock/election",
- new HashMap<>()
- ).thenAcceptAsync(newJson -> mayorJson = newJson);
+ NotEnoughUpdates.INSTANCE.manager.apiUtils
+ .newHypixelApiRequest("resources/skyblock/election")
+ .requestJson()
+ .thenAccept(newJson -> mayorJson = newJson);
}