Util Player name history lookup!

Discussion in 'Resources' started by mine-care, Mar 25, 2016.

Thread Status:
Not open for further replies.
  1. Offline

    mine-care

    PreviousPlayerNameEntryAs you know, since 1.8 a player may change their name in Minecraft once every so often. That leads to confusion in many servers where people change their MC names and are not recognized by their friends, and it is not very convenient to stop your game to lookup their name history really...
    That isnt the only case, some plugins may be in the process of changing their stored data from name-based to UUID-based (as they should have done), but with the players changing their name, their data cannot be located with their current name, and their original one has to be found.
    These are just two reasons why i created a Utility class that essentially uses Mojang API to find the name history of a player and turn the JSON String returned, into Java Objects so that they can be used by your plugin(s) easily.

    This is the class:
    Code:
    import java.io.BufferedReader;
    import java.io.IOException;
    import java.io.InputStreamReader;
    import java.net.HttpURLConnection;
    import java.net.URL;
    import java.util.Date;
    import java.util.UUID;
    
    import org.bukkit.OfflinePlayer;
    
    import com.google.gson.Gson;
    import com.google.gson.JsonObject;
    import com.google.gson.annotations.SerializedName;
    
    /**
    * @author mine-care (AKA fillpant)
    * This class performs a name lookup for a player and gets back all the name changes of the player (if any).
    * @since 25-3-2016
    */
    public class NameLookup {
    
        /**
         * The URL from Mojang API that provides the JSON String in response.
         */
        private static final String LOOKUP_URL = "https://api.mojang.com/user/profiles/%s/names";
    
        /**
         * The URL from Mojang API to resolve the UUID of a player from their name.
         */
        private static final String GET_UUID_URL = "https://api.mojang.com/users/profiles/minecraft/%s?t=0";
        private static final Gson JSON_PARSER = new Gson();
    
        /**
         * <h1>NOTE: Avoid running this method <i>Synchronously</i> with the main thread!It blocks while attempting to get a response from Mojang servers!</h1>
         * @param player The UUID of the player to be looked up.
         * @return Returns an array of {@link PreviousPlayerNameEntry} objects, or null if the response couldn't be interpreted.
         * @throws IOException {@link #getPlayerPreviousNames(String)}
         */
        public static PreviousPlayerNameEntry[] getPlayerPreviousNames(UUID player) throws IOException {
            return getPlayerPreviousNames(player.toString());
        }
    
        /**
         * <h1>NOTE: Avoid running this method <i>Synchronously</i> with the main thread! It blocks while attempting to get a response from Mojang servers!</h1>
         * Alternative method accepting an 'OfflinePlayer' (and therefore 'Player') objects as parameter.
         * @param player The OfflinePlayer object to obtain the UUID from.
         * @return Returns an array of {@link PreviousPlayerNameEntry} objects, or null if the response couldn't be interpreted.
         * @throws IOException {@link #getPlayerPreviousNames(UUID)}
         */
        public static PreviousPlayerNameEntry[] getPlayerPreviousNames(OfflinePlayer player) throws IOException {
            return getPlayerPreviousNames(player.getUniqueId());
        }
    
        /**
         * <h1>NOTE: Avoid running this method <i>Synchronously</i> with the main thread! It blocks while attempting to get a response from Mojang servers!</h1>
         * Alternative method accepting an {@link OfflinePlayer} (and therefore {@link Player}) objects as parameter.
         * @param uuid The UUID String to lookup
         * @return Returns an array of {@link PreviousPlayerNameEntry} objects, or null if the response couldn't be interpreted.
         * @throws IOException {@link #getRawJsonResponse(String)}
         */
        public static PreviousPlayerNameEntry[] getPlayerPreviousNames(String uuid) throws IOException {
            if (uuid == null || uuid.isEmpty())
                return null;
            String response = getRawJsonResponse(new URL(String.format(LOOKUP_URL, uuid)));
            PreviousPlayerNameEntry[] names = JSON_PARSER.fromJson(response, PreviousPlayerNameEntry[].class);
            return names;
        }
    
        /**
         * If you don't have the UUID of a player, this method will resolve it for you.<br>
         * The output of this method may be used directly with {@link #getPlayerPreviousNames(String)}.<br>
         * <b>NOTE: as with the rest, this method opens a connection with a remote server, so running it synchronously will block the main thread which will lead to server lag.</b>
         * @param name The name of the player to lookup.
         * @return A String which represents the player's UUID. <b>Note: the uuid cannot be parsed to a UUID object directly, as it doesnt contain dashes. This feature will be implemented later</b>
         * @throws IOException Inherited by {@link BufferedReader#readLine()}, {@link BufferedReader#close()}, {@link URL}, {@link HttpURLConnection#getInputStream()}
         */
        public static String getPlayerUUID(String name) throws IOException {
            String response = getRawJsonResponse(new URL(String.format(GET_UUID_URL, name)));
            JsonObject o = JSON_PARSER.fromJson(response, JsonObject.class);
            if (o == null)
                return null;
            return o.get("id") == null ? null : o.get("id").getAsString();
        }
    
        /**
         * This is a helper method used to read the response of Mojang's API webservers.
         * @param u the URL to connect to
         * @return a String with the data read.
         * @throws IOException Inherited by {@link BufferedReader#readLine()}, {@link BufferedReader#close()}, {@link URL}, {@link HttpURLConnection#getInputStream()}
         */
        private static String getRawJsonResponse(URL u) throws IOException {
            HttpURLConnection con = (HttpURLConnection) u.openConnection();
            con.setDoInput(true);
            con.setConnectTimeout(2000);
            con.setReadTimeout(2000);
            con.connect();
            BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()));
            String response = in.readLine();
            in.close();
            return response;
        }
    
        /**
         * This class represents the typical response expected by Mojang servers when requesting the name history of a player.
         */
        public class PreviousPlayerNameEntry {
            private String name;
            @SerializedName("changedToAt")
            private long changeTime;
    
            /**
             * Gets the player name of this entry.
             * @return The name of the player.
             */
            public String getPlayerName() {
                return name;
            }
    
            /**
             * Get the time of change of the name.
             * <br><b>Note: This will return 0 if the name is the original (initial) name of the player! Make sure you check if it is 0 before handling!
             * <br>Parsing 0 to a Date will result in the date "01/01/1970".</b>
             * @return a timestamp in miliseconds that you can turn into a date or handle however you want :)
             */
            public long getChangeTime() {
                return changeTime;
            }
    
            /**
             * Check if this name is the name used to register the account (the initial/original name)
             * @return a boolean, true if it is the the very first name of the player, otherwise false.
             */
            public boolean isPlayersInitialName() {
                return getChangeTime() == 0;
            }
    
            @Override
            public String toString() {
                return "Name: " + name + " Date of change: " + new Date(changeTime).toString();
            }
        }
    
    }
    
    How to use:
    It is very simple to use, all you need to do is invoke the apropriate method depending on what you want to do.
    So, Lets supose you have the UUID of a Player, and you want to get their name history.
    All you need to do is:
    Code:
    PreviousPlayerNameEntry[] previousNames = NameLookup.getPlayerPreviousNames(playerUUID);
    As you see, the getPlayerPreviousNames method, returns an array of PreviousPlayerNameEntry objects.
    If you want to present those to the player, you may loop though them and use the information each of them holds (Name, Time of change) to do that.

    Example given:
    Code:
        PreviousPlayerNameEntry[] previousNames = NameLookup.getPlayerPreviousNames(playerUUID);
            for(PreviousPlayerNameEntry entry : previousNames){
                player.sendMessage("Name: "+entry.getPlayerName()+", Time of change: "+ new Date(entry.getChangeTime()));
            }
    As you see here, next to "Time of change" i created a new Date object with parametes the ChangeTime provided by the PreviousPlayerNameEntry. That is because the changeTime is in miliseconds so it will look just like a very large number for most. So that is why, i create a Date object and then invoke toString() so that it will turn into a more human-readable form.

    There is also another method in the class "PreviousPlayerNameEntry", which is called 'isPlayersInitialName' and returns a boolean. Well what it does is that it checks if the name held by this entry is the name under which the accound was registered (the very first name). This is handy, because if it is the very first name, then the "changeTime" will be 0, so that will result in a very spectacular Date! hehe.

    There are three getPlayerPreviousNames methods available, each accepting a different parameter. Use the one that is the most helpful for you. :)
    The problem is, when you dont have the UUID of a player, and you dont have a way to obtain it on the server side either because the server holds no Player.dat files, or because it is in Offline mode, and UUID's are messed up (NOT RECOMENDED). In this case, where you have the name and not the UUID, you cannot use these methods D: But dont worry, there is a method called 'getPlayerUUID' that resolves the UUID of a player given the player's name! :D Keep in mind that it should only be used when there is no other (more efficient) way through!!! and the reason is that it sends a request to an external server (of Mojang) that may take time to respond which can be avoided. So use wisely!

    For further information about all these methods, refer to the docs they come with :)

    Last but not least, the most important note here that is also underlined in the docs:

    DONT run these methods synchronosuly with the main thread!!! Run them asynchronously!!!
    Because they perform a request to external servers, delays may be caused untill the response comes back! If that runs synchronously with the main thread, i twill cause it to block for as long as it doesnt receive a response, which means... server lag that can take great extends!!! This aplies to ALL methods held in the class above.


    Thank you :)
    Waiting for your comments!
     
  2. Offline

    mcdorli

    I think it announces them as "XYZ has logged in (formerly known as ZYX)"
     
  3. Offline

    mine-care

    @mcdorli ?
    Sorry i didnt get that :-?
     
  4. Offline

    ChipDev

    If my name was "Hi", and I changed it to "Goodbye", it would announce Goodbye has logged in (formerly known as Hi)" :) Awesome tut btw! Never knew it was possible this way!!!
     
    mine-care likes this.
  5. Offline

    mine-care

    @ChipDev oh intresting... I got this message disabled by default so I didn't notice :S
    And thanks :) with it you can find the in-between names of a player, not just the very first and current ones ;)
     
    ChipDev likes this.
Thread Status:
Not open for further replies.

Share This Page