Changed bypass list to track UUIDs instead of names, in advance of changeable names in Minecraft 1.8.0+. There might be a slight delay in response now when running the /wb bypasslist command, and when running the /wb bypass command on a player who is not online. This is because the Mojang UUID lookup server must be queried in those cases. This may be handled better in a future update if/when Bukkit provides methods for caching and looking up that information.

The UUID/player name lookup code I've used is by evilmidget38, from: http://forums.bukkit.org/threads/player-name-uuid-fetcher.250926/

NOTE: if you have a bypass list saved, it will be wiped when you first run this update. The wipe will only happen once, as it is a safety precaution.
This commit is contained in:
Brettflan 2014-05-28 11:13:16 -05:00
parent 1698548fb5
commit 2984d9513f
7 changed files with 275 additions and 37 deletions

View File

@ -30,7 +30,7 @@
<dependency>
<groupId>org.bukkit</groupId>
<artifactId>bukkit</artifactId>
<version>1.7.2-R0.4-SNAPSHOT</version>
<version>1.7.9-R0.1</version>
</dependency>
<dependency>
<groupId>org.dynmap</groupId>

View File

@ -53,7 +53,7 @@ public class BorderCheckTask implements Runnable
return null;
// if player is in bypass list (from bypass command), allow them beyond border; also ignore players currently being handled already
if (Config.isPlayerBypassing(player.getName()) || handlingPlayers.contains(player.getName().toLowerCase()))
if (Config.isPlayerBypassing(player.getUniqueId()) || handlingPlayers.contains(player.getName().toLowerCase()))
return null;
// tag this player as being handled so we can't get stuck in a loop due to Bukkit currently sometimes repeatedly providing incorrect location through teleport event

View File

@ -6,11 +6,13 @@ import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.UUID;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.FileConfiguration;
@ -36,7 +38,7 @@ public class Config
// actual configuration values which can be changed
private static boolean shapeRound = true;
private static Map<String, BorderData> borders = Collections.synchronizedMap(new LinkedHashMap<String, BorderData>());
private static Set<String> bypassPlayers = Collections.synchronizedSet(new LinkedHashSet<String>());
private static Set<UUID> bypassPlayers = Collections.synchronizedSet(new LinkedHashSet<UUID>());
private static String message; // raw message without color code formatting
private static String messageFmt; // message with color code formatting ("&" changed to funky sort-of-double-dollar-sign for legitimate color/formatting codes)
private static String messageClean; // message cleaned of formatting codes
@ -373,32 +375,42 @@ public class Config
return dynmapMessage;
}
public static void setPlayerBypass(String player, boolean bypass)
public static void setPlayerBypass(UUID player, boolean bypass)
{
if (bypass)
bypassPlayers.add(player.toLowerCase());
bypassPlayers.add(player);
else
bypassPlayers.remove(player.toLowerCase());
bypassPlayers.remove(player);
save(true);
}
public static boolean isPlayerBypassing(String player)
public static boolean isPlayerBypassing(UUID player)
{
return bypassPlayers.contains(player.toLowerCase());
return bypassPlayers.contains(player);
}
public static void togglePlayerBypass(String player)
public static ArrayList<UUID> getPlayerBypassList()
{
setPlayerBypass(player, !isPlayerBypassing(player));
return new ArrayList(bypassPlayers);
}
public static String getPlayerBypassList()
// for converting bypass UUID list to/from String list, for storage in config
private static void importBypassStringList(List<String> strings)
{
if (bypassPlayers.isEmpty())
return "<none>";
String newString = bypassPlayers.toString();
return newString.substring(1, newString.length() - 1);
for (String string: strings)
{
bypassPlayers.add(UUID.fromString(string));
}
}
private static ArrayList<String> exportBypassStringList()
{
ArrayList<String> strings = new ArrayList<String>();
for (UUID uuid: bypassPlayers)
{
strings.add(uuid.toString());
}
return strings;
}
public static boolean isBorderTimerRunning()
@ -526,7 +538,7 @@ public class Config
}
private static final int currentCfgVersion = 10;
private static final int currentCfgVersion = 11;
public static void load(WorldBorder master, boolean logIt)
{ // load config from file
@ -552,7 +564,7 @@ public class Config
killPlayer = cfg.getBoolean("player-killed-bad-spawn", false);
denyEnderpearl = cfg.getBoolean("deny-enderpearl", true);
fillAutosaveFrequency = cfg.getInt("fill-autosave-frequency", 30);
bypassPlayers = Collections.synchronizedSet(new LinkedHashSet<String>(cfg.getStringList("bypass-list")));
importBypassStringList(cfg.getStringList("bypass-list-uuids"));
fillMemoryTolerance = cfg.getInt("fill-memory-tolerance", 500);
StartBorderTimer();
@ -579,6 +591,10 @@ public class Config
if (cfgVersion < 10)
denyEnderpearl = true;
// the border bypass list used to be stored as list of names rather than UUIDs; wipe that old list so the data won't be automatically saved back to the config file again
if (cfgVersion < 11)
cfg.set("bypass-list", null);
ConfigurationSection worlds = cfg.getConfigurationSection("worlds");
if (worlds != null)
{
@ -654,7 +670,7 @@ public class Config
cfg.set("player-killed-bad-spawn", killPlayer);
cfg.set("deny-enderpearl", denyEnderpearl);
cfg.set("fill-autosave-frequency", fillAutosaveFrequency);
cfg.set("bypass-list", new ArrayList<String>(bypassPlayers));
cfg.set("bypass-list-uuids", exportBypassStringList());
cfg.set("fill-memory-tolerance", fillMemoryTolerance);
cfg.set("worlds", null);

View File

@ -0,0 +1,49 @@
package com.wimbli.WorldBorder.UUID;
import com.google.common.collect.ImmutableList;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.Callable;
/*
* code by evilmidget38
* from http://forums.bukkit.org/threads/player-name-uuid-fetcher.250926/
*/
public class NameFetcher implements Callable<Map<UUID, String>> {
private static final String PROFILE_URL = "https://sessionserver.mojang.com/session/minecraft/profile/";
private final JSONParser jsonParser = new JSONParser();
private final List<UUID> uuids;
public NameFetcher(List<UUID> uuids) {
this.uuids = ImmutableList.copyOf(uuids);
}
@Override
public Map<UUID, String> call() throws Exception {
Map<UUID, String> uuidStringMap = new HashMap<UUID, String>();
for (UUID uuid: uuids) {
HttpURLConnection connection = (HttpURLConnection) new URL(PROFILE_URL+uuid.toString().replace("-", "")).openConnection();
JSONObject response = (JSONObject) jsonParser.parse(new InputStreamReader(connection.getInputStream()));
String name = (String) response.get("name");
if (name == null) {
continue;
}
String cause = (String) response.get("cause");
String errorMessage = (String) response.get("errorMessage");
if (cause != null && cause.length() > 0) {
throw new IllegalStateException(errorMessage);
}
uuidStringMap.put(uuid, name);
}
return uuidStringMap;
}
}

View File

@ -0,0 +1,103 @@
package com.wimbli.WorldBorder.UUID;
import com.google.common.collect.ImmutableList;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.ByteBuffer;
import java.util.*;
import java.util.concurrent.Callable;
/*
* code by evilmidget38
* from http://forums.bukkit.org/threads/player-name-uuid-fetcher.250926/
* slightly modified to fix name case mismatches for single name lookup
*/
public class UUIDFetcher implements Callable<Map<String, UUID>> {
private static final double PROFILES_PER_REQUEST = 100;
private static final String PROFILE_URL = "https://api.mojang.com/profiles/minecraft";
private final JSONParser jsonParser = new JSONParser();
private final List<String> names;
private final boolean rateLimiting;
public UUIDFetcher(List<String> names, boolean rateLimiting) {
this.names = ImmutableList.copyOf(names);
this.rateLimiting = rateLimiting;
}
public UUIDFetcher(List<String> names) {
this(names, true);
}
public Map<String, UUID> call() throws Exception {
Map<String, UUID> uuidMap = new HashMap<String, UUID>();
int requests = (int) Math.ceil(names.size() / PROFILES_PER_REQUEST);
for (int i = 0; i < requests; i++) {
HttpURLConnection connection = createConnection();
String body = JSONArray.toJSONString(names.subList(i * 100, Math.min((i + 1) * 100, names.size())));
writeBody(connection, body);
JSONArray array = (JSONArray) jsonParser.parse(new InputStreamReader(connection.getInputStream()));
for (Object profile : array) {
JSONObject jsonProfile = (JSONObject) profile;
String id = (String) jsonProfile.get("id");
String name = (String) jsonProfile.get("name");
UUID uuid = UUIDFetcher.getUUID(id);
uuidMap.put(name.toLowerCase(), uuid);
}
if (rateLimiting && i != requests - 1) {
Thread.sleep(100L);
}
}
return uuidMap;
}
private static void writeBody(HttpURLConnection connection, String body) throws Exception {
OutputStream stream = connection.getOutputStream();
stream.write(body.getBytes());
stream.flush();
stream.close();
}
private static HttpURLConnection createConnection() throws Exception {
URL url = new URL(PROFILE_URL);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("POST");
connection.setRequestProperty("Content-Type", "application/json");
connection.setUseCaches(false);
connection.setDoInput(true);
connection.setDoOutput(true);
return connection;
}
private static UUID getUUID(String id) {
return UUID.fromString(id.substring(0, 8) + "-" + id.substring(8, 12) + "-" + id.substring(12, 16) + "-" + id.substring(16, 20) + "-" +id.substring(20, 32));
}
public static byte[] toBytes(UUID uuid) {
ByteBuffer byteBuffer = ByteBuffer.wrap(new byte[16]);
byteBuffer.putLong(uuid.getMostSignificantBits());
byteBuffer.putLong(uuid.getLeastSignificantBits());
return byteBuffer.array();
}
public static UUID fromBytes(byte[] array) {
if (array.length != 16) {
throw new IllegalArgumentException("Illegal byte array length: " + array.length);
}
ByteBuffer byteBuffer = ByteBuffer.wrap(array);
long mostSignificant = byteBuffer.getLong();
long leastSignificant = byteBuffer.getLong();
return new UUID(mostSignificant, leastSignificant);
}
public static UUID getUUIDOf(String name) throws Exception {
return new UUIDFetcher(Arrays.asList(name)).call().get(name.toLowerCase());
}
}

View File

@ -1,12 +1,14 @@
package com.wimbli.WorldBorder.cmd;
import java.util.List;
import java.util.UUID;
import org.bukkit.Bukkit;
import org.bukkit.command.*;
import org.bukkit.entity.Player;
import com.wimbli.WorldBorder.*;
import com.wimbli.WorldBorder.UUID.UUIDFetcher;
public class CmdBypass extends WBCmd
@ -27,13 +29,15 @@ public class CmdBypass extends WBCmd
@Override
public void cmdStatus(CommandSender sender)
{
boolean bypass = Config.isPlayerBypassing(((Player)sender).getName());
if (sender instanceof Player)
sender.sendMessage(C_HEAD + "Border bypass is currently " + enabledColored(bypass) + C_HEAD + " for you.");
if (!(sender instanceof Player))
return;
boolean bypass = Config.isPlayerBypassing(((Player)sender).getUniqueId());
sender.sendMessage(C_HEAD + "Border bypass is currently " + enabledColored(bypass) + C_HEAD + " for you.");
}
@Override
public void execute(CommandSender sender, Player player, List<String> params, String worldName)
public void execute(final CommandSender sender, final Player player, final List<String> params, String worldName)
{
if (player == null && params.isEmpty())
{
@ -41,21 +45,56 @@ public class CmdBypass extends WBCmd
return;
}
String sPlayer = (params.isEmpty()) ? player.getName() : params.get(0);
Bukkit.getServer().getScheduler().scheduleSyncDelayedTask(WorldBorder.plugin, new Runnable()
{
@Override
public void run()
{
final String sPlayer = (params.isEmpty()) ? player.getName() : params.get(0);
UUID uPlayer = (params.isEmpty()) ? player.getUniqueId() : null;
boolean bypassing = !Config.isPlayerBypassing(sPlayer);
if (params.size() > 1)
bypassing = strAsBool(params.get(1));
if (uPlayer == null)
{
Player p = Bukkit.getPlayer(sPlayer);
if (p != null)
{
uPlayer = p.getUniqueId();
}
else
{
// only do UUID lookup using Mojang server if specified player isn't online
try
{
uPlayer = UUIDFetcher.getUUIDOf(sPlayer);
}
catch(Exception ex)
{
sendErrorAndHelp(sender, "Failed to look up UUID for the player name you specified. " + ex.getLocalizedMessage());
return;
}
}
}
if (uPlayer == null)
{
sendErrorAndHelp(sender, "Failed to look up UUID for the player name you specified; null value returned.");
return;
}
Config.setPlayerBypass(sPlayer, bypassing);
boolean bypassing = !Config.isPlayerBypassing(uPlayer);
if (params.size() > 1)
bypassing = strAsBool(params.get(1));
Player target = Bukkit.getPlayer(sPlayer);
if (target != null && target.isOnline())
target.sendMessage("Border bypass is now " + enabledColored(bypassing) + ".");
Config.setPlayerBypass(uPlayer, bypassing);
Config.log("Border bypass for player \"" + sPlayer + "\" is " + (bypassing ? "enabled" : "disabled") +
(player != null ? " at the command of player \"" + player.getName() + "\"" : "") + ".");
if (player != null && player != target)
sender.sendMessage("Border bypass for player \"" + sPlayer + "\" is " + enabledColored(bypassing) + ".");
Player target = Bukkit.getPlayer(sPlayer);
if (target != null && target.isOnline())
target.sendMessage("Border bypass is now " + enabledColored(bypassing) + ".");
Config.log("Border bypass for player \"" + sPlayer + "\" is " + (bypassing ? "enabled" : "disabled") +
(player != null ? " at the command of player \"" + player.getName() + "\"" : "") + ".");
if (player != null && player != target)
sender.sendMessage("Border bypass for player \"" + sPlayer + "\" is " + enabledColored(bypassing) + ".");
}
});
}
}

View File

@ -1,11 +1,16 @@
package com.wimbli.WorldBorder.cmd;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import org.bukkit.Bukkit;
import org.bukkit.command.*;
import org.bukkit.entity.Player;
import com.wimbli.WorldBorder.*;
import com.wimbli.WorldBorder.UUID.NameFetcher;
public class CmdBypasslist extends WBCmd
@ -21,8 +26,34 @@ public class CmdBypasslist extends WBCmd
}
@Override
public void execute(CommandSender sender, Player player, List<String> params, String worldName)
public void execute(final CommandSender sender, Player player, List<String> params, String worldName)
{
sender.sendMessage("Players with border bypass enabled: " + Config.getPlayerBypassList());
final ArrayList<UUID> uuids = Config.getPlayerBypassList();
if (uuids == null || uuids.isEmpty())
{
sender.sendMessage("Players with border bypass enabled: <none>");
return;
}
Bukkit.getServer().getScheduler().scheduleSyncDelayedTask(WorldBorder.plugin, new Runnable()
{
@Override
public void run()
{
try
{
NameFetcher fetcher = new NameFetcher(uuids);
Map<UUID, String> names = fetcher.call();
String nameString = names.values().toString();
sender.sendMessage("Players with border bypass enabled: " + nameString.substring(1, nameString.length() - 1));
}
catch(Exception ex)
{
sendErrorAndHelp(sender, "Failed to look up names for the UUIDs in the border bypass list. " + ex.getLocalizedMessage());
return;
}
}
});
}
}