上传 Minecraft 皮肤

Uploading a Minecraft Skin

在过去的几天里,我一直在尝试自动将 Minecraft 皮肤上传到 Mojang 的服务器。我能够成功登录到我的皮肤帐户(并适当地设置 cookie)。在不设置 cookie 的情况下,当我转到 https://minecraft.net/profile 时,我会被发送到登录页面,但如果我设置了 cookie,它会按原样将我带到个人资料页面。当我上传皮肤时,我查看了多次发送的 POST 数据,但我无法让它工作。我已经尝试了很多人的修复,但我找不到有效的修复。

public static void uploadSkin(BufferedImage image, boolean male, String username, String password){
    try {
        URL url = new URL("https://minecraft.net/login");
        URLConnection con = url.openConnection();
        HttpURLConnection http = (HttpURLConnection) con;
        http.setRequestMethod("POST");
        http.setDoOutput(true);
        Map<String, String> arguments = new HashMap<>();
        arguments.put("username", username);
        arguments.put("password", password);
        String s = "";
        for(Map.Entry<String, String> entry : arguments.entrySet())s += "&" + URLEncoder.encode(entry.getKey(), "UTF-8") + "=" + URLEncoder.encode(entry.getValue(), "UTF-8");
        s = s.replaceFirst("&", "");
        byte[] out = s.getBytes(StandardCharsets.UTF_8);
        int length = out.length;
        http.setFixedLengthStreamingMode(length);
        http.setRequestProperty("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
        http.setInstanceFollowRedirects(false);
        http.connect();
        OutputStream os = http.getOutputStream();
        os.write(out);
        String cooks = "";
        String at = "";
        for(int i = 0; i < 50; i++){
            String headerName = http.getHeaderFieldKey(i);
            String headerValue = http.getHeaderField(i);
            if(headerName != null && headerValue != null)if("Set-Cookie".equalsIgnoreCase(headerName))cooks += ";" + headerValue.split(";")[0];
        }
        http.disconnect();
        URL url3 = new URL("https://minecraft.net/profile");
        URLConnection con3 = url3.openConnection();
        HttpURLConnection http3 = (HttpURLConnection) con3;
        http3.setRequestProperty("Cookie", cooks);
        http3.connect();
        for(int i = 0; i < 50; i++){
            String headerName = http3.getHeaderFieldKey(i);
            String headerValue = http3.getHeaderField(i);
            if(headerName != null && headerValue != null)if("Set-Cookie".equalsIgnoreCase(headerName))if(headerValue.startsWith("PLAY_SESSION"))at = headerValue.split("AT=")[1].split("\"")[0];
        }
        http3.disconnect();
        cooks = cooks.replaceFirst(";", "");
        URL url2 = new URL("https://minecraft.net/profile/skin");
        URLConnection con2 = url2.openConnection();
        HttpURLConnection http2 = (HttpURLConnection) con2;
        http2.setRequestProperty("Cookie", cooks);
        http2.setRequestMethod("POST");
        http2.setDoOutput(true);
        Map<String, String> arguments2 = new HashMap<>();
        arguments2.put("model", male ? "steve" : "3pxarm");
        arguments2.put("authenticityToken", at);
        String encoded = "PNG";
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ImageIO.write(image, "png", bos);
        byte[] imageBytes = bos.toByteArray();
        BASE64Encoder encoder = new BASE64Encoder();
        encoded += encoder.encode(imageBytes);
        bos.close();
        arguments2.put("skin", encoded);
        String s2 = "";
        for(Map.Entry<String, String> entry : arguments2.entrySet())s2 += "&" + URLEncoder.encode(entry.getKey(), "UTF-8") + "=" + URLEncoder.encode(entry.getValue(), "UTF-8");
        s2 = s2.replaceFirst("&", "");
        byte[] out2 = s2.getBytes(StandardCharsets.UTF_8);
        int length2 = out2.length;
        http2.setFixedLengthStreamingMode(length2);
        http2.setRequestProperty("Content-Type", "multipart/form-data; charset=UTF-8;");
        http2.setInstanceFollowRedirects(false);
        http2.connect();
        OutputStream os2 = http2.getOutputStream();
        os2.write(out2);
        InputStream is = http2.getInputStream();
        Scanner sc = new Scanner(is, "UTF-8");
        sc.useDelimiter("\A");
        while(sc.hasNext())System.out.println(sc.next());
        sc.close();
        http2.disconnect();
    } catch (Exception e) {e.printStackTrace();}
}

我在尝试 运行 时遇到此错误

java.io.IOException: Server returned HTTP response code: 500 for URL: https://minecraft.net/profile/skin
at sun.net.www.protocol.http.HttpURLConnection.getInputStream(Unknown Source)
at sun.net.www.protocol.https.HttpsURLConnectionImpl.getInputStream(Unknown Source)
at net.supernatenetwork.snn.TTESTT.uploadSkin(TTESTT.java:104)
at net.supernatenetwork.snn.TTESTT.main(TTESTT.java:27)

第 98 行是 "InputStream is = http2.getInputStream();" 当我将用于读取 HTML 的块与第一个 http 变量放在一起时,它什么也不打印。我不断收到错误代码 404。我知道错误代码 404 会转换为找不到文件,但如果我不发送 POST 数据,它会将我带到个人资料页面,所以我假设它链接到 POST,因为如果我删除第一个 http 中的一个字段,它会给我同样的错误(仅在登录页面并且仅当我尝试从中获取数据时)。 BufferedImage 不为空,并且它有数据。我需要它是 BufferedImage,因为我需要在上传之前从模板对其进行编辑。查看之后,我看到皮肤的内容类型是 image/png,但我需要它是我目前拥有的内容类型,因为 POST 中有多个内容。我的编码变量以 "PNG" 开头,因为当我在 Firefox 中调试时,我看到皮肤以 PNG 开头(可能只是巧合)。我在没有 "PNG" 的情况下尝试过,但仍然没有运气。我上传的图片是 PNG 格式。任何帮助表示赞赏!谢谢!

编辑:我得到了一个不同的错误(来自新代码),我把它放在旧代码和错误所在的位置。我没有动过旧笔记。感谢 gre_gor 帮助我找出了一些错误。

编辑 2:我知道如何通过 post 发送东西。我只是想知道如何上传皮肤,这与标记为重复的皮肤不同。

要上传文件,您需要将数据编码为multipart/form-data。只是将 Content-Type 更改为 multipart/form-data 不会神奇地使其工作。您需要自己构建适当的 HTTP 负载。

它应该看起来像这样:

------WebKitFormBoundary4ytVzCJnzOYoi3v4
Content-Disposition: form-data; name="authenticityToken"

78142fca85887e53d795fab390a78cfe1f96acd5
------WebKitFormBoundary4ytVzCJnzOYoi3v4
Content-Disposition: form-data; name="model"

steve
------WebKitFormBoundary4ytVzCJnzOYoi3v4
Content-Disposition: form-data; name="skin"; filename="skin.png"
Content-Type: image/png

[PNG skin image data]
------WebKitFormBoundary4ytVzCJnzOYoi3v4--

这是工作代码:

import java.util.*;
import java.lang.*;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.net.URLDecoder;
import java.net.HttpCookie;
import java.net.CookieManager;
import java.net.CookieHandler;
import java.awt.image.BufferedImage;
import javax.imageio.ImageIO;

class SkinUpload
{
    public static void login(String email, String password) throws java.lang.Exception
    {
        String payload = "username=" + URLEncoder.encode(email, "UTF-8");
        payload += "&password=" + URLEncoder.encode(password, "UTF-8");
        byte[] payload_data = payload.getBytes("UTF-8");

        HttpURLConnection http = (HttpURLConnection)(new URL("https://minecraft.net/login").openConnection());
        http.setRequestMethod("POST");
        http.setDoOutput(true);
        http.setRequestProperty("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
        http.setFixedLengthStreamingMode(payload_data.length);

        http.connect();
        http.getOutputStream().write(payload_data);

        System.out.println("login: "+http.getResponseCode()+" "+http.getResponseMessage());

        http.disconnect();
    }
    public static void profile() throws java.lang.Exception
    {
        HttpURLConnection http = (HttpURLConnection)(new URL("https://minecraft.net/profile").openConnection());
        http.setRequestMethod("GET");

        http.connect();

        System.out.println("profile: "+http.getResponseCode()+" "+http.getResponseMessage());

        http.disconnect();
    }
    public static void upload(String authenticityToken, BufferedImage image, boolean male) throws java.lang.Exception
    {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ImageIO.write(image, "png", baos);

        String boundary = Long.toHexString(System.currentTimeMillis()); 

        HttpURLConnection http = (HttpURLConnection)(new URL("https://minecraft.net/profile/skin").openConnection());
        http.setRequestMethod("POST");
        http.setDoOutput(true);
        http.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary);

        http.connect();

        http.getOutputStream().write(("--"+boundary+"\r\n").getBytes());
        http.getOutputStream().write(("Content-Disposition: form-data; name=\"authenticityToken\"\r\n\r\n").getBytes());
        http.getOutputStream().write((authenticityToken+"\r\n").getBytes());

        http.getOutputStream().write(("--"+boundary+"\r\n").getBytes());
        http.getOutputStream().write(("Content-Disposition: form-data; name=\"model\"\r\n\r\n").getBytes());
        http.getOutputStream().write(((male ? "steve" : "3pxarm")+"\r\n").getBytes());

        http.getOutputStream().write(("--"+boundary+"\r\n").getBytes());
        http.getOutputStream().write(("Content-Disposition: form-data; name=\"skin\"; filename=\"skin.png\"\r\n").getBytes());
        http.getOutputStream().write(("Content-Type: image/png\r\n\r\n").getBytes());
        http.getOutputStream().write(baos.toByteArray());
        http.getOutputStream().write(("\r\n").getBytes());

        http.getOutputStream().write(("--"+boundary+"--\r\n").getBytes());

        http.getOutputStream().flush();

        System.out.println("upload: "+http.getResponseCode()+" "+http.getResponseMessage());

        http.disconnect();
    }
    public static String get_authenticityToken(List<HttpCookie> cookies) throws java.lang.Exception
    {
        for (HttpCookie cookie : cookies)
        {
            if (cookie.getName().equals("PLAY_SESSION"))
            {
                for (String param : cookie.getValue().split("&"))
                {
                    int i = param.indexOf("=");
                    String name = URLDecoder.decode(param.substring(0, i), "UTF-8");
                    String value = URLDecoder.decode(param.substring(i + 1), "UTF-8");
                    if (name.equals("___AT"))
                        return value;
                }
            }
        }
        return null;
    }
    public static void main (String[] args) throws java.lang.Exception
    {
        if (args.length < 3)
        {
            System.out.println("Usage:\nSkinUpload [email] [password] [image file path]");
            return;
        }
        String email = args[0];
        String password = args[1];
        String image_file = args[2];

        // this should handle cookies for all HTTP requests
        CookieManager cookieManager = new CookieManager();
        CookieHandler.setDefault(cookieManager);

        login(email, password);
        profile();

        // get authenticityToken  from picked up cookies
        String authenticityToken = get_authenticityToken(cookieManager.getCookieStore().getCookies());
        if (authenticityToken == null)
        {
            System.out.println("Failed to get authenticityToken");
        }
        else
        {
            System.out.println("authenticityToken = "+authenticityToken);

            BufferedImage skin = ImageIO.read(new File(image_file));

            upload(authenticityToken, skin, true);
        }
    }
}