Split off the boilerplate code

This commit is contained in:
Sollace 2018-07-25 16:31:09 +02:00
parent 51362be542
commit 70f96723e6
8 changed files with 194 additions and 248 deletions

View file

@ -0,0 +1,47 @@
package com.voxelmodpack.hdskins.skins;
import java.io.IOException;
import java.net.URI;
import java.util.Collections;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import javax.annotation.Nullable;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.exceptions.AuthenticationException;
import com.mojang.authlib.minecraft.MinecraftProfileTexture;
import com.mojang.authlib.minecraft.MinecraftProfileTexture.Type;
import com.mojang.authlib.yggdrasil.response.MinecraftTexturesPayload;
import com.voxelmodpack.hdskins.HDSkinManager;
import net.minecraft.util.Session;
public abstract class AbstractSkinServer implements SkinServer {
@Override
public final Optional<MinecraftTexturesPayload> loadProfileData(GameProfile profile) {
return Optional.ofNullable(getProfileData(profile));
}
@Override
public Map<Type, MinecraftProfileTexture> getPreviewTextures(GameProfile profile) {
return loadProfileData(profile)
.map(MinecraftTexturesPayload::getTextures)
.orElse(Collections.emptyMap());
}
@Override
public final CompletableFuture<SkinUploadResponse> uploadSkin(Session session, @Nullable URI image, Type type, Map<String, String> metadata) {
return CallableFutures.asyncFailableFuture(() -> {
return doUpload(session, image, type, metadata);
}, HDSkinManager.skinUploadExecutor);
}
protected abstract MinecraftTexturesPayload getProfileData(GameProfile profile);
protected abstract SkinUploadResponse doUpload(Session session, URI image, Type type, Map<String, String> metadata) throws AuthenticationException, IOException;
}

View file

@ -1,26 +1,24 @@
package com.voxelmodpack.hdskins.skins;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMap.Builder;
import com.google.gson.JsonObject;
import com.google.gson.annotations.Expose;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.minecraft.MinecraftProfileTexture.Type;
import com.mojang.authlib.yggdrasil.response.MinecraftTexturesPayload;
import com.mojang.util.UUIDTypeAdapter;
import com.voxelmodpack.hdskins.HDSkinManager;
import net.minecraft.util.Session;
import org.apache.commons.lang3.builder.ToStringBuilder;
import java.io.IOException;
import java.net.URI;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import org.apache.commons.lang3.builder.ToStringBuilder;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMap.Builder;
import com.google.gson.annotations.Expose;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.exceptions.AuthenticationException;
import com.mojang.authlib.minecraft.MinecraftProfileTexture.Type;
import com.mojang.authlib.yggdrasil.response.MinecraftTexturesPayload;
import com.mojang.util.UUIDTypeAdapter;
import net.minecraft.util.Session;
@ServerType("bethlehem")
public class BethlehemSkinServer implements SkinServer {
public class BethlehemSkinServer extends AbstractSkinServer {
private static final String SERVER_ID = "7853dfddc358333843ad55a2c7485c4aa0380a51";
@ -32,28 +30,24 @@ public class BethlehemSkinServer implements SkinServer {
}
@Override
public Optional<MinecraftTexturesPayload> loadProfileData(GameProfile profile) {
NetClient client = new NetClient("GET", getPath(profile));
protected MinecraftTexturesPayload getProfileData(GameProfile profile) {
try (NetClient client = new NetClient("GET", getPath(profile))) {
if (!client.send()) {
return null;
}
String json = client.getResponseText();
JsonObject s = gson.fromJson(json, JsonObject.class);
if (s.has("success") && s.get("success").getAsBoolean()) {
s = s.get("data").getAsJsonObject();
return Optional.ofNullable(gson.fromJson(s, MinecraftTexturesPayload.class));
return gson.fromJson(client.getResponseText(), MinecraftTexturesPayload.class);
} catch (IOException e) {
return null;
}
return Optional.empty();
}
@Override
public CompletableFuture<SkinUploadResponse> uploadSkin(Session session, URI image, Type type, Map<String, String> metadata) {
return CallableFutures.asyncFailableFuture(() -> {
SkinServer.verifyServerConnection(session, SERVER_ID);
protected SkinUploadResponse doUpload(Session session, URI image, Type type, Map<String, String> metadata) throws AuthenticationException, IOException {
SkinServer.verifyServerConnection(session, SERVER_ID);
NetClient client = new NetClient("POST", address).putHeaders(createHeaders(session, type, image, metadata));
try (NetClient client = new NetClient("POST", address)) {
client.putHeaders(createHeaders(session, type, image, metadata));
if (image != null) {
client.putFile(type.toString().toLowerCase(Locale.US), "image/png", image);
@ -64,7 +58,7 @@ public class BethlehemSkinServer implements SkinServer {
}
return new SkinUploadResponse(client.getResponseText());
}, HDSkinManager.skinUploadExecutor);
}
}
protected Map<String, ?> createHeaders(Session session, Type type, URI image, Map<String, String> metadata) {

View file

@ -17,10 +17,4 @@ public class CallableFutures {
});
return ret;
}
public static <T> CompletableFuture<T> failedFuture(Exception e) {
CompletableFuture<T> ret = new CompletableFuture<>();
ret.completeExceptionally(e);
return ret;
}
}

View file

@ -2,33 +2,30 @@ package com.voxelmodpack.hdskins.skins;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMap.Builder;
import com.google.gson.annotations.Expose;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.exceptions.AuthenticationException;
import com.mojang.authlib.minecraft.MinecraftProfileTexture;
import com.mojang.authlib.minecraft.MinecraftProfileTexture.Type;
import com.mojang.authlib.yggdrasil.response.MinecraftTexturesPayload;
import com.mojang.util.UUIDTypeAdapter;
import com.voxelmodpack.hdskins.HDSkinManager;
import com.voxelmodpack.hdskins.upload.ThreadMultipartPostUpload;
import net.minecraft.util.Session;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URL;
import java.util.Collections;
import java.util.EnumMap;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import javax.annotation.Nullable;
@ServerType("legacy")
public class LegacySkinServer implements SkinServer {
public class LegacySkinServer extends AbstractSkinServer {
private static final String SERVER_ID = "7853dfddc358333843ad55a2c7485c4aa0380a51";
@ -36,6 +33,7 @@ public class LegacySkinServer implements SkinServer {
@Expose
private final String address;
@Expose
private final String gateway;
@ -45,28 +43,33 @@ public class LegacySkinServer implements SkinServer {
}
@Override
public Map<MinecraftProfileTexture.Type, MinecraftProfileTexture> getPreviewTextures(GameProfile profile) {
public Map<Type, MinecraftProfileTexture> getPreviewTextures(GameProfile profile) {
if (Strings.isNullOrEmpty(gateway)) {
return Collections.emptyMap();
}
Map<MinecraftProfileTexture.Type, MinecraftProfileTexture> map = new EnumMap<>(MinecraftProfileTexture.Type.class);
for (MinecraftProfileTexture.Type type : MinecraftProfileTexture.Type.values()) {
Map<Type, MinecraftProfileTexture> map = new EnumMap<>(Type.class);
for (Type type : Type.values()) {
map.put(type, new MinecraftProfileTexture(getPath(gateway, type, profile), null));
}
return map;
}
@SuppressWarnings("deprecation")
@Override
public Optional<MinecraftTexturesPayload> loadProfileData(GameProfile profile) {
ImmutableMap.Builder<MinecraftProfileTexture.Type, MinecraftProfileTexture> builder = ImmutableMap.builder();
for (MinecraftProfileTexture.Type type : MinecraftProfileTexture.Type.values()) {
protected MinecraftTexturesPayload getProfileData(GameProfile profile) {
ImmutableMap.Builder<Type, MinecraftProfileTexture> builder = ImmutableMap.builder();
for (Type type : Type.values()) {
String url = getPath(address, type, profile);
try {
HttpURLConnection urlConnection = (HttpURLConnection) new URL(url).openConnection();
if (urlConnection.getResponseCode() / 100 != 2) {
throw new IOException("Bad response code: " + urlConnection.getResponseCode());
try (NetClient client = new NetClient("GET", url)) {
if (!client.send()) {
throw new IOException("Bad response code: " + client.getResponseCode());
}
builder.put(type, new MinecraftProfileTexture(url, null));
logger.debug("Found skin for {} at {}", profile.getName(), url);
} catch (IOException e) {
@ -74,58 +77,59 @@ public class LegacySkinServer implements SkinServer {
}
}
Map<MinecraftProfileTexture.Type, MinecraftProfileTexture> map = builder.build();
Map<Type, MinecraftProfileTexture> map = builder.build();
if (map.isEmpty()) {
logger.debug("No textures found for {} at {}", profile, address);
return Optional.empty();
return null;
}
return Optional.of(TexturesPayloadBuilder.createTexturesPayload(profile, map));
return TexturesPayloadBuilder.createTexturesPayload(profile, map);
}
@SuppressWarnings("deprecation")
@Override
public CompletableFuture<SkinUploadResponse> uploadSkin(Session session, @Nullable URI image, MinecraftProfileTexture.Type type, Map<String, String> metadata) {
if (Strings.isNullOrEmpty(gateway)) {
return CallableFutures.failedFuture(new NullPointerException("gateway url is blank"));
}
protected SkinUploadResponse doUpload(Session session, URI image, Type type, Map<String, String> metadata) throws AuthenticationException, IOException {
SkinServer.verifyServerConnection(session, SERVER_ID);
return CallableFutures.asyncFailableFuture(() -> {
SkinServer.verifyServerConnection(session, SERVER_ID);
String model = metadata.getOrDefault("model", "default");
Map<String, ?> data = image == null ? getClearData(session, type) : getUploadData(session, type, model, image);
ThreadMultipartPostUpload upload = new ThreadMultipartPostUpload(gateway, data);
String response = upload.uploadMultipart();
if (response.startsWith("ERROR: ")) {
try (NetClient client = new NetClient("POST", address)) {
client.putHeaders(createHeaders(session, type, image, metadata));
if (image != null) {
client.putFile(type.toString().toLowerCase(Locale.US), "image/png", image);
}
String response = client.getResponseText();
if (response.startsWith("ERROR: ")) { // lol @ "ERROR: OK"
response = response.substring(7);
}
if (!response.equalsIgnoreCase("OK") && !response.endsWith("OK"))
throw new IOException(response);
return new SkinUploadResponse(response);
}, HDSkinManager.skinUploadExecutor);
}
}
private static Map<String, ?> getData(Session session, MinecraftProfileTexture.Type type, String model, String param, Object val) {
return ImmutableMap.of(
"user", session.getUsername(),
"uuid", UUIDTypeAdapter.fromUUID(session.getProfile().getId()),
"type", type.toString().toLowerCase(Locale.US),
"model", model,
param, val);
protected Map<String, ?> createHeaders(Session session, Type type, URI image, Map<String, String> metadata) {
Builder<String, Object> builder = ImmutableMap.<String, Object>builder()
.put("user", session.getUsername())
.put("uuid", UUIDTypeAdapter.fromUUID(session.getProfile().getId()))
.put("type", type.toString().toLowerCase(Locale.US));
if (image == null) {
builder.put("clear", "1");
} else {
builder.put("model", metadata.getOrDefault("mode", "default"));
}
return builder.build();
}
private static Map<String, ?> getClearData(Session session, MinecraftProfileTexture.Type type) {
return getData(session, type, "default", "clear", "1");
}
private static Map<String, ?> getUploadData(Session session, MinecraftProfileTexture.Type type, String model, URI skinFile) {
return getData(session, type, model, type.toString().toLowerCase(Locale.US), skinFile);
}
private static String getPath(String address, MinecraftProfileTexture.Type type, GameProfile profile) {
private static String getPath(String address, Type type, GameProfile profile) {
String uuid = UUIDTypeAdapter.fromUUID(profile.getId());
String path = type.toString().toLowerCase() + "s";
return String.format("%s/%s/%s.png", address, path, uuid);
}

View file

@ -1,6 +1,7 @@
package com.voxelmodpack.hdskins.skins;
import java.io.BufferedReader;
import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
@ -15,21 +16,36 @@ import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.methods.RequestBuilder;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
/**
* Ew. Why so many builders? >.<
*/
public class NetClient {
public class NetClient implements Closeable {
private final RequestBuilder rqBuilder;
private CloseableHttpClient client;
private RequestBuilder rqBuilder;
private Map<String, ?> headers;
private CloseableHttpResponse response = null;
public NetClient(String method, String uri) {
start(method, uri);
}
public NetClient start(String method, String uri) {
rqBuilder = RequestBuilder.create(method).setUri(uri);
headers = null;
if (response != null) {
IOUtils.closeQuietly(response);
response = null;
}
return this;
}
public NetClient putFile(String key, String contentType, URI file) {
@ -50,12 +66,18 @@ public class NetClient {
public boolean send() {
HttpUriRequest request = rqBuilder.build();
for (Map.Entry<String, ?> parameter : headers.entrySet()) {
request.addHeader(parameter.getKey(), parameter.getValue().toString());
if (headers != null) {
for (Map.Entry<String, ?> parameter : headers.entrySet()) {
request.addHeader(parameter.getKey(), parameter.getValue().toString());
}
}
if (client == null) {
client = HttpClients.createSystem();
}
try {
response = HttpClients.createSystem().execute(request);
response = client.execute(request);
return getResponseCode() == HttpStatus.SC_OK;
} catch (IOException e) { }
@ -78,11 +100,7 @@ public class NetClient {
}
}
BufferedReader reader = null;
try {
reader = new BufferedReader(new InputStreamReader(response.getEntity().getContent()));
try (BufferedReader reader = new BufferedReader(new InputStreamReader(response.getEntity().getContent()))) {
StringBuilder builder = new StringBuilder();
int ch;
@ -93,10 +111,21 @@ public class NetClient {
return builder.toString();
} catch (IOException e) {
} finally {
IOUtils.closeQuietly(reader);
}
return "";
}
@Override
public void close() throws IOException {
if (response != null) {
IOUtils.closeQuietly(response);
response = null;
}
if (client != null) {
IOUtils.closeQuietly(client);
client = null;
}
}
}

View file

@ -15,7 +15,6 @@ import net.minecraft.client.Minecraft;
import net.minecraft.util.Session;
import java.net.URI;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@ -36,13 +35,10 @@ public interface SkinServer extends Exposable {
Optional<MinecraftTexturesPayload> loadProfileData(GameProfile profile);
default Map<MinecraftProfileTexture.Type, MinecraftProfileTexture> getPreviewTextures(GameProfile profile) {
return loadProfileData(profile).map(MinecraftTexturesPayload::getTextures).orElse(Collections.emptyMap());
}
Map<MinecraftProfileTexture.Type, MinecraftProfileTexture> getPreviewTextures(GameProfile profile);
CompletableFuture<SkinUploadResponse> uploadSkin(Session session, @Nullable URI image, MinecraftProfileTexture.Type type, Map<String, String> metadata);
public static void verifyServerConnection(Session session, String serverId) throws AuthenticationException {
MinecraftSessionService service = Minecraft.getMinecraft().getSessionService();
service.joinServer(session.getProfile(), session.getToken(), serverId);

View file

@ -6,9 +6,12 @@ import com.google.gson.annotations.Expose;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.exceptions.AuthenticationException;
import com.mojang.authlib.minecraft.MinecraftProfileTexture;
import com.mojang.authlib.minecraft.MinecraftProfileTexture.Type;
import com.mojang.authlib.yggdrasil.response.MinecraftTexturesPayload;
import com.mojang.util.UUIDTypeAdapter;
import com.voxelmodpack.hdskins.HDSkinManager;
import javax.annotation.Nullable;
import net.minecraft.client.Minecraft;
import net.minecraft.util.Session;
import org.apache.commons.lang3.builder.ToStringBuilder;
@ -34,14 +37,10 @@ import java.io.Reader;
import java.net.URI;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import javax.annotation.Nullable;
@ServerType("valhalla")
public class ValhallaSkinServer implements SkinServer {
public class ValhallaSkinServer extends AbstractSkinServer {
@Expose
private final String address;
@ -53,44 +52,39 @@ public class ValhallaSkinServer implements SkinServer {
}
@Override
public Optional<MinecraftTexturesPayload> loadProfileData(GameProfile profile) {
protected MinecraftTexturesPayload getProfileData(GameProfile profile) {
try (CloseableHttpClient client = HttpClients.createSystem();
CloseableHttpResponse response = client.execute(new HttpGet(getTexturesURI(profile)))) {
if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
return Optional.of(readJson(response, MinecraftTexturesPayload.class));
return readJson(response, MinecraftTexturesPayload.class);
}
} catch (IOException e) {
e.printStackTrace();
}
return Optional.empty();
return null;
}
@Override
public CompletableFuture<SkinUploadResponse> uploadSkin(Session session, @Nullable URI image, MinecraftProfileTexture.Type type, Map<String, String> metadata) {
return CallableFutures.asyncFailableFuture(() -> {
try (CloseableHttpClient client = HttpClients.createSystem()) {
authorize(client, session);
protected SkinUploadResponse doUpload(Session session, URI image, Type type, Map<String, String> metadata) throws AuthenticationException, IOException {
try (CloseableHttpClient client = HttpClients.createSystem()) {
authorize(client, session);
try {
try {
return upload(client, session, image, type, metadata);
} catch (IOException e) {
if (e.getMessage().equals("Authorization failed")) {
accessToken = null;
authorize(client, session);
return upload(client, session, image, type, metadata);
} catch (IOException e) {
if (e.getMessage().equals("Authorization failed")) {
accessToken = null;
authorize(client, session);
return upload(client, session, image, type, metadata);
}
throw e;
}
throw e;
}
}, HDSkinManager.skinUploadExecutor);
}
}
private SkinUploadResponse upload(CloseableHttpClient client, Session session, @Nullable URI image,
MinecraftProfileTexture.Type type, Map<String, String> metadata)
throws IOException {
private SkinUploadResponse upload(CloseableHttpClient client, Session session, @Nullable URI image, MinecraftProfileTexture.Type type, Map<String, String> metadata) throws IOException {
GameProfile profile = session.getProfile();
if (image == null) {
@ -151,7 +145,7 @@ public class ValhallaSkinServer implements SkinServer {
}
GameProfile profile = session.getProfile();
String token = session.getToken();
AuthHandshake handshake = authHandshake(client, profile.getName());
if (handshake.offline) {
@ -159,7 +153,7 @@ public class ValhallaSkinServer implements SkinServer {
}
// join the session server
Minecraft.getMinecraft().getSessionService().joinServer(profile, token, handshake.serverId);
Minecraft.getMinecraft().getSessionService().joinServer(profile, session.getToken(), handshake.serverId);
AuthResponse response = authResponse(client, profile.getName(), handshake.verifyToken);
if (!response.userId.equals(profile.getId())) {
@ -232,7 +226,7 @@ public class ValhallaSkinServer implements SkinServer {
public String toString() {
return new ToStringBuilder(this, IndentedToStringStyle.INSTANCE)
.append("address", address)
.toString();
.build();
}
@SuppressWarnings("WeakerAccess")
@ -248,6 +242,5 @@ public class ValhallaSkinServer implements SkinServer {
private String accessToken;
private UUID userId;
}
}

View file

@ -1,111 +0,0 @@
package com.voxelmodpack.hdskins.upload;
import org.apache.commons.io.IOUtils;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Map;
import java.util.Map.Entry;
import javax.annotation.Nullable;
/**
* Uploader for Multipart form data
*
* @author Adam Mummery-Smith
* @deprecated Use httpmime multipart upload
*/
@Deprecated
public class ThreadMultipartPostUpload {
protected final Map<String, ?> sourceData;
protected final String method;
protected final String authorization;
protected final String urlString;
protected HttpURLConnection httpClient;
protected static final String CRLF = "\r\n";
protected static final String twoHyphens = "--";
protected static final String boundary = "----------AaB03x";
public String response;
public ThreadMultipartPostUpload(String method, String url, Map<String, ?> sourceData, @Nullable String authorization) {
this.method = method;
this.urlString = url;
this.sourceData = sourceData;
this.authorization = authorization;
}
public ThreadMultipartPostUpload(String url, Map<String, ?> sourceData) {
this("POST", url, sourceData, null);
}
public String uploadMultipart() throws IOException {
// open a URL connection
URL url = new URL(this.urlString);
// Open a HTTP connection to the URL
this.httpClient = (HttpURLConnection) url.openConnection();
this.httpClient.setDoOutput(true);
this.httpClient.setUseCaches(false);
this.httpClient.setRequestMethod(this.method);
this.httpClient.setRequestProperty("Connection", "Close");
this.httpClient.addRequestProperty("User-Agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0)"); // For CloudFlare
if (this.sourceData.size() > 0) {
this.httpClient.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary);
}
if (this.authorization != null) {
this.httpClient.addRequestProperty("Authorization", this.authorization);
}
try (DataOutputStream outputStream = new DataOutputStream(this.httpClient.getOutputStream())) {
for (Entry<String, ?> data : this.sourceData.entrySet()) {
outputStream.writeBytes(twoHyphens + boundary + CRLF);
String paramName = data.getKey();
Object paramData = data.getValue();
if (paramData instanceof URI) {
Path uploadPath = Paths.get((URI) paramData);
outputStream.writeBytes("Content-Disposition: form-data; name=\"" + paramName + "\"; filename=\"" + uploadPath.getFileName() + "\"" + CRLF);
outputStream.writeBytes("Content-Type: image/png" + CRLF + CRLF);
Files.copy(uploadPath, outputStream);
} else {
outputStream.writeBytes("Content-Disposition: form-data; name=\"" + paramName + "\"" + CRLF + CRLF);
outputStream.writeBytes(paramData.toString());
}
outputStream.writeBytes(ThreadMultipartPostUpload.CRLF);
}
outputStream.writeBytes(twoHyphens + boundary + twoHyphens + CRLF);
}
try (InputStream input = this.httpClient.getInputStream()) {
return IOUtils.toString(input, StandardCharsets.UTF_8);
}
}
}