diff options
| author | Roman / Linnea Gräf <roman.graef@gmail.com> | 2022-09-29 17:25:37 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2022-09-29 17:25:37 +0200 |
| commit | 2dd4a2b36211c380c0bf4e231859dfafd3c04a5b (patch) | |
| tree | 20cde26c05f32699ccb91cda1d37b67f4eea6c62 /src/main/java/io/github/moulberry/notenoughupdates/util | |
| parent | cbcc4c3b4004cbf3f86aeab515ea94a93b4efd1e (diff) | |
| download | notenoughupdates-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')
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); } |
