From eae26708556ea7c9df6f894cdcfa10ef047c2aff Mon Sep 17 00:00:00 2001 From: Glavo Date: Thu, 24 Oct 2024 20:03:21 +0800 Subject: [PATCH] =?UTF-8?q?=E6=94=AF=E6=8C=81=E5=9C=A8=E5=90=AF=E5=8A=A8?= =?UTF-8?q?=E5=99=A8=E5=86=85=E4=B8=8A=E4=BC=A0=E5=BE=AE=E8=BD=AF=E8=B4=A6?= =?UTF-8?q?=E5=8F=B7=E7=9A=AE=E8=82=A4=20(#3375)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * update * Fix clearCache --- .../hmcl/ui/account/AccountListItem.java | 13 ++--- .../java/org/jackhuang/hmcl/auth/Account.java | 9 ++++ .../hmcl/auth/microsoft/MicrosoftAccount.java | 14 +++++- .../hmcl/auth/microsoft/MicrosoftService.java | 48 +++++++++++++------ .../hmcl/auth/yggdrasil/YggdrasilAccount.java | 10 +++- .../hmcl/auth/yggdrasil/YggdrasilService.java | 4 +- 6 files changed, 68 insertions(+), 30 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListItem.java index 22a0b39f9..6fc255dd9 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListItem.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/AccountListItem.java @@ -32,17 +32,14 @@ import org.jackhuang.hmcl.auth.AuthenticationException; import org.jackhuang.hmcl.auth.CredentialExpiredException; import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorAccount; import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer; -import org.jackhuang.hmcl.auth.microsoft.MicrosoftAccount; import org.jackhuang.hmcl.auth.offline.OfflineAccount; import org.jackhuang.hmcl.auth.yggdrasil.CompleteGameProfile; import org.jackhuang.hmcl.auth.yggdrasil.TextureType; -import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount; import org.jackhuang.hmcl.setting.Accounts; import org.jackhuang.hmcl.task.Schedulers; import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.ui.Controllers; import org.jackhuang.hmcl.ui.DialogController; -import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.ui.construct.MessageDialogPane.MessageType; import org.jackhuang.hmcl.util.skin.InvalidSkinException; import org.jackhuang.hmcl.util.skin.NormalizedSkin; @@ -128,7 +125,7 @@ public class AccountListItem extends RadioButton { .orElse(emptySet()); return uploadableTextures.contains(TextureType.SKIN); }, profile); - } else if (account instanceof OfflineAccount || account instanceof MicrosoftAccount) { + } else if (account instanceof OfflineAccount || account.canUploadSkin()) { return createBooleanBinding(() -> true); } else { return createBooleanBinding(() -> false); @@ -144,11 +141,7 @@ public class AccountListItem extends RadioButton { Controllers.dialog(new OfflineAccountSkinPane((OfflineAccount) account)); return null; } - if (account instanceof MicrosoftAccount) { - FXUtils.openLink("https://www.minecraft.net/msaprofile/mygames/editskin"); - return null; - } - if (!(account instanceof YggdrasilAccount)) { + if (!account.canUploadSkin()) { return null; } @@ -174,7 +167,7 @@ public class AccountListItem extends RadioButton { NormalizedSkin skin = new NormalizedSkin(skinImg); String model = skin.isSlim() ? "slim" : ""; LOG.info("Uploading skin [" + selectedFile + "], model [" + model + "]"); - ((YggdrasilAccount) account).uploadSkin(model, selectedFile.toPath()); + account.uploadSkin(skin.isSlim(), selectedFile.toPath()); }) .thenComposeAsync(refreshAsync()) .whenComplete(Schedulers.javafx(), e -> { diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/Account.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/Account.java index 8ac98a2a9..3d40c983f 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/Account.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/Account.java @@ -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 toStorage(); public void clearCache() { diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/microsoft/MicrosoftAccount.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/microsoft/MicrosoftAccount.java index 774178a72..ef9401630 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/microsoft/MicrosoftAccount.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/microsoft/MicrosoftAccount.java @@ -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 toStorage() { return session.toStorage(); @@ -150,6 +161,7 @@ public class MicrosoftAccount extends OAuthAccount { @Override public void clearCache() { authenticated = false; + service.getProfileRepository().invalidate(characterUUID); } @Override diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/microsoft/MicrosoftService.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/microsoft/MicrosoftService.java index 214f062de..335e074d0 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/microsoft/MicrosoftService.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/microsoft/MicrosoftService.java @@ -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 fromJson(String text, Class 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; diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/YggdrasilAccount.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/YggdrasilAccount.java index db0f06fde..f253eb40b 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/YggdrasilAccount.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/YggdrasilAccount.java @@ -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() { diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/YggdrasilService.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/YggdrasilService.java index 3e3b71e5e..ef3b69562 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/YggdrasilService.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/YggdrasilService.java @@ -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); }