add: upload skin
This commit is contained in:
@@ -53,6 +53,11 @@ public class AuthlibInjectorProvider implements YggdrasilProvider {
|
||||
return toURL(apiRoot + "authserver/invalidate");
|
||||
}
|
||||
|
||||
@Override
|
||||
public URL getSkinUploadURL(UUID uuid) throws UnsupportedOperationException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public URL getProfilePropertiesURL(UUID uuid) throws AuthenticationException {
|
||||
return toURL(apiRoot + "sessionserver/session/minecraft/profile/" + UUIDTypeAdapter.fromUUID(uuid));
|
||||
|
||||
@@ -21,7 +21,7 @@ import org.jackhuang.hmcl.util.gson.UUIDTypeAdapter;
|
||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.UUID;
|
||||
import java.util.*;
|
||||
|
||||
public class MojangYggdrasilProvider implements YggdrasilProvider {
|
||||
|
||||
@@ -45,6 +45,11 @@ public class MojangYggdrasilProvider implements YggdrasilProvider {
|
||||
return NetworkUtils.toURL("https://authserver.mojang.com/invalidate");
|
||||
}
|
||||
|
||||
@Override
|
||||
public URL getSkinUploadURL(UUID uuid) throws UnsupportedOperationException {
|
||||
return NetworkUtils.toURL("https://api.mojang.com/user/profile/" + UUIDTypeAdapter.fromUUID(uuid) + "/skin");
|
||||
}
|
||||
|
||||
@Override
|
||||
public URL getProfilePropertiesURL(UUID uuid) {
|
||||
return NetworkUtils.toURL("https://sessionserver.mojang.com/session/minecraft/profile/" + UUIDTypeAdapter.fromUUID(uuid));
|
||||
|
||||
@@ -17,13 +17,7 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.auth.yggdrasil;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
import javafx.beans.binding.ObjectBinding;
|
||||
import org.jackhuang.hmcl.auth.Account;
|
||||
import org.jackhuang.hmcl.auth.AuthInfo;
|
||||
import org.jackhuang.hmcl.auth.AuthenticationException;
|
||||
@@ -34,7 +28,10 @@ import org.jackhuang.hmcl.auth.NoCharacterException;
|
||||
import org.jackhuang.hmcl.auth.ServerResponseMalformedException;
|
||||
import org.jackhuang.hmcl.util.gson.UUIDTypeAdapter;
|
||||
|
||||
import javafx.beans.binding.ObjectBinding;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
public class YggdrasilAccount extends Account {
|
||||
|
||||
@@ -193,6 +190,10 @@ public class YggdrasilAccount extends Account {
|
||||
service.getProfileRepository().invalidate(characterUUID);
|
||||
}
|
||||
|
||||
public void uploadSkin(String model, Path file) throws AuthenticationException, UnsupportedOperationException {
|
||||
service.uploadSkin(characterUUID, model, file);
|
||||
}
|
||||
|
||||
private static String randomClientToken() {
|
||||
return UUIDTypeAdapter.fromUUID(UUID.randomUUID());
|
||||
}
|
||||
|
||||
@@ -35,6 +35,24 @@ public interface YggdrasilProvider {
|
||||
|
||||
URL getInvalidationURL() throws AuthenticationException;
|
||||
|
||||
/**
|
||||
* URL to upload skin.
|
||||
*
|
||||
* Headers:
|
||||
* Authentication: Bearer <access token>
|
||||
*
|
||||
* Payload:
|
||||
* The payload for this API consists of multipart form data. There are two parts (order does not matter b/c of boundary):
|
||||
* model: Empty string for the default model and "slim" for the slim model
|
||||
* file: Raw image file data
|
||||
*
|
||||
* @see <a href="https://wiki.vg/Mojang_API#Upload_Skin">https://wiki.vg/Mojang_API#Upload_Skin</a>
|
||||
* @return url to upload skin
|
||||
* @throws AuthenticationException if url cannot be generated. e.g. some parameter or query is malformed.
|
||||
* @throws UnsupportedOperationException if the Yggdrasil provider does not support third-party skin uploading.
|
||||
*/
|
||||
URL getSkinUploadURL(UUID uuid) throws AuthenticationException, UnsupportedOperationException;
|
||||
|
||||
URL getProfilePropertiesURL(UUID uuid) throws AuthenticationException;
|
||||
|
||||
}
|
||||
|
||||
@@ -26,11 +26,17 @@ import org.jackhuang.hmcl.auth.ServerResponseMalformedException;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.gson.UUIDTypeAdapter;
|
||||
import org.jackhuang.hmcl.util.gson.ValidationTypeAdapterFactory;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.io.HttpMultipartRequest;
|
||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
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;
|
||||
@@ -145,6 +151,22 @@ public class YggdrasilService {
|
||||
requireEmpty(request(provider.getInvalidationURL(), createRequestWithCredentials(accessToken, clientToken)));
|
||||
}
|
||||
|
||||
public void uploadSkin(UUID uuid, String model, Path file) throws AuthenticationException, UnsupportedOperationException {
|
||||
try {
|
||||
HttpURLConnection con = NetworkUtils.createHttpConnection(provider.getSkinUploadURL(uuid));
|
||||
con.setRequestMethod("PUT");
|
||||
con.setDoOutput(true);
|
||||
try (HttpMultipartRequest request = new HttpMultipartRequest(con)) {
|
||||
try (InputStream fis = Files.newInputStream(file)) {
|
||||
request.file("file", FileUtils.getName(file), "image/" + FileUtils.getExtension(file), fis);
|
||||
}
|
||||
request.param("model", model);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new AuthenticationException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get complete game profile.
|
||||
*
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
package org.jackhuang.hmcl.util.io;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
public class HttpMultipartRequest implements Closeable {
|
||||
private final String boundary = "*****" + System.currentTimeMillis() + "*****";
|
||||
private final HttpURLConnection urlConnection;
|
||||
private final ByteArrayOutputStream stream;
|
||||
private final String endl = "\r\n";
|
||||
|
||||
public HttpMultipartRequest(HttpURLConnection urlConnection) throws IOException {
|
||||
this.urlConnection = urlConnection;
|
||||
urlConnection.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary);
|
||||
|
||||
stream = new ByteArrayOutputStream();
|
||||
}
|
||||
|
||||
private void addLine(String content) throws IOException {
|
||||
stream.write(content.getBytes(UTF_8));
|
||||
stream.write(endl.getBytes(UTF_8));
|
||||
}
|
||||
|
||||
public HttpMultipartRequest file(String name, String filename, String contentType, InputStream inputStream) throws IOException {
|
||||
addLine("--" + boundary);
|
||||
addLine(String.format("Content-Disposition: form-data; name=\"%s\"; filename=\"%s\"", name, filename));
|
||||
addLine("Content-Type: " + contentType);
|
||||
addLine("Content-Transfer-Encoding: binary");
|
||||
addLine("");
|
||||
IOUtils.copyTo(inputStream, stream);
|
||||
return this;
|
||||
}
|
||||
|
||||
public HttpMultipartRequest param(String name, String value) throws IOException {
|
||||
addLine("--" + boundary);
|
||||
addLine(String.format("Content-Disposition: form-data; name=\"%s\"", name));
|
||||
addLine("");
|
||||
addLine(value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
addLine("--" + boundary + "--");
|
||||
urlConnection.setRequestProperty("Content-Length", "" + stream.size());
|
||||
try (OutputStream os = urlConnection.getOutputStream()) {
|
||||
IOUtils.write(stream.toByteArray(), os);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user