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:
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
155
common/src/main/java/org/geysermc/floodgate/util/HttpUtils.java
Normal file
155
common/src/main/java/org/geysermc/floodgate/util/HttpUtils.java
Normal 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user