aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/at/hannibal2/skyhanni/utils/APIUtils.kt
diff options
context:
space:
mode:
authorCalMWolfs <94038482+CalMWolfs@users.noreply.github.com>2024-09-24 00:11:18 +1000
committerGitHub <noreply@github.com>2024-09-23 16:11:18 +0200
commit7c9f889efd9c5d889394f8bf00288ef1c38f8ae4 (patch)
treee8fd52a149573e096fa37389a37dddb47213f545 /src/main/java/at/hannibal2/skyhanni/utils/APIUtils.kt
parentf78d580531941d52bbbeca625c3f078beae8996a (diff)
downloadskyhanni-7c9f889efd9c5d889394f8bf00288ef1c38f8ae4.tar.gz
skyhanni-7c9f889efd9c5d889394f8bf00288ef1c38f8ae4.tar.bz2
skyhanni-7c9f889efd9c5d889394f8bf00288ef1c38f8ae4.zip
Backend: Add Http Request Patching (#2578)
Diffstat (limited to 'src/main/java/at/hannibal2/skyhanni/utils/APIUtils.kt')
-rw-r--r--src/main/java/at/hannibal2/skyhanni/utils/APIUtils.kt213
1 files changed, 213 insertions, 0 deletions
diff --git a/src/main/java/at/hannibal2/skyhanni/utils/APIUtils.kt b/src/main/java/at/hannibal2/skyhanni/utils/APIUtils.kt
new file mode 100644
index 000000000..d28263af6
--- /dev/null
+++ b/src/main/java/at/hannibal2/skyhanni/utils/APIUtils.kt
@@ -0,0 +1,213 @@
+package at.hannibal2.skyhanni.utils
+
+import at.hannibal2.skyhanni.SkyHanniMod
+import at.hannibal2.skyhanni.test.command.ErrorManager
+import com.google.gson.JsonElement
+import com.google.gson.JsonObject
+import com.google.gson.JsonParser
+import com.google.gson.JsonSyntaxException
+import org.apache.http.HttpEntity
+import org.apache.http.client.config.RequestConfig
+import org.apache.http.client.methods.HttpGet
+import org.apache.http.client.methods.HttpPost
+import org.apache.http.entity.ContentType
+import org.apache.http.entity.StringEntity
+import org.apache.http.impl.client.HttpClientBuilder
+import org.apache.http.impl.client.HttpClients
+import org.apache.http.message.BasicHeader
+import org.apache.http.util.EntityUtils
+import java.security.KeyStore
+import javax.net.ssl.HttpsURLConnection
+import javax.net.ssl.KeyManagerFactory
+import javax.net.ssl.SSLContext
+import javax.net.ssl.TrustManagerFactory
+
+object APIUtils {
+
+ private val parser = JsonParser()
+ private var showApiErrors = false
+
+ private val ctx: SSLContext? = run {
+ try {
+ val myKeyStore = KeyStore.getInstance("JKS")
+ myKeyStore.load(APIUtils.javaClass.getResourceAsStream("/keystore.jks"), "changeit".toCharArray())
+ val kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm())
+ val tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
+ kmf.init(myKeyStore, null)
+ tmf.init(myKeyStore)
+ SSLContext.getInstance("TLS").apply {
+ init(kmf.keyManagers, tmf.trustManagers, null)
+ }
+ } catch (e: Exception) {
+ println("Failed to load keystore. A lot of API requests won't work")
+ e.printStackTrace()
+ null
+ }
+ }
+
+ fun patchHttpsRequest(connection: HttpsURLConnection) {
+ ctx?.let {
+ connection.sslSocketFactory = it.socketFactory
+ }
+ }
+
+ data class ApiResponse(val success: Boolean, val message: String?, val data: JsonObject)
+
+ private val builder: HttpClientBuilder =
+ HttpClients.custom().setUserAgent("SkyHanni/${SkyHanniMod.version}")
+ .setDefaultHeaders(
+ mutableListOf(
+ BasicHeader("Pragma", "no-cache"),
+ BasicHeader("Cache-Control", "no-cache"),
+ ),
+ )
+ .setDefaultRequestConfig(
+ RequestConfig.custom()
+ .build(),
+ )
+ .useSystemProperties()
+
+ /**
+ * TODO
+ * make suspend
+ * use withContext(Dispatchers.IO) { APIUtils.getJSONResponse(url) }.asJsonObject
+ */
+ fun getJSONResponse(urlString: String, silentError: Boolean = false) =
+ getJSONResponseAsElement(urlString, silentError) as JsonObject
+
+ fun getJSONResponseAsElement(
+ urlString: String,
+ silentError: Boolean = false,
+ apiName: String = "Hypixel API",
+ ): JsonElement {
+ val client = builder.build()
+ try {
+ client.execute(HttpGet(urlString)).use { response ->
+ val entity = response.entity
+ if (entity != null) {
+ val retSrc = EntityUtils.toString(entity)
+ try {
+ return parser.parse(retSrc)
+ } catch (e: JsonSyntaxException) {
+ val name = e.javaClass.name
+ val message = "$name: ${e.message}"
+ if (e.message?.contains("Use JsonReader.setLenient(true)") == true) {
+ println("MalformedJsonException: Use JsonReader.setLenient(true)")
+ println(" - getJSONResponse: '$urlString'")
+ ChatUtils.debug("MalformedJsonException: Use JsonReader.setLenient(true)")
+ } else if (retSrc.contains("<center><h1>502 Bad Gateway</h1></center>")) {
+ if (showApiErrors && apiName == "Hypixel API") {
+ ChatUtils.clickableChat(
+ "Problems with detecting the Hypixel API. §eClick here to hide this message for now.",
+ onClick = { toggleApiErrorMessages() },
+ "§eClick to run /shtogglehypixelapierrors!",
+ )
+ }
+ ErrorManager.skyHanniError(
+ "SkyHanni Connection Error",
+ "error message" to "$message(502 Bad Gateway)",
+ "apiName" to apiName,
+ "urlString" to urlString,
+ "returnedData" to retSrc,
+ )
+ } else {
+ ErrorManager.skyHanniError(
+ "SkyHanni Connection Error",
+ "error message" to message,
+ "apiName" to apiName,
+ "urlString" to urlString,
+ "returnedData" to retSrc,
+ )
+ }
+ }
+ }
+ }
+ } catch (e: Throwable) {
+ if (silentError) {
+ throw e
+ }
+ val name = e.javaClass.name
+ val message = "$name: ${e.message}"
+ ErrorManager.skyHanniError(
+ "SkyHanni Connection Error",
+ "error message" to message,
+ "urlString" to urlString,
+ )
+ } finally {
+ client.close()
+ }
+ return JsonObject()
+ }
+
+ fun postJSON(urlString: String, body: String, silentError: Boolean = false): ApiResponse {
+ val client = builder.build()
+
+ try {
+ val method = HttpPost(urlString)
+ method.entity = StringEntity(body, ContentType.APPLICATION_JSON)
+
+ client.execute(method).use { response ->
+ val status = response.statusLine
+ val entity = response.entity
+
+ if (status.statusCode in 200..299) {
+ val data = readResponse(entity)
+ return ApiResponse(true, "Request successful", data)
+ }
+
+ val message = "POST request to '$urlString' returned status ${status.statusCode}"
+ ErrorManager.logErrorStateWithData(
+ "Error communicating with API", "APIUtil POST request returned an error code",
+ "statusCode" to status.statusCode,
+ "urlString" to urlString,
+ "body" to body,
+ )
+ return ApiResponse(false, message, JsonObject())
+ }
+ } catch (throwable: Throwable) {
+ if (silentError) {
+ throw throwable
+ }
+ ErrorManager.logErrorWithData(
+ throwable, "SkyHanni ran into an ${throwable::class.simpleName ?: "error"} whilst sending a resource",
+ "urlString" to urlString,
+ "body" to body,
+ )
+ return ApiResponse(false, throwable.message, JsonObject())
+ } finally {
+ client.close()
+ }
+ }
+
+ private fun readResponse(entity: HttpEntity): JsonObject {
+ val retSrc = EntityUtils.toString(entity) ?: return JsonObject()
+ val parsed = parser.parse(retSrc)
+ if (parsed.isJsonNull) return JsonObject()
+ return parsed as JsonObject
+ }
+
+ fun postJSONIsSuccessful(url: String, body: String, silentError: Boolean = false): Boolean {
+ val response = postJSON(url, body, silentError)
+
+ if (response.success) {
+ return true
+ }
+
+ ErrorManager.logErrorStateWithData(
+ "An error occurred during the API request",
+ "unsuccessful API response",
+ "url" to url,
+ "body" to body,
+ "message" to response.message,
+ "response" to response,
+ )
+
+ return false
+ }
+
+ // TODO remove command, use clickable chat message instead
+ fun toggleApiErrorMessages() {
+ showApiErrors = !showApiErrors
+ ChatUtils.chat("Hypixel API error messages " + if (showApiErrors) "§chidden" else "§ashown")
+ }
+}