1
0
mirror of https://github.com/GeyserMC/Floodgate.git synced 2026-01-03 22:16:46 +00:00

Added a dumb hack to make pre 1.12 work on Spigot and removed OkHttp

This commit is contained in:
Tim203
2020-11-22 16:21:49 +01:00
parent 28c23610c0
commit b1a2b04fee
5 changed files with 258 additions and 92 deletions

View File

@@ -25,117 +25,71 @@
package org.geysermc.floodgate.skin;
import com.google.common.net.HttpHeaders;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import javax.imageio.ImageIO;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okio.Buffer;
import okio.BufferedSource;
import org.geysermc.floodgate.util.FloodgateHttpException;
import org.geysermc.floodgate.util.HttpUtils;
import org.geysermc.floodgate.util.HttpUtils.HttpPostResponse;
import org.geysermc.floodgate.util.RawSkin;
public final class SkinUploader {
private static final String UPLOAD_URL = "https://api.mineskin.org/generate/upload";
private static final String USER_AGENT = "GeyserMC/Floodgate";
private static final MediaType PNG_TYPE = MediaType.get("image/png");
private static final Gson GSON = new Gson();
private static final int MAX_TRIES = 3;
private final Executor requestExecutor = Executors.newSingleThreadExecutor();
private final OkHttpClient httpClient =
new OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(20, TimeUnit.SECONDS)
.build();
private long nextResult = 0;
public CompletableFuture<UploadResult> uploadSkin(RawSkin rawSkin) {
return CompletableFuture.supplyAsync(
() -> {
if (System.currentTimeMillis() < nextResult) {
try {
Thread.sleep(nextResult - System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return CompletableFuture.supplyAsync(() -> uploadSkinInner(rawSkin, 0), requestExecutor);
}
SkinModel model = rawSkin.alex ? SkinModel.ALEX : SkinModel.STEVE;
private UploadResult uploadSkinInner(RawSkin rawSkin, int times) {
if (System.currentTimeMillis() < nextResult) {
try {
Thread.sleep(nextResult - System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
BufferedImage image = SkinUtils.toBufferedImage(rawSkin);
// a black 32x32 png was 133 bytes, so we assume that it's at least 133 bytes
ByteArrayOutputStream stream = new ByteArrayOutputStream(133);
try {
ImageIO.write(image, "png", stream);
} catch (IOException exception) {
throw new RuntimeException(exception);
}
SkinModel model = rawSkin.alex ? SkinModel.ALEX : SkinModel.STEVE;
RequestBody fileData = RequestBody.create(PNG_TYPE, stream.toByteArray());
String url = UPLOAD_URL + getUploadUrlParameters(model);
BufferedImage image = SkinUtils.toBufferedImage(rawSkin);
RequestBody body = new MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("file", "skin.png", fileData)
.build();
System.out.println("final file stream size: " + stream.size());
Buffer buffer = new Buffer();
try {
body.writeTo(buffer);
} catch (IOException exception) {
throw new RuntimeException(exception);
}
Request request = new Request.Builder()
.url(UPLOAD_URL + getUploadUrlParameters(model))
.header(HttpHeaders.USER_AGENT, USER_AGENT)
.post(body)
.build();
try (Response response = httpClient.newCall(request).execute()) {
if (response.body() == null) {
throw new RuntimeException("Response didn't have a body!");
}
return parseAndHandleResponse(response.code(), response.body().source());
} catch (IOException exception) {
throw new RuntimeException(exception);
}
},
requestExecutor
);
try {
UploadResult result = parseAndHandleResponse(HttpUtils.post(url, image));
if (result.httpCode == 429) {
times += 1;
if (times >= MAX_TRIES) {
return result;
}
uploadSkinInner(rawSkin, times);
}
return result;
} catch (FloodgateHttpException exception) {
return UploadResult.exception(exception);
}
}
private String getUploadUrlParameters(SkinModel model) {
return "?visibility=1&model=" + model.name;
}
private UploadResult parseAndHandleResponse(int httpCode, BufferedSource response) {
InputStreamReader reader = new InputStreamReader(response.inputStream());
JsonObject jsonResponse = GSON.fromJson(reader, JsonObject.class);
private UploadResult parseAndHandleResponse(HttpPostResponse response) {
int httpCode = response.getHttpCode();
JsonObject jsonResponse = response.getResponse();
if (jsonResponse == null) {
throw new IllegalStateException("Response cannot be null!");
}
System.out.println(jsonResponse.toString());
nextResult = jsonResponse.get("nextRequest").getAsLong();
if (httpCode >= 200 && httpCode < 300) {
@@ -165,14 +119,19 @@ public final class SkinUploader {
public static final class UploadResult {
private final int httpCode;
private final String error;
private final boolean exception;
private final SkinModel model;
private final String skinUrl;
private final String capeUrl;
private final JsonObject response;
public static UploadResult exception(Throwable throwable) {
return new UploadResult(-1, throwable.getMessage(), true, null, null, null, null);
}
public static UploadResult failed(int httpCode, String error) {
return new UploadResult(httpCode, error, SkinModel.STEVE, null, null, null);
return new UploadResult(httpCode, error, false, SkinModel.STEVE, null, null, null);
}
public static UploadResult success(int httpCode, JsonObject body) {
@@ -189,7 +148,7 @@ public final class SkinUploader {
response.addProperty("value", textureData.get("value").getAsString());
response.addProperty("signature", textureData.get("signature").getAsString());
return new UploadResult(httpCode, null, model, skinUrl, capeUrl, response);
return new UploadResult(httpCode, null, false, model, skinUrl, capeUrl, response);
}
}
}

View File

@@ -0,0 +1,42 @@
/*
* Copyright (c) 2019-2020 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Floodgate
*/
package org.geysermc.floodgate.util;
import lombok.Getter;
@Getter
public class FloodgateHttpException extends Exception {
private final int httpCode;
public FloodgateHttpException(String message, Throwable exception, int httpCode) {
super(message, exception);
this.httpCode = httpCode;
}
public FloodgateHttpException(String message, Throwable exception) {
this(message, exception, -1);
}
}

View File

@@ -0,0 +1,155 @@
/*
* Copyright (c) 2019-2020 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Floodgate
*/
package org.geysermc.floodgate.util;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import java.awt.image.BufferedImage;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import javax.imageio.ImageIO;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
@SuppressWarnings("all")
public class HttpUtils {
private static final Gson GSON = new Gson();
private static final String USER_AGENT = "GeyserMC/Floodgate";
private static final String CONNECTION_STRING = "--";
private static final String BOUNDARY = "******";
private static final String END = "\r\n";
public static HttpPostResponse post(String urlString, BufferedImage... images)
throws FloodgateHttpException {
HttpURLConnection connection;
try {
URL url = new URL(urlString);
connection = (HttpURLConnection) url.openConnection();
} catch (Exception e) {
throw new FloodgateHttpException("Failed to create connection", e);
}
OutputStream outputStream = null;
DataOutputStream dataOutputStream = null;
try {
connection.setDoInput(true);
connection.setDoOutput(true);
connection.setUseCaches(false);
connection.setRequestProperty(
"Content-Type",
"multipart/form-data;boundary=" + BOUNDARY
);
outputStream = connection.getOutputStream();
dataOutputStream = new DataOutputStream(outputStream);
writeDataFor(dataOutputStream, images);
} catch (Exception e) {
try {
outputStream.close();
dataOutputStream.close();
} catch (Exception ignored) {
}
throw new FloodgateHttpException("Failed to create request", e);
}
int responseCode = -1;
try {
responseCode = connection.getResponseCode();
} catch (Exception ignored) {
}
InputStream inputStream = null;
InputStreamReader inputStreamReader = null;
try {
inputStream = connection.getInputStream();
inputStreamReader = new InputStreamReader(inputStream);
JsonObject response = GSON.fromJson(inputStreamReader, JsonObject.class);
inputStreamReader.close();
inputStream.close();
dataOutputStream.close();
outputStream.close();
return new HttpPostResponse(responseCode, response);
} catch (Exception e) {
throw new FloodgateHttpException("Failed to read response", e, responseCode);
} finally {
try {
outputStream.close();
dataOutputStream.close();
inputStream.close();
inputStreamReader.close();
} catch (Exception ignored) {
}
}
}
public static void writeDataFor(DataOutputStream outputStream, BufferedImage... images) {
try {
for (int i = 0; i < images.length; i++) {
outputStream.writeBytes(CONNECTION_STRING + BOUNDARY + END);
outputStream.writeBytes(
"Content-Disposition:form-data;name=file;filename=image" + i + ".png");
outputStream.writeBytes(END);
outputStream.writeBytes(END);
fileDataForImage(outputStream, images[i]);
outputStream.writeBytes(END);
}
outputStream.writeBytes(CONNECTION_STRING + BOUNDARY + CONNECTION_STRING + END);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
public static void fileDataForImage(OutputStream outputStream, BufferedImage image) {
try {
ImageIO.write(image, "png", outputStream);
} catch (IOException exception) {
throw new RuntimeException(exception);
}
}
@Getter
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public static final class HttpPostResponse {
private final int httpCode;
private final JsonObject response;
}
}