@@ -30,6 +30,7 @@ import org.jackhuang.hmcl.auth.yggdrasil.TextureType;
|
||||
import org.jackhuang.hmcl.util.ToStringBuilder;
|
||||
import org.jackhuang.hmcl.util.javafx.ObservableHelper;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
@@ -69,6 +70,14 @@ public abstract class Account implements Observable {
|
||||
*/
|
||||
public abstract AuthInfo playOffline() throws AuthenticationException;
|
||||
|
||||
public boolean canUploadSkin() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public void uploadSkin(boolean isSlim, Path file) throws AuthenticationException, UnsupportedOperationException {
|
||||
throw new UnsupportedOperationException("Unsupported Operation");
|
||||
}
|
||||
|
||||
public abstract Map<Object, Object> toStorage();
|
||||
|
||||
public void clearCache() {
|
||||
|
||||
@@ -24,6 +24,7 @@ import org.jackhuang.hmcl.auth.yggdrasil.TextureType;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilService;
|
||||
import org.jackhuang.hmcl.util.javafx.BindingMapping;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
@@ -32,7 +33,7 @@ import java.util.UUID;
|
||||
import static java.util.Objects.requireNonNull;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
public class MicrosoftAccount extends OAuthAccount {
|
||||
public final class MicrosoftAccount extends OAuthAccount {
|
||||
|
||||
protected final MicrosoftService service;
|
||||
protected UUID characterUUID;
|
||||
@@ -125,6 +126,16 @@ public class MicrosoftAccount extends OAuthAccount {
|
||||
return session.toAuthInfo();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canUploadSkin() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void uploadSkin(boolean isSlim, Path file) throws AuthenticationException, UnsupportedOperationException {
|
||||
service.uploadSkin(session.getAccessToken(), isSlim, file);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Object, Object> toStorage() {
|
||||
return session.toStorage();
|
||||
@@ -150,6 +161,7 @@ public class MicrosoftAccount extends OAuthAccount {
|
||||
@Override
|
||||
public void clearCache() {
|
||||
authenticated = false;
|
||||
service.getProfileRepository().invalidate(characterUUID);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -25,19 +25,18 @@ import org.jackhuang.hmcl.auth.AuthenticationException;
|
||||
import org.jackhuang.hmcl.auth.OAuth;
|
||||
import org.jackhuang.hmcl.auth.ServerDisconnectException;
|
||||
import org.jackhuang.hmcl.auth.ServerResponseMalformedException;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.CompleteGameProfile;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.RemoteAuthenticationException;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.Texture;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.TextureType;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.*;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.gson.*;
|
||||
import org.jackhuang.hmcl.util.io.HttpRequest;
|
||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
import org.jackhuang.hmcl.util.io.ResponseCodeException;
|
||||
import org.jackhuang.hmcl.util.io.*;
|
||||
import org.jackhuang.hmcl.util.javafx.ObservableOptionalCache;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
@@ -261,6 +260,33 @@ public class MicrosoftService {
|
||||
return Optional.ofNullable(GSON.fromJson(request(NetworkUtils.toURL("https://sessionserver.mojang.com/session/minecraft/profile/" + UUIDTypeAdapter.fromUUID(uuid)), null), CompleteGameProfile.class));
|
||||
}
|
||||
|
||||
public void uploadSkin(String accessToken, boolean isSlim, Path file) throws AuthenticationException, UnsupportedOperationException {
|
||||
try {
|
||||
HttpURLConnection con = NetworkUtils.createHttpConnection(NetworkUtils.toURL("https://api.minecraftservices.com/minecraft/profile/skins"));
|
||||
con.setRequestMethod("POST");
|
||||
con.setRequestProperty("Authorization", "Bearer " + accessToken);
|
||||
con.setDoOutput(true);
|
||||
try (HttpMultipartRequest request = new HttpMultipartRequest(con)) {
|
||||
request.param("variant", isSlim ? "slim" : "classic");
|
||||
try (InputStream fis = Files.newInputStream(file)) {
|
||||
request.file("file", FileUtils.getName(file), "image/" + FileUtils.getExtension(file), fis);
|
||||
}
|
||||
}
|
||||
|
||||
String response = NetworkUtils.readData(con);
|
||||
if (StringUtils.isBlank(response)) {
|
||||
if (con.getResponseCode() / 100 != 2)
|
||||
throw new ResponseCodeException(con.getURL(), con.getResponseCode());
|
||||
} else {
|
||||
MinecraftErrorResponse profileResponse = GSON.fromJson(response, MinecraftErrorResponse.class);
|
||||
if (StringUtils.isNotBlank(profileResponse.errorMessage) || con.getResponseCode() / 100 != 2)
|
||||
throw new AuthenticationException("Failed to upload skin, response code: " + con.getResponseCode() + ", response: " + response);
|
||||
}
|
||||
} catch (IOException | JsonParseException e) {
|
||||
throw new AuthenticationException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static String request(URL url, Object payload) throws AuthenticationException {
|
||||
try {
|
||||
if (payload == null)
|
||||
@@ -272,14 +298,6 @@ public class MicrosoftService {
|
||||
}
|
||||
}
|
||||
|
||||
private static <T> T fromJson(String text, Class<T> typeOfT) throws ServerResponseMalformedException {
|
||||
try {
|
||||
return GSON.fromJson(text, typeOfT);
|
||||
} catch (JsonParseException e) {
|
||||
throw new ServerResponseMalformedException(text, e);
|
||||
}
|
||||
}
|
||||
|
||||
public static class XboxAuthorizationException extends AuthenticationException {
|
||||
private final long errorCode;
|
||||
private final String redirect;
|
||||
|
||||
@@ -203,8 +203,14 @@ public abstract class YggdrasilAccount extends ClassicAccount {
|
||||
|
||||
}
|
||||
|
||||
public void uploadSkin(String model, Path file) throws AuthenticationException, UnsupportedOperationException {
|
||||
service.uploadSkin(characterUUID, session.getAccessToken(), model, file);
|
||||
@Override
|
||||
public boolean canUploadSkin() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void uploadSkin(boolean isSlim, Path file) throws AuthenticationException, UnsupportedOperationException {
|
||||
service.uploadSkin(characterUUID, session.getAccessToken(), isSlim, file);
|
||||
}
|
||||
|
||||
private static String randomClientToken() {
|
||||
|
||||
@@ -148,14 +148,14 @@ public class YggdrasilService {
|
||||
requireEmpty(request(provider.getInvalidationURL(), createRequestWithCredentials(accessToken, clientToken)));
|
||||
}
|
||||
|
||||
public void uploadSkin(UUID uuid, String accessToken, String model, Path file) throws AuthenticationException, UnsupportedOperationException {
|
||||
public void uploadSkin(UUID uuid, String accessToken, boolean isSlim, Path file) throws AuthenticationException, UnsupportedOperationException {
|
||||
try {
|
||||
HttpURLConnection con = NetworkUtils.createHttpConnection(provider.getSkinUploadURL(uuid));
|
||||
con.setRequestMethod("PUT");
|
||||
con.setRequestProperty("Authorization", "Bearer " + accessToken);
|
||||
con.setDoOutput(true);
|
||||
try (HttpMultipartRequest request = new HttpMultipartRequest(con)) {
|
||||
request.param("model", model);
|
||||
request.param("model", isSlim ? "slim" : "");
|
||||
try (InputStream fis = Files.newInputStream(file)) {
|
||||
request.file("file", FileUtils.getName(file), "image/" + FileUtils.getExtension(file), fis);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user