[UtilKlasse] GameProfileFetcher - GameProfile mit Skin/Cape

In Minecraft 1.8 wird der Skin mehr oder weniger vom Server gesendet.
In vielen Packets, die z.B. den Spieler spawnen oder ihn zu Tab-Liste hinzufügen wird ein GameProfile gesendet.
Ab Minecraft 1.8 steht in diesem Profil ein Link zu Textur drinnen, in Base64 kodiert.
Das sieht dann z.B. so aus:
Decodiert man diesen Code (z.B. auf base64decode.org), erhält man das:
Die Daten eines kompletten GameProfiles bekommt man über
Ich habe hier eine Klasse geschrieben, die diese Daten direkt in ein GameProfile serialisiert:
Achtung: Capes auf Servern anzubieten ist illegal (siehe EULA). (Ob es legal ist, einem Spieler, der sich als ein anderer Verkleidet auch dessen Cape zu geben, weiss ich nicht)
In vielen Packets, die z.B. den Spieler spawnen oder ihn zu Tab-Liste hinzufügen wird ein GameProfile gesendet.
Ab Minecraft 1.8 steht in diesem Profil ein Link zu Textur drinnen, in Base64 kodiert.
Das sieht dann z.B. so aus:
- Code: Alles auswählen
- eyJ0aW1lc3RhbXAiOjE0MTYxNjc5NDExMzQsInByb2ZpbGVJZCI6IjA2OWE3OWY0NDRlOTQ3MjZhNWJlZmNhOTBlMzhhYWY1IiwicHJvZmlsZU5hbWUiOiJOb3RjaCIsImlzUHVibGljIjp0cnVlLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYTExNmU2OWE4NDVlMjI3ZjdjYTFmZGRlOGMzNTdjOGM4MjFlYmQ0YmE2MTkzODJlYTRhMWY4N2Q0YWU5NCJ9LCJDQVBFIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvM2Y2ODhlMGU2OTliM2Q5ZmU0NDhiNWJiNTBhM2EyODhmOWM1ODk3NjJiM2RhZTgzMDg4NDIxMjJkY2I4MSJ9fX0=
Decodiert man diesen Code (z.B. auf base64decode.org), erhält man das:
- Code: Alles auswählen
- {
- "timestamp": 1416167941134,
- "profileId": "069a79f444e94726a5befca90e38aaf5",
- "profileName": "Notch",
- "isPublic": true,
- "textures": {
- "SKIN": {
- "url": "http://textures.minecraft.net/texture/a116e69a845e227f7ca1fdde8c357c8c821ebd4ba619382ea4a1f87d4ae94"
- },
- "CAPE": {
- "url": "http://textures.minecraft.net/texture/3f688e0e699b3d9fe448b5bb50a3a288f9c589762b3dae8308842122dcb81"
- }
- }
- }
Die Daten eines kompletten GameProfiles bekommt man über
- Code: Alles auswählen
- https://sessionserver.mojang.com/session/minecraft/profile/<uuid ohne '-'>
Ich habe hier eine Klasse geschrieben, die diese Daten direkt in ein GameProfile serialisiert:
- Code: Alles auswählen
- import java.io.BufferedReader;
- import java.io.IOException;
- import java.io.InputStreamReader;
- import java.lang.reflect.Type;
- import java.net.HttpURLConnection;
- import java.net.URL;
- import java.util.ArrayList;
- import java.util.HashMap;
- import java.util.List;
- import java.util.Map.Entry;
- import java.util.UUID;
- import org.bukkit.craftbukkit.libs.com.google.gson.Gson;
- import org.bukkit.craftbukkit.libs.com.google.gson.GsonBuilder;
- import org.bukkit.craftbukkit.libs.com.google.gson.JsonDeserializationContext;
- import org.bukkit.craftbukkit.libs.com.google.gson.JsonDeserializer;
- import org.bukkit.craftbukkit.libs.com.google.gson.JsonElement;
- import org.bukkit.craftbukkit.libs.com.google.gson.JsonObject;
- import org.bukkit.craftbukkit.libs.com.google.gson.JsonParseException;
- import org.bukkit.craftbukkit.libs.com.google.gson.JsonParser;
- import org.bukkit.craftbukkit.libs.com.google.gson.JsonSerializationContext;
- import org.bukkit.craftbukkit.libs.com.google.gson.JsonSerializer;
- import org.yaml.snakeyaml.external.biz.base64Coder.Base64Coder;
- import com.mojang.authlib.GameProfile;
- import com.mojang.authlib.properties.Property;
- import com.mojang.authlib.properties.PropertyMap;
- import com.mojang.util.UUIDTypeAdapter;
- public class GameProfileBuilder {
- private static final String SERVICE_URL = "https://sessionserver.mojang.com/session/minecraft/profile/%s?unsigned=false";
- private static final String JSON_SKIN = "{\"timestamp\":%d,\"profileId\":\"%s\",\"profileName\":\"%s\",\"isPublic\":true,\"textures\":{\"SKIN\":{\"url\":\"%s\"}}}";
- private static final String JSON_CAPE = "{\"timestamp\":%d,\"profileId\":\"%s\",\"profileName\":\"%s\",\"isPublic\":true,\"textures\":{\"SKIN\":{\"url\":\"%s\"},\"CAPE\":{\"url\":\"%s\"}}}";
- private static Gson gson = new GsonBuilder().disableHtmlEscaping().registerTypeAdapter(UUID.class, new UUIDTypeAdapter()).registerTypeAdapter(GameProfile.class, new GameProfileSerializer()).registerTypeAdapter(PropertyMap.class, new PropertyMap.Serializer()).create();
- private static HashMap<UUID, CachedProfile> cache = new HashMap<UUID, CachedProfile>();
- private static long cacheTime = -1;
- /**
- * Don't run in main thread!
- *
- * Fetches the GameProfile from the Mojang servers
- *
- * @param uuid The player uuid
- * @return The GameProfile
- * @throws IOException If something wents wrong while fetching
- * @see GameProfile
- */
- public static GameProfile fetch(UUID uuid) throws IOException {
- return fetch(uuid, false);
- }
- /**
- * Don't run in main thread!
- *
- * Fetches the GameProfile from the Mojang servers
- * @param uuid The player uuid
- * @param forceNew If true the cache is ignored
- * @return The GameProfile
- * @throws IOException If something wents wrong while fetching
- * @see GameProfile
- */
- public static GameProfile fetch(UUID uuid, boolean forceNew) throws IOException {
- if (!forceNew && cache.containsKey(uuid) && cache.get(uuid).isValid()) {
- return cache.get(uuid).profile;
- } else {
- HttpURLConnection connection = (HttpURLConnection) new URL(String.format(SERVICE_URL, UUIDTypeAdapter.fromUUID(uuid))).openConnection();
- connection.setReadTimeout(5000);
- if (connection.getResponseCode() == HttpURLConnection.HTTP_OK) {
- String json = new BufferedReader(new InputStreamReader(connection.getInputStream())).readLine();
- GameProfile result = gson.fromJson(json, GameProfile.class);
- cache.put(uuid, new CachedProfile(result));
- return result;
- } else {
- if (!forceNew && cache.containsKey(uuid)) {
- return cache.get(uuid).profile;
- }
- JsonObject error = (JsonObject) new JsonParser().parse(new BufferedReader(new InputStreamReader(connection.getErrorStream())).readLine());
- throw new IOException(error.get("error").getAsString() + ": " + error.get("errorMessage").getAsString());
- }
- }
- }
- /**
- * Builds a GameProfile for the specified args
- *
- * @param uuid The uuid
- * @param name The name
- * @param skin The url from the skin image
- * @return A GameProfile built from the arguments
- * @see GameProfile
- */
- public static GameProfile getProfile(UUID uuid, String name, String skin) {
- return getProfile(uuid, name, skin, null);
- }
- /**
- * Builds a GameProfile for the specified args
- *
- * @param uuid The uuid
- * @param name The name
- * @param skinUrl Url from the skin image
- * @param capeUrl Url from the cape image
- * @return A GameProfile built from the arguments
- * @see GameProfile
- */
- public static GameProfile getProfile(UUID uuid, String name, String skinUrl, String capeUrl) {
- GameProfile profile = new GameProfile(uuid, name);
- boolean cape = capeUrl != null && !capeUrl.isEmpty();
- List<Object> args = new ArrayList<Object>();
- args.add(System.currentTimeMillis());
- args.add(UUIDTypeAdapter.fromUUID(uuid));
- args.add(name);
- args.add(skinUrl);
- if (cape) args.add(capeUrl);
- profile.getProperties().put("textures", new Property("textures", Base64Coder.encodeString(String.format(cape ? JSON_CAPE : JSON_SKIN, args.toArray(new Object[args.size()])))));
- return profile;
- }
- /**
- * Sets the time as long as you want to keep the gameprofiles in cache (-1 = never remove it)
- * @param time cache time (default = -1)
- */
- public static void setCacheTime(long time) {
- cacheTime = time;
- }
- private static class GameProfileSerializer implements JsonSerializer<GameProfile>, JsonDeserializer<GameProfile> {
- public GameProfile deserialize(JsonElement json, Type type, JsonDeserializationContext context) throws JsonParseException {
- JsonObject object = (JsonObject) json;
- UUID id = object.has("id") ? (UUID) context.deserialize(object.get("id"), UUID.class) : null;
- String name = object.has("name") ? object.getAsJsonPrimitive("name").getAsString() : null;
- GameProfile profile = new GameProfile(id, name);
- if (object.has("properties")) {
- for (Entry<String, Property> prop : ((PropertyMap) context.deserialize(object.get("properties"), PropertyMap.class)).entries()) {
- profile.getProperties().put(prop.getKey(), prop.getValue());
- }
- }
- return profile;
- }
- public JsonElement serialize(GameProfile profile, Type type, JsonSerializationContext context) {
- JsonObject result = new JsonObject();
- if (profile.getId() != null)
- result.add("id", context.serialize(profile.getId()));
- if (profile.getName() != null)
- result.addProperty("name", profile.getName());
- if (!profile.getProperties().isEmpty())
- result.add("properties", context.serialize(profile.getProperties()));
- return result;
- }
- }
- private static class CachedProfile {
- private long timestamp = System.currentTimeMillis();
- private GameProfile profile;
- public CachedProfile(GameProfile profile) {
- this.profile = profile;
- }
- public boolean isValid() {
- return cacheTime < 0 ? true : (System.currentTimeMillis() - timestamp) < cacheTime;
- }
- }
- }
- fetch(UUID uuid, boolean forceNew) - muss Asynchron aufgerufen werden, gibt ein Komplettes GameProfile für die angegebene UUID zurück (falls forceNew immer neu, sonst ausm Cache), wirft Exception bei Fehlschlagen (TooManyRequests, etc)
- getProfile(UUID uuid, String name, String skinUrl, String capeUrl) - gibt ein GameProfile für die angegebenen Daten zurück (cape ist Optional)
- setCacheTime(long time) - setzt die Zeit in Millisekunden, wie lange die GameProfile gecacht werden. Standartmässig werden sie nie aus dem cache entfernt (time = -1)
Achtung: Capes auf Servern anzubieten ist illegal (siehe EULA). (Ob es legal ist, einem Spieler, der sich als ein anderer Verkleidet auch dessen Cape zu geben, weiss ich nicht)