[UtilKlasse] GameProfileFetcher - GameProfile mit Skin/Cape

Hier könnt ihr anderen Leuten helfen, indem ihr Anleitungen oder praktische Codesegmente zur Verfügung stellt.

[UtilKlasse] GameProfileFetcher - GameProfile mit Skin/Cape

Beitragvon Jofkos » So 16. Nov 2014, 21:17

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:
Code: Alles auswählen
  1. eyJ0aW1lc3RhbXAiOjE0MTYxNjc5NDExMzQsInByb2ZpbGVJZCI6IjA2OWE3OWY0NDRlOTQ3MjZhNWJlZmNhOTBlMzhhYWY1IiwicHJvZmlsZU5hbWUiOiJOb3RjaCIsImlzUHVibGljIjp0cnVlLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYTExNmU2OWE4NDVlMjI3ZjdjYTFmZGRlOGMzNTdjOGM4MjFlYmQ0YmE2MTkzODJlYTRhMWY4N2Q0YWU5NCJ9LCJDQVBFIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvM2Y2ODhlMGU2OTliM2Q5ZmU0NDhiNWJiNTBhM2EyODhmOWM1ODk3NjJiM2RhZTgzMDg4NDIxMjJkY2I4MSJ9fX0=

Decodiert man diesen Code (z.B. auf base64decode.org), erhält man das:
Code: Alles auswählen
  1. {
  2.   "timestamp": 1416167941134,
  3.   "profileId": "069a79f444e94726a5befca90e38aaf5",
  4.   "profileName": "Notch",
  5.   "isPublic": true,
  6.   "textures": {
  7.     "SKIN": {
  8.       "url": "http://textures.minecraft.net/texture/a116e69a845e227f7ca1fdde8c357c8c821ebd4ba619382ea4a1f87d4ae94"
  9.     },
  10.     "CAPE": {
  11.       "url": "http://textures.minecraft.net/texture/3f688e0e699b3d9fe448b5bb50a3a288f9c589762b3dae8308842122dcb81"
  12.     }
  13.   }
  14. }
Man sieht hier einmal einen Link zum Skin, und ein Link zum Cape. Ich habe jetzt hier als Beispiel Notch genommen, er hat ein Mojang Cape.

Die Daten eines kompletten GameProfiles bekommt man über
Code: Alles auswählen
  1. 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
  1. import java.io.BufferedReader;
  2. import java.io.IOException;
  3. import java.io.InputStreamReader;
  4. import java.lang.reflect.Type;
  5. import java.net.HttpURLConnection;
  6. import java.net.URL;
  7. import java.util.ArrayList;
  8. import java.util.HashMap;
  9. import java.util.List;
  10. import java.util.Map.Entry;
  11. import java.util.UUID;
  12. import org.bukkit.craftbukkit.libs.com.google.gson.Gson;
  13. import org.bukkit.craftbukkit.libs.com.google.gson.GsonBuilder;
  14. import org.bukkit.craftbukkit.libs.com.google.gson.JsonDeserializationContext;
  15. import org.bukkit.craftbukkit.libs.com.google.gson.JsonDeserializer;
  16. import org.bukkit.craftbukkit.libs.com.google.gson.JsonElement;
  17. import org.bukkit.craftbukkit.libs.com.google.gson.JsonObject;
  18. import org.bukkit.craftbukkit.libs.com.google.gson.JsonParseException;
  19. import org.bukkit.craftbukkit.libs.com.google.gson.JsonParser;
  20. import org.bukkit.craftbukkit.libs.com.google.gson.JsonSerializationContext;
  21. import org.bukkit.craftbukkit.libs.com.google.gson.JsonSerializer;
  22. import org.yaml.snakeyaml.external.biz.base64Coder.Base64Coder;
  23. import com.mojang.authlib.GameProfile;
  24. import com.mojang.authlib.properties.Property;
  25. import com.mojang.authlib.properties.PropertyMap;
  26. import com.mojang.util.UUIDTypeAdapter;
  27. public class GameProfileBuilder {
  28.    
  29.    private static final String SERVICE_URL = "https://sessionserver.mojang.com/session/minecraft/profile/%s?unsigned=false";
  30.    private static final String JSON_SKIN = "{\"timestamp\":%d,\"profileId\":\"%s\",\"profileName\":\"%s\",\"isPublic\":true,\"textures\":{\"SKIN\":{\"url\":\"%s\"}}}";
  31.    private static final String JSON_CAPE = "{\"timestamp\":%d,\"profileId\":\"%s\",\"profileName\":\"%s\",\"isPublic\":true,\"textures\":{\"SKIN\":{\"url\":\"%s\"},\"CAPE\":{\"url\":\"%s\"}}}";
  32.    
  33.    private static Gson gson = new GsonBuilder().disableHtmlEscaping().registerTypeAdapter(UUID.class, new UUIDTypeAdapter()).registerTypeAdapter(GameProfile.class, new GameProfileSerializer()).registerTypeAdapter(PropertyMap.class, new PropertyMap.Serializer()).create();
  34.    
  35.    private static HashMap<UUID, CachedProfile> cache = new HashMap<UUID, CachedProfile>();
  36.    
  37.    private static long cacheTime = -1;
  38.    
  39.    /**
  40.     * Don't run in main thread!
  41.     *
  42.     * Fetches the GameProfile from the Mojang servers
  43.     *
  44.     * @param uuid The player uuid
  45.     * @return The GameProfile
  46.     * @throws IOException If something wents wrong while fetching
  47.     * @see GameProfile
  48.     */
  49.    public static GameProfile fetch(UUID uuid) throws IOException {
  50.       return fetch(uuid, false);
  51.    }
  52.    
  53.    /**
  54.     * Don't run in main thread!
  55.     *
  56.     * Fetches the GameProfile from the Mojang servers
  57.     * @param uuid The player uuid
  58.     * @param forceNew If true the cache is ignored
  59.     * @return The GameProfile
  60.     * @throws IOException If something wents wrong while fetching
  61.     * @see GameProfile
  62.     */
  63.    public static GameProfile fetch(UUID uuid, boolean forceNew) throws IOException {
  64.       if (!forceNew && cache.containsKey(uuid) && cache.get(uuid).isValid()) {
  65.          return cache.get(uuid).profile;
  66.       } else {
  67.          HttpURLConnection connection = (HttpURLConnection) new URL(String.format(SERVICE_URL, UUIDTypeAdapter.fromUUID(uuid))).openConnection();
  68.          connection.setReadTimeout(5000);
  69.          
  70.          if (connection.getResponseCode() == HttpURLConnection.HTTP_OK) {
  71.             String json = new BufferedReader(new InputStreamReader(connection.getInputStream())).readLine();
  72.             
  73.             GameProfile result = gson.fromJson(json, GameProfile.class);
  74.             cache.put(uuid, new CachedProfile(result));
  75.             return result;
  76.          } else {
  77.             if (!forceNew && cache.containsKey(uuid)) {
  78.                return cache.get(uuid).profile;
  79.             }
  80.             JsonObject error = (JsonObject) new JsonParser().parse(new BufferedReader(new InputStreamReader(connection.getErrorStream())).readLine());
  81.             throw new IOException(error.get("error").getAsString() + ": " + error.get("errorMessage").getAsString());
  82.          }
  83.       }
  84.    }
  85.    
  86.    /**
  87.     * Builds a GameProfile for the specified args
  88.     *
  89.     * @param uuid The uuid
  90.     * @param name The name
  91.     * @param skin The url from the skin image
  92.     * @return A GameProfile built from the arguments
  93.     * @see GameProfile
  94.     */
  95.    public static GameProfile getProfile(UUID uuid, String name, String skin) {
  96.       return getProfile(uuid, name, skin, null);
  97.    }
  98.    
  99.    /**
  100.     * Builds a GameProfile for the specified args
  101.     *
  102.     * @param uuid The uuid
  103.     * @param name The name
  104.     * @param skinUrl Url from the skin image
  105.     * @param capeUrl Url from the cape image
  106.     * @return A GameProfile built from the arguments
  107.     * @see GameProfile
  108.     */
  109.    public static GameProfile getProfile(UUID uuid, String name, String skinUrl, String capeUrl) {
  110.       GameProfile profile = new GameProfile(uuid, name);
  111.       boolean cape = capeUrl != null && !capeUrl.isEmpty();
  112.       
  113.       List<Object> args = new ArrayList<Object>();
  114.       args.add(System.currentTimeMillis());
  115.       args.add(UUIDTypeAdapter.fromUUID(uuid));
  116.       args.add(name);
  117.       args.add(skinUrl);
  118.       if (cape) args.add(capeUrl);
  119.       
  120.       profile.getProperties().put("textures", new Property("textures", Base64Coder.encodeString(String.format(cape ? JSON_CAPE : JSON_SKIN, args.toArray(new Object[args.size()])))));
  121.       return profile;
  122.    }
  123.    
  124.    /**
  125.     * Sets the time as long as you want to keep the gameprofiles in cache (-1 = never remove it)
  126.     * @param time cache time (default = -1)
  127.     */
  128.    public static void setCacheTime(long time) {
  129.       cacheTime = time;
  130.    }
  131.    
  132.    private static class GameProfileSerializer implements JsonSerializer<GameProfile>, JsonDeserializer<GameProfile> {
  133.       
  134.       public GameProfile deserialize(JsonElement json, Type type, JsonDeserializationContext context) throws JsonParseException {
  135.          JsonObject object = (JsonObject) json;
  136.          UUID id = object.has("id") ? (UUID) context.deserialize(object.get("id"), UUID.class) : null;
  137.          String name = object.has("name") ? object.getAsJsonPrimitive("name").getAsString() : null;
  138.          GameProfile profile = new GameProfile(id, name);
  139.          
  140.          if (object.has("properties")) {
  141.             for (Entry<String, Property> prop : ((PropertyMap) context.deserialize(object.get("properties"), PropertyMap.class)).entries()) {
  142.                profile.getProperties().put(prop.getKey(), prop.getValue());
  143.             }
  144.          }
  145.          return profile;
  146.       }
  147.       
  148.       public JsonElement serialize(GameProfile profile, Type type, JsonSerializationContext context) {
  149.          JsonObject result = new JsonObject();
  150.          if (profile.getId() != null)
  151.             result.add("id", context.serialize(profile.getId()));
  152.          if (profile.getName() != null)
  153.             result.addProperty("name", profile.getName());
  154.          if (!profile.getProperties().isEmpty())
  155.             result.add("properties", context.serialize(profile.getProperties()));
  156.          return result;
  157.       }
  158.    }
  159.    
  160.    private static class CachedProfile {
  161.       
  162.       private long timestamp = System.currentTimeMillis();
  163.       private GameProfile profile;
  164.       
  165.       public CachedProfile(GameProfile profile) {
  166.          this.profile = profile;
  167.       }
  168.       
  169.       public boolean isValid() {
  170.          return cacheTime < 0 ? true : (System.currentTimeMillis() - timestamp) < cacheTime;
  171.       }
  172.    }
  173. }
Diese Klasse hat drei Methoden:
  • 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)
Zuletzt geändert von Jofkos am Sa 14. Mär 2015, 22:57, insgesamt 11-mal geändert.
Jofkos

...........

..Bild
Benutzeravatar
Jofkos
 
Beiträge: 1537
Registriert: So 16. Jun 2013, 22:45

Re: Komplettes GameProfile fetchen (mit Skin & Cape)

Beitragvon ilouHD » So 16. Nov 2014, 21:44

Gefällt mir. Mal gucken, wann ich es wo anwenden kann.
Bild
Benutzeravatar
ilouHD
 
Beiträge: 1733
Registriert: Do 9. Jan 2014, 14:49

Re: Komplettes GameProfile fetchen (mit Skin & Cape)

Beitragvon Sep2703 » Mo 17. Nov 2014, 15:23

Erstmal vielen Dank für dieses Tutorial!

Ich habe jedoch noch eine kleine Frage:
Jofkos hat geschrieben:(Capes auf Servern zu verkaufen wäre jedoch illegal, siehe EULA)

Darf ich Capes verschenken bzw. für rein erspielbare Leistungen anbieten?
Du möchtest programmieren lernen oder dein Bukkit-/Spigot-Wissen erweitern?
Hier habe ich für dich kostenlose Tutorials: https://youtube.com/janhektor
Benutzeravatar
Sep2703
 
Beiträge: 677
Registriert: Mi 8. Jan 2014, 15:13
Wohnort: 127.0.0.1

Re: Komplettes GameProfile fetchen (mit Skin & Cape)

Beitragvon IK_Raptor » Mo 17. Nov 2014, 15:41

Sep2703 hat geschrieben:Erstmal vielen Dank für dieses Tutorial!

Ich habe jedoch noch eine kleine Frage:
Jofkos hat geschrieben:(Capes auf Servern zu verkaufen wäre jedoch illegal, siehe EULA)

Darf ich Capes verschenken bzw. für rein erspielbare Leistungen anbieten?


Nein. :(
Benutzeravatar
IK_Raptor
 
Beiträge: 609
Registriert: Mo 12. Aug 2013, 15:37

Re: Komplettes GameProfile fetchen (mit Skin & Cape)

Beitragvon Sep2703 » Mo 17. Nov 2014, 15:52

Aber mit dem Skin ist das erlaubt, oder?
Du möchtest programmieren lernen oder dein Bukkit-/Spigot-Wissen erweitern?
Hier habe ich für dich kostenlose Tutorials: https://youtube.com/janhektor
Benutzeravatar
Sep2703
 
Beiträge: 677
Registriert: Mi 8. Jan 2014, 15:13
Wohnort: 127.0.0.1

Re: Komplettes GameProfile fetchen (mit Skin & Cape)

Beitragvon IK_Raptor » Mo 17. Nov 2014, 15:54

Ja, ich schätze mal schon, dass man den Skin eines Spielers ändern darf. Nur wird in der EULA ausdrücklich der Verkauf sowie das Weggeben von Capes verboten.
Benutzeravatar
IK_Raptor
 
Beiträge: 609
Registriert: Mo 12. Aug 2013, 15:37

Re: Komplettes GameProfile fetchen (mit Skin & Cape)

Beitragvon naturboy » Mo 17. Nov 2014, 16:59

Und dann muss man das noch mit einem packet senden oder?
Könntest du noch hinzufügen wie das geht?
Benutzeravatar
naturboy
 
Beiträge: 279
Registriert: Mi 17. Jul 2013, 17:45

Re: Komplettes GameProfile fetchen (mit Skin & Cape)

Beitragvon Jofkos » Mo 17. Nov 2014, 17:10

naturboy hat geschrieben:Und dann muss man das noch mit einem packet senden oder?
Könntest du noch hinzufügen wie das geht?
Im PacketPlayOutNamedEntitySpawn wäre das GameProfile z.B. Das Feld 'b'
Jofkos

...........

..Bild
Benutzeravatar
Jofkos
 
Beiträge: 1537
Registriert: So 16. Jun 2013, 22:45

Re: Komplettes GameProfile fetchen (mit Skin & Cape)

Beitragvon Summerfeeling » Mo 17. Nov 2014, 17:16

naturboy hat geschrieben:Und dann muss man das noch mit einem packet senden oder?
Könntest du noch hinzufügen wie das geht?

Kommt auf das Packet an.
Du musst dem Packet dies übergeben (falls der Constructor es annimmt, via. Constructor ansonsten via. Reflections), und dann (generell gesagt)
Code: Alles auswählen
  1. ((CraftPlayer) player).getHandle().playerConnection().sendPacket(Packet);

bei "Out-Packets" oder
Code: Alles auswählen
  1. ((CraftPlayer) player).getHandle().playerConnection().a(Packet);

bei "In-Packets".
Grüße
Summerfeeling | Timo
Benutzeravatar
Summerfeeling
 
Beiträge: 1300
Registriert: Sa 15. Jun 2013, 18:43
Wohnort: Viersen

Re: Komplettes GameProfile fetchen (mit Skin & Cape)

Beitragvon IK_Raptor » Mo 17. Nov 2014, 17:35

Summerfeeling hat geschrieben:
naturboy hat geschrieben:Und dann muss man das noch mit einem packet senden oder?
Könntest du noch hinzufügen wie das geht?

Kommt auf das Packet an.
Du musst dem Packet dies übergeben (falls der Constructor es annimmt, via. Constructor ansonsten via. Reflections), und dann (generell gesagt)
Code: Alles auswählen
  1. ((CraftPlayer) player).getHandle().playerConnection().sendPacket(Packet);

bei "Out-Packets" oder
Code: Alles auswählen
  1. ((CraftPlayer) player).getHandle().playerConnection().a(Packet);

bei "In-Packets".


Ich glaube es geht eher darum, wie man das Feld bearbeitet.
@naturboy
Grundsätzlich musst du, wenn du das GameProfile bereits gegettet hast, in den Properties noch den kodierten Link hinzufügen.
Benutzeravatar
IK_Raptor
 
Beiträge: 609
Registriert: Mo 12. Aug 2013, 15:37

Nächste

Zurück zu Anleitungen

Wer ist online?

Mitglieder in diesem Forum: 0 Mitglieder und 5 Gäste