mirror of
https://github.com/MineLittlePony/MineLittlePony.git
synced 2025-02-13 16:24:23 +01:00
SkinServer updates (#83)
SkinServer update: * loadProfileData was unwrapped from Optional. Now it throws an exception. * getPreviewTextures is now a CompletableFuture so it won't lag GuiSkins * arguments for uploadSkin were condensed into a single object * Added a class wrapper for getting different http response types * HttpClient is now a singleton which lives in SkinServer.httpClient
This commit is contained in:
parent
1369665fd6
commit
f8ee05ca11
11 changed files with 247 additions and 197 deletions
|
@ -9,16 +9,11 @@ import com.google.common.collect.HashBiMap;
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
import com.google.common.collect.Maps;
|
import com.google.common.collect.Maps;
|
||||||
import com.google.common.util.concurrent.ListeningExecutorService;
|
|
||||||
import com.google.common.util.concurrent.MoreExecutors;
|
|
||||||
import com.google.gson.Gson;
|
|
||||||
import com.google.gson.GsonBuilder;
|
|
||||||
import com.mojang.authlib.GameProfile;
|
import com.mojang.authlib.GameProfile;
|
||||||
import com.mojang.authlib.minecraft.MinecraftProfileTexture;
|
import com.mojang.authlib.minecraft.MinecraftProfileTexture;
|
||||||
import com.mojang.authlib.minecraft.MinecraftProfileTexture.Type;
|
import com.mojang.authlib.minecraft.MinecraftProfileTexture.Type;
|
||||||
import com.mojang.authlib.properties.Property;
|
import com.mojang.authlib.properties.Property;
|
||||||
import com.mojang.authlib.yggdrasil.response.MinecraftTexturesPayload;
|
import com.mojang.authlib.yggdrasil.response.MinecraftTexturesPayload;
|
||||||
import com.mojang.util.UUIDTypeAdapter;
|
|
||||||
import com.mumfrey.liteloader.core.LiteLoader;
|
import com.mumfrey.liteloader.core.LiteLoader;
|
||||||
import com.mumfrey.liteloader.util.log.LiteLoaderLogger;
|
import com.mumfrey.liteloader.util.log.LiteLoaderLogger;
|
||||||
import com.voxelmodpack.hdskins.gui.GuiSkins;
|
import com.voxelmodpack.hdskins.gui.GuiSkins;
|
||||||
|
@ -39,6 +34,9 @@ import net.minecraft.client.resources.IResourceManagerReloadListener;
|
||||||
import net.minecraft.client.resources.SkinManager.SkinAvailableCallback;
|
import net.minecraft.client.resources.SkinManager.SkinAvailableCallback;
|
||||||
import net.minecraft.util.ResourceLocation;
|
import net.minecraft.util.ResourceLocation;
|
||||||
import org.apache.commons.io.FileUtils;
|
import org.apache.commons.io.FileUtils;
|
||||||
|
import org.apache.http.impl.client.CloseableHttpClient;
|
||||||
|
import org.apache.http.impl.client.HttpClients;
|
||||||
|
import org.apache.logging.log4j.LogManager;
|
||||||
|
|
||||||
import java.awt.Graphics;
|
import java.awt.Graphics;
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
|
@ -53,6 +51,7 @@ import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
@ -62,13 +61,11 @@ import javax.annotation.Nonnull;
|
||||||
|
|
||||||
public final class HDSkinManager implements IResourceManagerReloadListener {
|
public final class HDSkinManager implements IResourceManagerReloadListener {
|
||||||
|
|
||||||
private static final ResourceLocation LOADING = new ResourceLocation("LOADING");
|
public static final ExecutorService skinUploadExecutor = Executors.newSingleThreadExecutor();
|
||||||
private static final Gson GSON = new GsonBuilder()
|
public static final ExecutorService skinDownloadExecutor = Executors.newFixedThreadPool(8);
|
||||||
.registerTypeAdapter(UUID.class, new UUIDTypeAdapter())
|
public static final CloseableHttpClient httpClient = HttpClients.createSystem();
|
||||||
.create();
|
|
||||||
|
|
||||||
private static final ExecutorService skinDownloadExecutor = Executors.newFixedThreadPool(8);
|
private static final ResourceLocation LOADING = new ResourceLocation("LOADING");
|
||||||
public static final ListeningExecutorService skinUploadExecutor = MoreExecutors.listeningDecorator(Executors.newSingleThreadExecutor());
|
|
||||||
|
|
||||||
public static final HDSkinManager INSTANCE = new HDSkinManager();
|
public static final HDSkinManager INSTANCE = new HDSkinManager();
|
||||||
|
|
||||||
|
@ -133,7 +130,7 @@ public final class HDSkinManager implements IResourceManagerReloadListener {
|
||||||
Property textures = Iterables.getFirst(profile1.getProperties().get("textures"), null);
|
Property textures = Iterables.getFirst(profile1.getProperties().get("textures"), null);
|
||||||
if (textures != null) {
|
if (textures != null) {
|
||||||
String json = new String(Base64.getDecoder().decode(textures.getValue()), StandardCharsets.UTF_8);
|
String json = new String(Base64.getDecoder().decode(textures.getValue()), StandardCharsets.UTF_8);
|
||||||
MinecraftTexturesPayload texturePayload = GSON.fromJson(json, MinecraftTexturesPayload.class);
|
MinecraftTexturesPayload texturePayload = SkinServer.gson.fromJson(json, MinecraftTexturesPayload.class);
|
||||||
if (texturePayload != null) {
|
if (texturePayload != null) {
|
||||||
// name is optional
|
// name is optional
|
||||||
String name = texturePayload.getProfileName();
|
String name = texturePayload.getProfileName();
|
||||||
|
@ -216,10 +213,13 @@ public final class HDSkinManager implements IResourceManagerReloadListener {
|
||||||
private Map<Type, MinecraftProfileTexture> loadProfileData(GameProfile profile) {
|
private Map<Type, MinecraftProfileTexture> loadProfileData(GameProfile profile) {
|
||||||
Map<Type, MinecraftProfileTexture> textures = Maps.newEnumMap(Type.class);
|
Map<Type, MinecraftProfileTexture> textures = Maps.newEnumMap(Type.class);
|
||||||
for (SkinServer server : skinServers) {
|
for (SkinServer server : skinServers) {
|
||||||
Optional<MinecraftTexturesPayload> profileData = server.loadProfileData(profile);
|
try {
|
||||||
profileData.map(MinecraftTexturesPayload::getTextures).ifPresent(it -> it.forEach(textures::putIfAbsent));
|
server.loadProfileData(profile).getTextures().forEach(textures::putIfAbsent);
|
||||||
if (textures.size() == Type.values().length) {
|
if (textures.size() == Type.values().length) {
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
LogManager.getLogger().trace(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -263,8 +263,8 @@ public final class HDSkinManager implements IResourceManagerReloadListener {
|
||||||
this.enabled = enabled;
|
this.enabled = enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static PreviewTextureManager getPreviewTextureManager(GameProfile profile) {
|
public static CompletableFuture<PreviewTextureManager> getPreviewTextureManager(GameProfile profile) {
|
||||||
return new PreviewTextureManager(INSTANCE.getGatewayServer().getPreviewTextures(profile));
|
return INSTANCE.getGatewayServer().getPreviewTextures(profile).thenApply(PreviewTextureManager::new);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addClearListener(ISkinCacheClearListener listener) {
|
public void addClearListener(ISkinCacheClearListener listener) {
|
||||||
|
|
|
@ -2,7 +2,7 @@ package com.voxelmodpack.hdskins;
|
||||||
|
|
||||||
import com.google.common.collect.Maps;
|
import com.google.common.collect.Maps;
|
||||||
import com.mojang.authlib.minecraft.MinecraftProfileTexture;
|
import com.mojang.authlib.minecraft.MinecraftProfileTexture;
|
||||||
import net.minecraft.client.Minecraft;
|
import com.mojang.authlib.yggdrasil.response.MinecraftTexturesPayload;
|
||||||
import net.minecraft.client.renderer.IImageBuffer;
|
import net.minecraft.client.renderer.IImageBuffer;
|
||||||
import net.minecraft.client.resources.SkinManager;
|
import net.minecraft.client.resources.SkinManager;
|
||||||
import net.minecraft.util.ResourceLocation;
|
import net.minecraft.util.ResourceLocation;
|
||||||
|
@ -20,8 +20,8 @@ public class PreviewTextureManager {
|
||||||
|
|
||||||
private final Map<MinecraftProfileTexture.Type, MinecraftProfileTexture> textures;
|
private final Map<MinecraftProfileTexture.Type, MinecraftProfileTexture> textures;
|
||||||
|
|
||||||
PreviewTextureManager(Map<MinecraftProfileTexture.Type, MinecraftProfileTexture> textures) {
|
PreviewTextureManager(MinecraftTexturesPayload payload) {
|
||||||
this.textures = textures;
|
this.textures = payload.getTextures();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
|
@ -47,7 +47,8 @@ public class PreviewTextureManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} : null);
|
} : null);
|
||||||
Minecraft.getMinecraft().getTextureManager().loadTexture(location, skinTexture);
|
|
||||||
|
TextureLoader.loadTexture(location, skinTexture);
|
||||||
|
|
||||||
return skinTexture;
|
return skinTexture;
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,6 @@ import com.voxelmodpack.hdskins.DynamicTextureImage;
|
||||||
import com.voxelmodpack.hdskins.HDSkinManager;
|
import com.voxelmodpack.hdskins.HDSkinManager;
|
||||||
import com.voxelmodpack.hdskins.ImageBufferDownloadHD;
|
import com.voxelmodpack.hdskins.ImageBufferDownloadHD;
|
||||||
import com.voxelmodpack.hdskins.PreviewTexture;
|
import com.voxelmodpack.hdskins.PreviewTexture;
|
||||||
import com.voxelmodpack.hdskins.PreviewTextureManager;
|
|
||||||
import net.minecraft.client.Minecraft;
|
import net.minecraft.client.Minecraft;
|
||||||
import net.minecraft.client.renderer.texture.DynamicTexture;
|
import net.minecraft.client.renderer.texture.DynamicTexture;
|
||||||
import net.minecraft.client.renderer.texture.TextureManager;
|
import net.minecraft.client.renderer.texture.TextureManager;
|
||||||
|
@ -25,6 +24,7 @@ import java.awt.image.BufferedImage;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import javax.imageio.ImageIO;
|
import javax.imageio.ImageIO;
|
||||||
|
|
||||||
@SuppressWarnings("EntityConstructor")
|
@SuppressWarnings("EntityConstructor")
|
||||||
|
@ -41,11 +41,11 @@ public class EntityPlayerModel extends EntityLivingBase {
|
||||||
EntityEquipmentSlot.MAINHAND, ItemStack.EMPTY
|
EntityEquipmentSlot.MAINHAND, ItemStack.EMPTY
|
||||||
));
|
));
|
||||||
|
|
||||||
private PreviewTexture remoteSkinTexture;
|
private volatile PreviewTexture remoteSkinTexture;
|
||||||
private ResourceLocation remoteSkinResource;
|
private ResourceLocation remoteSkinResource;
|
||||||
protected ResourceLocation localSkinResource;
|
protected ResourceLocation localSkinResource;
|
||||||
private DynamicTexture localSkinTexture;
|
private DynamicTexture localSkinTexture;
|
||||||
private PreviewTexture remoteElytraTexture;
|
private volatile PreviewTexture remoteElytraTexture;
|
||||||
private ResourceLocation remoteElytraResource;
|
private ResourceLocation remoteElytraResource;
|
||||||
private ResourceLocation localElytraResource;
|
private ResourceLocation localElytraResource;
|
||||||
private DynamicTexture localElytraTexture;
|
private DynamicTexture localElytraTexture;
|
||||||
|
@ -77,10 +77,11 @@ public class EntityPlayerModel extends EntityLivingBase {
|
||||||
this.textureManager.deleteTexture(this.remoteElytraResource);
|
this.textureManager.deleteTexture(this.remoteElytraResource);
|
||||||
}
|
}
|
||||||
|
|
||||||
PreviewTextureManager ptm = HDSkinManager.getPreviewTextureManager(this.profile);
|
HDSkinManager.getPreviewTextureManager(this.profile).thenAccept(ptm -> {
|
||||||
|
this.remoteSkinTexture = ptm.getPreviewTexture(this.remoteSkinResource, Type.SKIN, getBlankSkin(), listener);
|
||||||
|
this.remoteElytraTexture = ptm.getPreviewTexture(this.remoteElytraResource, Type.ELYTRA, getBlankElytra(), null);
|
||||||
|
});
|
||||||
|
|
||||||
this.remoteSkinTexture = ptm.getPreviewTexture(this.remoteSkinResource, Type.SKIN, getBlankSkin(), listener);
|
|
||||||
this.remoteElytraTexture = ptm.getPreviewTexture(this.remoteElytraResource, Type.ELYTRA, getBlankElytra(), null);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,7 @@ import com.mojang.authlib.minecraft.MinecraftProfileTexture;
|
||||||
import com.mumfrey.liteloader.util.log.LiteLoaderLogger;
|
import com.mumfrey.liteloader.util.log.LiteLoaderLogger;
|
||||||
import com.voxelmodpack.hdskins.HDSkinManager;
|
import com.voxelmodpack.hdskins.HDSkinManager;
|
||||||
import com.voxelmodpack.hdskins.skins.SkinServer;
|
import com.voxelmodpack.hdskins.skins.SkinServer;
|
||||||
|
import com.voxelmodpack.hdskins.skins.SkinUpload;
|
||||||
import com.voxelmodpack.hdskins.skins.SkinUploadResponse;
|
import com.voxelmodpack.hdskins.skins.SkinUploadResponse;
|
||||||
import com.voxelmodpack.hdskins.upload.awt.ThreadOpenFilePNG;
|
import com.voxelmodpack.hdskins.upload.awt.ThreadOpenFilePNG;
|
||||||
import net.minecraft.client.Minecraft;
|
import net.minecraft.client.Minecraft;
|
||||||
|
@ -567,7 +568,7 @@ public class GuiSkins extends GuiScreen {
|
||||||
this.uploadingSkin = true;
|
this.uploadingSkin = true;
|
||||||
this.skinUploadMessage = I18n.format("hdskins.request");
|
this.skinUploadMessage = I18n.format("hdskins.request");
|
||||||
HDSkinManager.INSTANCE.getGatewayServer()
|
HDSkinManager.INSTANCE.getGatewayServer()
|
||||||
.uploadSkin(session, null, this.textureType, getMetadata())
|
.uploadSkin(session, new SkinUpload(this.textureType, null, getMetadata()))
|
||||||
.thenAccept(this::onUploadComplete)
|
.thenAccept(this::onUploadComplete)
|
||||||
.exceptionally(this::onFailure);
|
.exceptionally(this::onFailure);
|
||||||
}
|
}
|
||||||
|
@ -577,7 +578,7 @@ public class GuiSkins extends GuiScreen {
|
||||||
this.skinUploadMessage = I18n.format("hdskins.upload");
|
this.skinUploadMessage = I18n.format("hdskins.upload");
|
||||||
URI path = skinFile == null ? null : skinFile.toURI();
|
URI path = skinFile == null ? null : skinFile.toURI();
|
||||||
HDSkinManager.INSTANCE.getGatewayServer()
|
HDSkinManager.INSTANCE.getGatewayServer()
|
||||||
.uploadSkin(session, path, this.textureType, getMetadata())
|
.uploadSkin(session, new SkinUpload(this.textureType, path, getMetadata()))
|
||||||
.thenAccept(this::onUploadComplete)
|
.thenAccept(this::onUploadComplete)
|
||||||
.exceptionally(this::onFailure);
|
.exceptionally(this::onFailure);
|
||||||
}
|
}
|
||||||
|
@ -591,7 +592,6 @@ public class GuiSkins extends GuiScreen {
|
||||||
this.btnUpload.enabled = true;
|
this.btnUpload.enabled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private Void onFailure(Throwable t) {
|
private Void onFailure(Throwable t) {
|
||||||
t = Throwables.getRootCause(t);
|
t = Throwables.getRootCause(t);
|
||||||
LogManager.getLogger().warn("Upload failed", t);
|
LogManager.getLogger().warn("Upload failed", t);
|
||||||
|
|
|
@ -5,6 +5,7 @@ import com.google.common.collect.ImmutableMap.Builder;
|
||||||
import com.google.gson.JsonObject;
|
import com.google.gson.JsonObject;
|
||||||
import com.google.gson.annotations.Expose;
|
import com.google.gson.annotations.Expose;
|
||||||
import com.mojang.authlib.GameProfile;
|
import com.mojang.authlib.GameProfile;
|
||||||
|
import com.mojang.authlib.minecraft.MinecraftProfileTexture;
|
||||||
import com.mojang.authlib.minecraft.MinecraftProfileTexture.Type;
|
import com.mojang.authlib.minecraft.MinecraftProfileTexture.Type;
|
||||||
import com.mojang.authlib.yggdrasil.response.MinecraftTexturesPayload;
|
import com.mojang.authlib.yggdrasil.response.MinecraftTexturesPayload;
|
||||||
import com.mojang.util.UUIDTypeAdapter;
|
import com.mojang.util.UUIDTypeAdapter;
|
||||||
|
@ -16,9 +17,10 @@ import java.io.IOException;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
@ServerType("bethlehem")
|
@ServerType("bethlehem")
|
||||||
public class BethlehemSkinServer implements SkinServer {
|
public class BethlehemSkinServer implements SkinServer {
|
||||||
|
|
||||||
|
@ -32,24 +34,24 @@ public class BethlehemSkinServer implements SkinServer {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Optional<MinecraftTexturesPayload> loadProfileData(GameProfile profile) {
|
public MinecraftTexturesPayload loadProfileData(GameProfile profile) throws IOException {
|
||||||
NetClient client = new NetClient("GET", getPath(profile));
|
try (MoreHttpResponses response = new NetClient("GET", getPath(profile)).send()) {
|
||||||
|
|
||||||
String json = client.getResponseText();
|
JsonObject s = response.json(JsonObject.class);
|
||||||
|
|
||||||
JsonObject s = gson.fromJson(json, JsonObject.class);
|
if (s.has("success") && s.get("success").getAsBoolean()) {
|
||||||
|
s = s.get("data").getAsJsonObject();
|
||||||
if (s.has("success") && s.get("success").getAsBoolean()) {
|
return gson.fromJson(s, MinecraftTexturesPayload.class);
|
||||||
s = s.get("data").getAsJsonObject();
|
}
|
||||||
|
throw new IOException(s.get("error").getAsString());
|
||||||
return Optional.ofNullable(gson.fromJson(s, MinecraftTexturesPayload.class));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CompletableFuture<SkinUploadResponse> uploadSkin(Session session, URI image, Type type, Map<String, String> metadata) {
|
public CompletableFuture<SkinUploadResponse> uploadSkin(Session session, SkinUpload skin) {
|
||||||
|
URI image = skin.getImage();
|
||||||
|
Map<String, String> metadata = skin.getMetadata();
|
||||||
|
MinecraftProfileTexture.Type type = skin.getType();
|
||||||
return CallableFutures.asyncFailableFuture(() -> {
|
return CallableFutures.asyncFailableFuture(() -> {
|
||||||
SkinServer.verifyServerConnection(session, SERVER_ID);
|
SkinServer.verifyServerConnection(session, SERVER_ID);
|
||||||
|
|
||||||
|
@ -59,15 +61,17 @@ public class BethlehemSkinServer implements SkinServer {
|
||||||
client.putFile(type.toString().toLowerCase(Locale.US), "image/png", image);
|
client.putFile(type.toString().toLowerCase(Locale.US), "image/png", image);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!client.send()) {
|
try (MoreHttpResponses response = client.send()) {
|
||||||
throw new IOException(client.getResponseText());
|
if (!response.ok()) {
|
||||||
|
throw new IOException(response.text());
|
||||||
|
}
|
||||||
|
return new SkinUploadResponse(response.text());
|
||||||
}
|
}
|
||||||
|
|
||||||
return new SkinUploadResponse(client.getResponseText());
|
|
||||||
}, HDSkinManager.skinUploadExecutor);
|
}, HDSkinManager.skinUploadExecutor);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Map<String, ?> createHeaders(Session session, Type type, URI image, Map<String, String> metadata) {
|
protected Map<String, ?> createHeaders(Session session, Type type, @Nullable URI image, Map<String, String> metadata) {
|
||||||
Builder<String, Object> builder = ImmutableMap.<String, Object>builder()
|
Builder<String, Object> builder = ImmutableMap.<String, Object>builder()
|
||||||
.put("accessToken", session.getToken())
|
.put("accessToken", session.getToken())
|
||||||
.put("user", session.getUsername())
|
.put("user", session.getUsername())
|
||||||
|
|
|
@ -18,11 +18,9 @@ import java.io.IOException;
|
||||||
import java.net.HttpURLConnection;
|
import java.net.HttpURLConnection;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.EnumMap;
|
import java.util.EnumMap;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
@ -45,30 +43,25 @@ public class LegacySkinServer implements SkinServer {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<MinecraftProfileTexture.Type, MinecraftProfileTexture> getPreviewTextures(GameProfile profile) {
|
public CompletableFuture<MinecraftTexturesPayload> getPreviewTextures(GameProfile profile) {
|
||||||
if (Strings.isNullOrEmpty(this.gateway)) {
|
if (Strings.isNullOrEmpty(this.gateway)) {
|
||||||
return Collections.emptyMap();
|
return CallableFutures.failedFuture(gatewayUnsupported());
|
||||||
}
|
}
|
||||||
Map<MinecraftProfileTexture.Type, MinecraftProfileTexture> map = new EnumMap<>(MinecraftProfileTexture.Type.class);
|
Map<MinecraftProfileTexture.Type, MinecraftProfileTexture> map = new EnumMap<>(MinecraftProfileTexture.Type.class);
|
||||||
for (MinecraftProfileTexture.Type type : MinecraftProfileTexture.Type.values()) {
|
for (MinecraftProfileTexture.Type type : MinecraftProfileTexture.Type.values()) {
|
||||||
map.put(type, new MinecraftProfileTexture(getPath(gateway, type, profile), null));
|
map.put(type, new MinecraftProfileTexture(getPath(gateway, type, profile), null));
|
||||||
}
|
}
|
||||||
return map;
|
return CompletableFuture.completedFuture(TexturesPayloadBuilder.createTexturesPayload(profile, map));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Optional<MinecraftTexturesPayload> loadProfileData(GameProfile profile) {
|
public MinecraftTexturesPayload loadProfileData(GameProfile profile) throws IOException {
|
||||||
ImmutableMap.Builder<MinecraftProfileTexture.Type, MinecraftProfileTexture> builder = ImmutableMap.builder();
|
ImmutableMap.Builder<MinecraftProfileTexture.Type, MinecraftProfileTexture> builder = ImmutableMap.builder();
|
||||||
for (MinecraftProfileTexture.Type type : MinecraftProfileTexture.Type.values()) {
|
for (MinecraftProfileTexture.Type type : MinecraftProfileTexture.Type.values()) {
|
||||||
|
|
||||||
String url = getPath(this.address, type, profile);
|
String url = getPath(this.address, type, profile);
|
||||||
try {
|
try {
|
||||||
HttpURLConnection urlConnection = (HttpURLConnection) new URL(url).openConnection();
|
builder.put(type, loadProfileTexture(profile, url));
|
||||||
if (urlConnection.getResponseCode() / 100 != 2) {
|
|
||||||
throw new IOException("Bad response code: " + urlConnection.getResponseCode());
|
|
||||||
}
|
|
||||||
builder.put(type, new MinecraftProfileTexture(url, null));
|
|
||||||
logger.debug("Found skin for {} at {}", profile.getName(), url);
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
logger.trace("Couldn't find texture for {} at {}. Does it exist?", profile.getName(), url, e);
|
logger.trace("Couldn't find texture for {} at {}. Does it exist?", profile.getName(), url, e);
|
||||||
}
|
}
|
||||||
|
@ -76,21 +69,32 @@ public class LegacySkinServer implements SkinServer {
|
||||||
|
|
||||||
Map<MinecraftProfileTexture.Type, MinecraftProfileTexture> map = builder.build();
|
Map<MinecraftProfileTexture.Type, MinecraftProfileTexture> map = builder.build();
|
||||||
if (map.isEmpty()) {
|
if (map.isEmpty()) {
|
||||||
logger.debug("No textures found for {} at {}", profile, this.address);
|
throw new IOException(String.format("No textures found for %s at %s", profile, this.address));
|
||||||
return Optional.empty();
|
|
||||||
}
|
}
|
||||||
|
return TexturesPayloadBuilder.createTexturesPayload(profile, map);
|
||||||
|
}
|
||||||
|
|
||||||
return Optional.of(TexturesPayloadBuilder.createTexturesPayload(profile, map));
|
private MinecraftProfileTexture loadProfileTexture(GameProfile profile, String url) throws IOException {
|
||||||
|
HttpURLConnection urlConnection = (HttpURLConnection) new URL(url).openConnection();
|
||||||
|
if (urlConnection.getResponseCode() / 100 != 2) {
|
||||||
|
throw new IOException("Bad response code: " + urlConnection.getResponseCode() + ". URL: " + url);
|
||||||
|
}
|
||||||
|
logger.debug("Found skin for {} at {}", profile.getName(), url);
|
||||||
|
return new MinecraftProfileTexture(url, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("deprecation")
|
@SuppressWarnings("deprecation")
|
||||||
@Override
|
@Override
|
||||||
public CompletableFuture<SkinUploadResponse> uploadSkin(Session session, @Nullable URI image, MinecraftProfileTexture.Type type, Map<String, String> metadata) {
|
public CompletableFuture<SkinUploadResponse> uploadSkin(Session session, SkinUpload skin) {
|
||||||
if (Strings.isNullOrEmpty(this.gateway)) {
|
if (Strings.isNullOrEmpty(this.gateway)) {
|
||||||
return CallableFutures.failedFuture(new NullPointerException("gateway url is blank"));
|
return CallableFutures.failedFuture(gatewayUnsupported());
|
||||||
}
|
}
|
||||||
|
|
||||||
return CallableFutures.asyncFailableFuture(() -> {
|
return CallableFutures.asyncFailableFuture(() -> {
|
||||||
|
URI image = skin.getImage();
|
||||||
|
MinecraftProfileTexture.Type type = skin.getType();
|
||||||
|
Map<String, String> metadata = skin.getMetadata();
|
||||||
|
|
||||||
SkinServer.verifyServerConnection(session, SERVER_ID);
|
SkinServer.verifyServerConnection(session, SERVER_ID);
|
||||||
String model = metadata.getOrDefault("model", "default");
|
String model = metadata.getOrDefault("model", "default");
|
||||||
Map<String, ?> data = image == null ? getClearData(session, type) : getUploadData(session, type, model, image);
|
Map<String, ?> data = image == null ? getClearData(session, type) : getUploadData(session, type, model, image);
|
||||||
|
@ -99,13 +103,18 @@ public class LegacySkinServer implements SkinServer {
|
||||||
if (response.startsWith("ERROR: ")) {
|
if (response.startsWith("ERROR: ")) {
|
||||||
response = response.substring(7);
|
response = response.substring(7);
|
||||||
}
|
}
|
||||||
if (!response.equalsIgnoreCase("OK") && !response.endsWith("OK"))
|
if (!response.equalsIgnoreCase("OK") && !response.endsWith("OK")) {
|
||||||
throw new IOException(response);
|
throw new IOException(response);
|
||||||
|
}
|
||||||
return new SkinUploadResponse(response);
|
return new SkinUploadResponse(response);
|
||||||
|
|
||||||
}, HDSkinManager.skinUploadExecutor);
|
}, HDSkinManager.skinUploadExecutor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private UnsupportedOperationException gatewayUnsupported() {
|
||||||
|
return new UnsupportedOperationException("Server does not have a gateway.");
|
||||||
|
}
|
||||||
|
|
||||||
private static Map<String, ?> getData(Session session, MinecraftProfileTexture.Type type, String model, String param, Object val) {
|
private static Map<String, ?> getData(Session session, MinecraftProfileTexture.Type type, String model, String param, Object val) {
|
||||||
return ImmutableMap.of(
|
return ImmutableMap.of(
|
||||||
"user", session.getUsername(),
|
"user", session.getUsername(),
|
||||||
|
|
|
@ -0,0 +1,75 @@
|
||||||
|
package com.voxelmodpack.hdskins.skins;
|
||||||
|
|
||||||
|
import com.google.common.io.CharStreams;
|
||||||
|
import org.apache.http.HttpStatus;
|
||||||
|
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||||
|
import org.apache.http.client.methods.HttpUriRequest;
|
||||||
|
import org.apache.http.impl.client.CloseableHttpClient;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.io.Reader;
|
||||||
|
import java.lang.reflect.Type;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility class for getting different response types from a http response.
|
||||||
|
*/
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface MoreHttpResponses extends AutoCloseable {
|
||||||
|
|
||||||
|
CloseableHttpResponse getResponse();
|
||||||
|
|
||||||
|
default boolean ok() {
|
||||||
|
return getResponseCode() == HttpStatus.SC_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
default int getResponseCode() {
|
||||||
|
return getResponse().getStatusLine().getStatusCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
default InputStream getInputStream() throws IOException {
|
||||||
|
return getResponse().getEntity().getContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
default BufferedReader getReader() throws IOException {
|
||||||
|
return new BufferedReader(new InputStreamReader(getInputStream(), StandardCharsets.UTF_8));
|
||||||
|
}
|
||||||
|
|
||||||
|
default String text() throws IOException {
|
||||||
|
try (BufferedReader reader = getReader()) {
|
||||||
|
return CharStreams.toString(reader);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
default Stream<String> lines() throws IOException {
|
||||||
|
try (BufferedReader reader = getReader()) {
|
||||||
|
return reader.lines();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
default <T> T json(Class<T> type) throws IOException {
|
||||||
|
try (Reader reader = new InputStreamReader(getResponse().getEntity().getContent())) {
|
||||||
|
return SkinServer.gson.fromJson(reader, type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
default <T> T json(Type type) throws IOException {
|
||||||
|
try (Reader reader = new InputStreamReader(getResponse().getEntity().getContent())) {
|
||||||
|
return SkinServer.gson.fromJson(reader, type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void close() throws IOException {
|
||||||
|
this.getResponse().close();
|
||||||
|
}
|
||||||
|
|
||||||
|
static MoreHttpResponses execute(CloseableHttpClient client, HttpUriRequest request) throws IOException {
|
||||||
|
CloseableHttpResponse response = client.execute(request);
|
||||||
|
return () -> response;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,21 +1,16 @@
|
||||||
package com.voxelmodpack.hdskins.skins;
|
package com.voxelmodpack.hdskins.skins;
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
import com.voxelmodpack.hdskins.HDSkinManager;
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStreamReader;
|
|
||||||
import java.net.URI;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import org.apache.commons.io.IOUtils;
|
|
||||||
import org.apache.http.HttpEntity;
|
import org.apache.http.HttpEntity;
|
||||||
import org.apache.http.HttpStatus;
|
|
||||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
|
||||||
import org.apache.http.client.methods.HttpUriRequest;
|
import org.apache.http.client.methods.HttpUriRequest;
|
||||||
import org.apache.http.client.methods.RequestBuilder;
|
import org.apache.http.client.methods.RequestBuilder;
|
||||||
import org.apache.http.entity.ContentType;
|
import org.apache.http.entity.ContentType;
|
||||||
import org.apache.http.entity.mime.MultipartEntityBuilder;
|
import org.apache.http.entity.mime.MultipartEntityBuilder;
|
||||||
import org.apache.http.impl.client.HttpClients;
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ew. Why so many builders? >.<
|
* Ew. Why so many builders? >.<
|
||||||
|
@ -26,8 +21,6 @@ public class NetClient {
|
||||||
|
|
||||||
private Map<String, ?> headers;
|
private Map<String, ?> headers;
|
||||||
|
|
||||||
private CloseableHttpResponse response = null;
|
|
||||||
|
|
||||||
public NetClient(String method, String uri) {
|
public NetClient(String method, String uri) {
|
||||||
rqBuilder = RequestBuilder.create(method).setUri(uri);
|
rqBuilder = RequestBuilder.create(method).setUri(uri);
|
||||||
}
|
}
|
||||||
|
@ -47,56 +40,14 @@ public class NetClient {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean send() {
|
public MoreHttpResponses send() throws IOException {
|
||||||
HttpUriRequest request = rqBuilder.build();
|
HttpUriRequest request = rqBuilder.build();
|
||||||
|
|
||||||
for (Map.Entry<String, ?> parameter : headers.entrySet()) {
|
for (Map.Entry<String, ?> parameter : headers.entrySet()) {
|
||||||
request.addHeader(parameter.getKey(), parameter.getValue().toString());
|
request.addHeader(parameter.getKey(), parameter.getValue().toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
return MoreHttpResponses.execute(HDSkinManager.httpClient, request);
|
||||||
response = HttpClients.createSystem().execute(request);
|
|
||||||
|
|
||||||
return getResponseCode() == HttpStatus.SC_OK;
|
|
||||||
} catch (IOException e) { }
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getResponseCode() {
|
|
||||||
if (response == null) {
|
|
||||||
send();
|
|
||||||
}
|
|
||||||
|
|
||||||
return response.getStatusLine().getStatusCode();
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getResponseText() {
|
|
||||||
if (response == null) {
|
|
||||||
if (!send()) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
BufferedReader reader = null;
|
|
||||||
|
|
||||||
try {
|
|
||||||
reader = new BufferedReader(new InputStreamReader(response.getEntity().getContent()));
|
|
||||||
|
|
||||||
StringBuilder builder = new StringBuilder();
|
|
||||||
|
|
||||||
int ch;
|
|
||||||
while ((ch = reader.read()) != -1) {
|
|
||||||
builder.append((char)ch);
|
|
||||||
}
|
|
||||||
|
|
||||||
return builder.toString();
|
|
||||||
} catch (IOException e) {
|
|
||||||
|
|
||||||
} finally {
|
|
||||||
IOUtils.closeQuietly(reader);
|
|
||||||
}
|
|
||||||
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,28 +5,22 @@ import com.google.gson.Gson;
|
||||||
import com.google.gson.GsonBuilder;
|
import com.google.gson.GsonBuilder;
|
||||||
import com.mojang.authlib.GameProfile;
|
import com.mojang.authlib.GameProfile;
|
||||||
import com.mojang.authlib.exceptions.AuthenticationException;
|
import com.mojang.authlib.exceptions.AuthenticationException;
|
||||||
import com.mojang.authlib.minecraft.MinecraftProfileTexture;
|
|
||||||
import com.mojang.authlib.minecraft.MinecraftSessionService;
|
import com.mojang.authlib.minecraft.MinecraftSessionService;
|
||||||
import com.mojang.authlib.yggdrasil.response.MinecraftTexturesPayload;
|
import com.mojang.authlib.yggdrasil.response.MinecraftTexturesPayload;
|
||||||
import com.mojang.util.UUIDTypeAdapter;
|
import com.mojang.util.UUIDTypeAdapter;
|
||||||
import com.mumfrey.liteloader.modconfig.Exposable;
|
import com.mumfrey.liteloader.modconfig.Exposable;
|
||||||
|
import com.voxelmodpack.hdskins.HDSkinManager;
|
||||||
import net.minecraft.client.Minecraft;
|
import net.minecraft.client.Minecraft;
|
||||||
import net.minecraft.util.Session;
|
import net.minecraft.util.Session;
|
||||||
|
|
||||||
import java.net.URI;
|
import java.io.IOException;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
|
|
||||||
public interface SkinServer extends Exposable {
|
public interface SkinServer extends Exposable {
|
||||||
|
|
||||||
static final Gson gson = new GsonBuilder()
|
Gson gson = new GsonBuilder()
|
||||||
.registerTypeAdapter(UUID.class, new UUIDTypeAdapter())
|
.registerTypeAdapter(UUID.class, new UUIDTypeAdapter())
|
||||||
.create();
|
.create();
|
||||||
|
|
||||||
|
@ -34,16 +28,15 @@ public interface SkinServer extends Exposable {
|
||||||
"http://skins.voxelmodpack.com",
|
"http://skins.voxelmodpack.com",
|
||||||
"http://skinmanager.voxelmodpack.com"));
|
"http://skinmanager.voxelmodpack.com"));
|
||||||
|
|
||||||
Optional<MinecraftTexturesPayload> loadProfileData(GameProfile profile);
|
MinecraftTexturesPayload loadProfileData(GameProfile profile) throws IOException;
|
||||||
|
|
||||||
default Map<MinecraftProfileTexture.Type, MinecraftProfileTexture> getPreviewTextures(GameProfile profile) {
|
CompletableFuture<SkinUploadResponse> uploadSkin(Session session, SkinUpload upload);
|
||||||
return loadProfileData(profile).map(MinecraftTexturesPayload::getTextures).orElse(Collections.emptyMap());
|
|
||||||
|
default CompletableFuture<MinecraftTexturesPayload> getPreviewTextures(GameProfile profile) {
|
||||||
|
return CallableFutures.asyncFailableFuture(() -> loadProfileData(profile), HDSkinManager.skinDownloadExecutor);
|
||||||
}
|
}
|
||||||
|
|
||||||
CompletableFuture<SkinUploadResponse> uploadSkin(Session session, @Nullable URI image, MinecraftProfileTexture.Type type, Map<String, String> metadata);
|
static void verifyServerConnection(Session session, String serverId) throws AuthenticationException {
|
||||||
|
|
||||||
|
|
||||||
public static void verifyServerConnection(Session session, String serverId) throws AuthenticationException {
|
|
||||||
MinecraftSessionService service = Minecraft.getMinecraft().getSessionService();
|
MinecraftSessionService service = Minecraft.getMinecraft().getSessionService();
|
||||||
service.joinServer(session.getProfile(), session.getToken(), serverId);
|
service.joinServer(session.getProfile(), session.getToken(), serverId);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
package com.voxelmodpack.hdskins.skins;
|
||||||
|
|
||||||
|
import com.mojang.authlib.minecraft.MinecraftProfileTexture;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
public class SkinUpload {
|
||||||
|
|
||||||
|
private URI image;
|
||||||
|
private Map<String, String> metadata;
|
||||||
|
private MinecraftProfileTexture.Type type;
|
||||||
|
|
||||||
|
public SkinUpload(MinecraftProfileTexture.Type type, @Nullable URI image, Map<String, String> metadata) {
|
||||||
|
this.image = image;
|
||||||
|
this.metadata = metadata;
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public URI getImage() {
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, String> getMetadata() {
|
||||||
|
return metadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MinecraftProfileTexture.Type getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
}
|
|
@ -13,28 +13,19 @@ import net.minecraft.client.Minecraft;
|
||||||
import net.minecraft.util.Session;
|
import net.minecraft.util.Session;
|
||||||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||||
import org.apache.http.HttpHeaders;
|
import org.apache.http.HttpHeaders;
|
||||||
import org.apache.http.HttpResponse;
|
|
||||||
import org.apache.http.HttpStatus;
|
|
||||||
import org.apache.http.NameValuePair;
|
import org.apache.http.NameValuePair;
|
||||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
|
||||||
import org.apache.http.client.methods.HttpGet;
|
import org.apache.http.client.methods.HttpGet;
|
||||||
import org.apache.http.client.methods.HttpUriRequest;
|
import org.apache.http.client.methods.HttpUriRequest;
|
||||||
import org.apache.http.client.methods.RequestBuilder;
|
import org.apache.http.client.methods.RequestBuilder;
|
||||||
import org.apache.http.entity.ContentType;
|
import org.apache.http.entity.ContentType;
|
||||||
import org.apache.http.entity.mime.MultipartEntityBuilder;
|
import org.apache.http.entity.mime.MultipartEntityBuilder;
|
||||||
import org.apache.http.impl.client.CloseableHttpClient;
|
|
||||||
import org.apache.http.impl.client.HttpClients;
|
|
||||||
import org.apache.http.message.BasicNameValuePair;
|
import org.apache.http.message.BasicNameValuePair;
|
||||||
import org.apache.http.util.EntityUtils;
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStreamReader;
|
|
||||||
import java.io.Reader;
|
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
|
@ -53,83 +44,81 @@ public class ValhallaSkinServer implements SkinServer {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Optional<MinecraftTexturesPayload> loadProfileData(GameProfile profile) {
|
public MinecraftTexturesPayload loadProfileData(GameProfile profile) throws IOException {
|
||||||
|
|
||||||
try (CloseableHttpClient client = HttpClients.createSystem();
|
try (MoreHttpResponses response = MoreHttpResponses.execute(HDSkinManager.httpClient, new HttpGet(getTexturesURI(profile)))) {
|
||||||
CloseableHttpResponse response = client.execute(new HttpGet(getTexturesURI(profile)))) {
|
|
||||||
|
|
||||||
if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
|
if (response.ok()) {
|
||||||
|
return readJson(response, MinecraftTexturesPayload.class);
|
||||||
return Optional.of(readJson(response, MinecraftTexturesPayload.class));
|
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
throw new IOException("Server sent non-ok response code: " + response.getResponseCode());
|
||||||
e.printStackTrace();
|
|
||||||
}
|
}
|
||||||
return Optional.empty();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CompletableFuture<SkinUploadResponse> uploadSkin(Session session, @Nullable URI image, MinecraftProfileTexture.Type type, Map<String, String> metadata) {
|
public CompletableFuture<SkinUploadResponse> uploadSkin(Session session, SkinUpload skin) {
|
||||||
|
URI image = skin.getImage();
|
||||||
|
Map<String, String> metadata = skin.getMetadata();
|
||||||
|
MinecraftProfileTexture.Type type = skin.getType();
|
||||||
|
|
||||||
return CallableFutures.asyncFailableFuture(() -> {
|
return CallableFutures.asyncFailableFuture(() -> {
|
||||||
try (CloseableHttpClient client = HttpClients.createSystem()) {
|
authorize(session);
|
||||||
authorize(client, session);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return upload(client, session, image, type, metadata);
|
return upload(session, image, type, metadata);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
if (e.getMessage().equals("Authorization failed")) {
|
if (e.getMessage().equals("Authorization failed")) {
|
||||||
accessToken = null;
|
accessToken = null;
|
||||||
authorize(client, session);
|
authorize(session);
|
||||||
return upload(client, session, image, type, metadata);
|
return upload(session, image, type, metadata);
|
||||||
}
|
}
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}, HDSkinManager.skinUploadExecutor);
|
}, HDSkinManager.skinUploadExecutor);
|
||||||
}
|
}
|
||||||
|
|
||||||
private SkinUploadResponse upload(CloseableHttpClient client, Session session, @Nullable URI image,
|
private SkinUploadResponse upload(Session session, @Nullable URI image,
|
||||||
MinecraftProfileTexture.Type type, Map<String, String> metadata)
|
MinecraftProfileTexture.Type type, Map<String, String> metadata)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
GameProfile profile = session.getProfile();
|
GameProfile profile = session.getProfile();
|
||||||
|
|
||||||
if (image == null) {
|
if (image == null) {
|
||||||
return resetSkin(client, profile, type);
|
return resetSkin(profile, type);
|
||||||
}
|
}
|
||||||
switch (image.getScheme()) {
|
switch (image.getScheme()) {
|
||||||
case "file":
|
case "file":
|
||||||
return uploadFile(client, new File(image), profile, type, metadata);
|
return uploadFile(new File(image), profile, type, metadata);
|
||||||
case "http":
|
case "http":
|
||||||
case "https":
|
case "https":
|
||||||
return uploadUrl(client, image, profile, type, metadata);
|
return uploadUrl(image, profile, type, metadata);
|
||||||
default:
|
default:
|
||||||
throw new IOException("Unsupported URI scheme: " + image.getScheme());
|
throw new IOException("Unsupported URI scheme: " + image.getScheme());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private SkinUploadResponse resetSkin(CloseableHttpClient client, GameProfile profile, MinecraftProfileTexture.Type type) throws IOException {
|
private SkinUploadResponse resetSkin(GameProfile profile, MinecraftProfileTexture.Type type) throws IOException {
|
||||||
return upload(client, RequestBuilder.delete()
|
return upload(RequestBuilder.delete()
|
||||||
.setUri(buildUserTextureUri(profile, type))
|
.setUri(buildUserTextureUri(profile, type))
|
||||||
.addHeader(HttpHeaders.AUTHORIZATION, this.accessToken)
|
.addHeader(HttpHeaders.AUTHORIZATION, this.accessToken)
|
||||||
.build());
|
.build());
|
||||||
}
|
}
|
||||||
|
|
||||||
private SkinUploadResponse uploadFile(CloseableHttpClient client, File file, GameProfile profile, MinecraftProfileTexture.Type type, Map<String, String> metadata) throws IOException {
|
private SkinUploadResponse uploadFile(File file, GameProfile profile, MinecraftProfileTexture.Type type, Map<String, String> metadata) throws IOException {
|
||||||
MultipartEntityBuilder b = MultipartEntityBuilder.create();
|
MultipartEntityBuilder b = MultipartEntityBuilder.create();
|
||||||
b.addBinaryBody("file", file, ContentType.create("image/png"), file.getName());
|
b.addBinaryBody("file", file, ContentType.create("image/png"), file.getName());
|
||||||
metadata.forEach(b::addTextBody);
|
metadata.forEach(b::addTextBody);
|
||||||
|
|
||||||
return upload(client, RequestBuilder.put()
|
return upload(RequestBuilder.put()
|
||||||
.setUri(buildUserTextureUri(profile, type))
|
.setUri(buildUserTextureUri(profile, type))
|
||||||
.addHeader(HttpHeaders.AUTHORIZATION, this.accessToken)
|
.addHeader(HttpHeaders.AUTHORIZATION, this.accessToken)
|
||||||
.setEntity(b.build())
|
.setEntity(b.build())
|
||||||
.build());
|
.build());
|
||||||
}
|
}
|
||||||
|
|
||||||
private SkinUploadResponse uploadUrl(CloseableHttpClient client, URI uri, GameProfile profile, MinecraftProfileTexture.Type type, Map<String, String> metadata) throws IOException {
|
private SkinUploadResponse uploadUrl(URI uri, GameProfile profile, MinecraftProfileTexture.Type type, Map<String, String> metadata) throws IOException {
|
||||||
|
|
||||||
return upload(client, RequestBuilder.post()
|
return upload(RequestBuilder.post()
|
||||||
.setUri(buildUserTextureUri(profile, type))
|
.setUri(buildUserTextureUri(profile, type))
|
||||||
.addHeader(HttpHeaders.AUTHORIZATION, this.accessToken)
|
.addHeader(HttpHeaders.AUTHORIZATION, this.accessToken)
|
||||||
.addParameter("file", uri.toString())
|
.addParameter("file", uri.toString())
|
||||||
|
@ -139,20 +128,19 @@ public class ValhallaSkinServer implements SkinServer {
|
||||||
.build());
|
.build());
|
||||||
}
|
}
|
||||||
|
|
||||||
private SkinUploadResponse upload(CloseableHttpClient client, HttpUriRequest request) throws IOException {
|
private SkinUploadResponse upload(HttpUriRequest request) throws IOException {
|
||||||
try (CloseableHttpResponse response = client.execute(request)) {
|
try (MoreHttpResponses response = MoreHttpResponses.execute(HDSkinManager.httpClient, request)) {
|
||||||
return readJson(response, SkinUploadResponse.class);
|
return readJson(response, SkinUploadResponse.class);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void authorize(Session session) throws IOException, AuthenticationException {
|
||||||
private void authorize(CloseableHttpClient client, Session session) throws IOException, AuthenticationException {
|
|
||||||
if (this.accessToken != null) {
|
if (this.accessToken != null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
GameProfile profile = session.getProfile();
|
GameProfile profile = session.getProfile();
|
||||||
String token = session.getToken();
|
String token = session.getToken();
|
||||||
AuthHandshake handshake = authHandshake(client, profile.getName());
|
AuthHandshake handshake = authHandshake(profile.getName());
|
||||||
|
|
||||||
if (handshake.offline) {
|
if (handshake.offline) {
|
||||||
return;
|
return;
|
||||||
|
@ -161,33 +149,27 @@ public class ValhallaSkinServer implements SkinServer {
|
||||||
// join the session server
|
// join the session server
|
||||||
Minecraft.getMinecraft().getSessionService().joinServer(profile, token, handshake.serverId);
|
Minecraft.getMinecraft().getSessionService().joinServer(profile, token, handshake.serverId);
|
||||||
|
|
||||||
AuthResponse response = authResponse(client, profile.getName(), handshake.verifyToken);
|
AuthResponse response = authResponse(profile.getName(), handshake.verifyToken);
|
||||||
if (!response.userId.equals(profile.getId())) {
|
if (!response.userId.equals(profile.getId())) {
|
||||||
throw new IOException("UUID mismatch!"); // probably won't ever throw
|
throw new IOException("UUID mismatch!"); // probably won't ever throw
|
||||||
}
|
}
|
||||||
this.accessToken = response.accessToken;
|
this.accessToken = response.accessToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
private <T> T readJson(HttpResponse resp, Class<T> cl) throws IOException {
|
private <T> T readJson(MoreHttpResponses resp, Class<T> cl) throws IOException {
|
||||||
String type = resp.getEntity().getContentType().getValue();
|
String type = resp.getResponse().getEntity().getContentType().getValue();
|
||||||
if (!"application/json".equals(type)) {
|
if (!"application/json".equals(type)) {
|
||||||
try {
|
throw new IOException("Server returned a non-json response!");
|
||||||
throw new IOException("Server returned a non-json response!");
|
|
||||||
} finally {
|
|
||||||
EntityUtils.consumeQuietly(resp.getEntity());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
try (Reader r = new InputStreamReader(resp.getEntity().getContent())) {
|
if (resp.ok()) {
|
||||||
if (resp.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
|
return resp.json(cl);
|
||||||
// TODO specific error handling
|
|
||||||
throw new IOException(gson.fromJson(r, JsonObject.class).get("message").getAsString());
|
|
||||||
}
|
|
||||||
return gson.fromJson(r, cl);
|
|
||||||
}
|
}
|
||||||
|
throw new IOException(resp.json(JsonObject.class).get("message").getAsString());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private AuthHandshake authHandshake(CloseableHttpClient client, String name) throws IOException {
|
private AuthHandshake authHandshake(String name) throws IOException {
|
||||||
try (CloseableHttpResponse resp = client.execute(RequestBuilder.post()
|
try (MoreHttpResponses resp = MoreHttpResponses.execute(HDSkinManager.httpClient, RequestBuilder.post()
|
||||||
.setUri(getHandshakeURI())
|
.setUri(getHandshakeURI())
|
||||||
.addParameter("name", name)
|
.addParameter("name", name)
|
||||||
.build())) {
|
.build())) {
|
||||||
|
@ -195,8 +177,8 @@ public class ValhallaSkinServer implements SkinServer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private AuthResponse authResponse(CloseableHttpClient client, String name, long verifyToken) throws IOException {
|
private AuthResponse authResponse(String name, long verifyToken) throws IOException {
|
||||||
try (CloseableHttpResponse resp = client.execute(RequestBuilder.post()
|
try (MoreHttpResponses resp = MoreHttpResponses.execute(HDSkinManager.httpClient, RequestBuilder.post()
|
||||||
.setUri(getResponseURI())
|
.setUri(getResponseURI())
|
||||||
.addParameter("name", name)
|
.addParameter("name", name)
|
||||||
.addParameter("verifyToken", String.valueOf(verifyToken))
|
.addParameter("verifyToken", String.valueOf(verifyToken))
|
||||||
|
|
Loading…
Reference in a new issue