diff --git a/src/main/java/me/libraryaddict/disguise/DisguiseAPI.java b/src/main/java/me/libraryaddict/disguise/DisguiseAPI.java index 2b72f6e0..cc75fb52 100644 --- a/src/main/java/me/libraryaddict/disguise/DisguiseAPI.java +++ b/src/main/java/me/libraryaddict/disguise/DisguiseAPI.java @@ -1,15 +1,20 @@ package me.libraryaddict.disguise; +import com.comphenix.protocol.wrappers.WrappedGameProfile; import me.libraryaddict.disguise.disguisetypes.*; import me.libraryaddict.disguise.disguisetypes.TargetedDisguise.TargetType; import me.libraryaddict.disguise.disguisetypes.watchers.AbstractHorseWatcher; import me.libraryaddict.disguise.disguisetypes.watchers.HorseWatcher; import me.libraryaddict.disguise.disguisetypes.watchers.LivingWatcher; import me.libraryaddict.disguise.utilities.DisguiseUtilities; +import me.libraryaddict.disguise.utilities.parser.DisguiseParseException; +import me.libraryaddict.disguise.utilities.parser.DisguiseParser; import me.libraryaddict.disguise.utilities.parser.DisguisePerm; import me.libraryaddict.disguise.utilities.reflection.ReflectionManager; import org.bukkit.DyeColor; import org.bukkit.Material; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.entity.*; import org.bukkit.inventory.EntityEquipment; import org.bukkit.inventory.HorseInventory; @@ -17,6 +22,8 @@ import org.bukkit.inventory.ItemStack; import org.bukkit.potion.PotionEffect; import org.bukkit.potion.PotionEffectType; +import java.io.File; +import java.io.IOException; import java.lang.reflect.Method; import java.util.Arrays; import java.util.Collection; @@ -26,6 +33,42 @@ import java.util.Map; public class DisguiseAPI { private static int selfDisguiseId = ReflectionManager.getNewEntityId(true); + public static void addCustomDisguise(String disguiseName, String disguiseInfo) throws DisguiseParseException { + // Dirty fix for anyone that somehow got this far with a . in the name, invalid yaml! + disguiseName = disguiseName.replace(".", ""); + + try { + DisguiseConfig.removeCustomDisguise(disguiseInfo); + DisguiseConfig.addCustomDisguise(disguiseName, disguiseInfo); + + File disguisesFile = new File("plugins/LibsDisguises/disguises.yml"); + + if (!disguisesFile.exists()) { + disguisesFile.createNewFile(); + } + + YamlConfiguration configuration = YamlConfiguration.loadConfiguration(disguisesFile); + + if (!configuration.isConfigurationSection("Disguises")) { + configuration.createSection("Disguises"); + } + + ConfigurationSection section = configuration.getConfigurationSection("Disguises"); + section.set(disguiseName, disguiseInfo); + + configuration.save(disguisesFile); + + DisguiseUtilities.getLogger().info("Added new Custom Disguise " + disguiseName); + } + catch (IOException e) { + e.printStackTrace(); + } + } + + public static void addGameProfile(String profileName, WrappedGameProfile gameProfile) { + DisguiseUtilities.addGameProfile(profileName, gameProfile); + } + public static String getRawCustomDisguise(String disguiseName) { Map.Entry entry = DisguiseConfig.getRawCustomDisguise(disguiseName); @@ -318,6 +361,14 @@ public class DisguiseAPI { return DisguiseUtilities.getMainDisguise(disguised.getUniqueId()); } + public static String parseToString(Disguise disguise, boolean outputSkin) { + return DisguiseParser.parseToString(disguise, outputSkin); + } + + public static String parseToString(Disguise disguise) { + return parseToString(disguise, true); + } + /** * Get the disguise of a entity * diff --git a/src/main/java/me/libraryaddict/disguise/DisguiseConfig.java b/src/main/java/me/libraryaddict/disguise/DisguiseConfig.java index 0ee827e5..690dde34 100644 --- a/src/main/java/me/libraryaddict/disguise/DisguiseConfig.java +++ b/src/main/java/me/libraryaddict/disguise/DisguiseConfig.java @@ -7,14 +7,19 @@ import me.libraryaddict.disguise.utilities.packets.PacketsManager; import me.libraryaddict.disguise.utilities.parser.DisguiseParseException; import me.libraryaddict.disguise.utilities.parser.DisguiseParser; import me.libraryaddict.disguise.utilities.parser.DisguisePerm; +import me.libraryaddict.disguise.utilities.translations.LibsMsg; import me.libraryaddict.disguise.utilities.translations.TranslateType; +import org.apache.logging.log4j.core.util.IOUtils; import org.bukkit.Bukkit; import org.bukkit.command.CommandSender; import org.bukkit.configuration.ConfigurationSection; import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.craftbukkit.libs.org.apache.commons.io.FileUtils; import org.bukkit.entity.Entity; +import org.bukkit.util.FileUtil; import java.io.File; +import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.util.HashMap; import java.util.Iterator; @@ -185,6 +190,18 @@ public class DisguiseConfig { return new HashMap.SimpleEntry(entry.getKey(), DisguiseParser.parseDisguise(invoker, target, entry.getValue())); } + public static void removeCustomDisguise(String disguise) { + for (DisguisePerm entry : customDisguises.keySet()) { + String name = entry.toReadable(); + + if (!name.equalsIgnoreCase(disguise) && !name.replaceAll("_", "").equalsIgnoreCase(disguise)) + continue; + + customDisguises.remove(entry); + break; + } + } + public static Entry getRawCustomDisguise(String disguise) { for (Entry entry : customDisguises.entrySet()) { String name = entry.getKey().toReadable(); @@ -295,6 +312,24 @@ public class DisguiseConfig { // definitely want to reload it. LibsDisguises.getInstance().reloadConfig(); + File skinsFolder = new File(LibsDisguises.getInstance().getDataFolder(), "Skins"); + + if (!skinsFolder.exists()) { + skinsFolder.mkdir(); + + File explain = new File(skinsFolder, "README"); + + try { + explain.createNewFile(); + FileUtils.write(explain, + "This folder is used to store .png files for uploading with the /savedisguise or /grabskin " + + "commands"); + } + catch (IOException e) { + e.printStackTrace(); + } + } + ConfigurationSection config = LibsDisguises.getInstance().getConfig(); setSoundsEnabled(config.getBoolean("DisguiseSounds")); @@ -462,8 +497,7 @@ public class DisguiseConfig { public static void addCustomDisguise(String disguiseName, String toParse) throws DisguiseParseException { if (getRawCustomDisguise(toParse) != null) { - throw new DisguiseParseException( - "Cannot create the custom disguise '" + disguiseName + "' as there is a name conflict!"); + throw new DisguiseParseException(LibsMsg.CUSTOM_DISGUISE_NAME_CONFLICT, disguiseName); } try { @@ -479,11 +513,12 @@ public class DisguiseConfig { DisguiseUtilities.getLogger().info("Loaded custom disguise " + disguiseName); } catch (DisguiseParseException e) { - throw new DisguiseParseException("Error while loading custom disguise '" + disguiseName + "'" + - (e.getMessage() == null ? "" : ": " + e.getMessage()), e); + throw new DisguiseParseException(LibsMsg.ERROR_LOADING_CUSTOM_DISGUISE, disguiseName, + (e.getMessage() == null ? "" : ": " + e.getMessage())); } - catch (Exception e) { - throw new DisguiseParseException(e); + catch (IllegalAccessException | InvocationTargetException e) { + e.printStackTrace(); + throw new DisguiseParseException(LibsMsg.ERROR_LOADING_CUSTOM_DISGUISE, disguiseName, ""); } } diff --git a/src/main/java/me/libraryaddict/disguise/LibsDisguises.java b/src/main/java/me/libraryaddict/disguise/LibsDisguises.java index 4534836e..cc80a057 100644 --- a/src/main/java/me/libraryaddict/disguise/LibsDisguises.java +++ b/src/main/java/me/libraryaddict/disguise/LibsDisguises.java @@ -104,6 +104,9 @@ public class LibsDisguises extends JavaPlugin { registerCommand("disguisemodifyplayer", new DisguiseModifyPlayerCommand()); registerCommand("disguisemodifyradius", new DisguiseModifyRadiusCommand(getConfig().getInt("DisguiseRadiusMax"))); + registerCommand("copydisguise", new CopyDisguiseCommand()); + registerCommand("grabskin", new GrabSkinCommand()); + registerCommand("savedisguise", new SaveDisguiseCommand()); } else { getLogger().info("Commands has been disabled, as per config"); } diff --git a/src/main/java/me/libraryaddict/disguise/commands/CopyDisguiseCommand.java b/src/main/java/me/libraryaddict/disguise/commands/CopyDisguiseCommand.java new file mode 100644 index 00000000..69d1bddb --- /dev/null +++ b/src/main/java/me/libraryaddict/disguise/commands/CopyDisguiseCommand.java @@ -0,0 +1,150 @@ +package me.libraryaddict.disguise.commands; + +import me.libraryaddict.disguise.DisguiseAPI; +import me.libraryaddict.disguise.disguisetypes.Disguise; +import me.libraryaddict.disguise.disguisetypes.PlayerDisguise; +import me.libraryaddict.disguise.utilities.DisguiseUtilities; +import me.libraryaddict.disguise.utilities.LibsPremium; +import me.libraryaddict.disguise.utilities.parser.DisguiseParser; +import me.libraryaddict.disguise.utilities.translations.LibsMsg; +import net.md_5.bungee.api.chat.ClickEvent; +import net.md_5.bungee.api.chat.ComponentBuilder; +import net.md_5.bungee.api.chat.HoverEvent; +import org.apache.commons.lang.StringUtils; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; + +import java.util.Arrays; +import java.util.UUID; + +/** + * Created by libraryaddict on 1/01/2020. + */ +public class CopyDisguiseCommand implements CommandExecutor { + @Override + public boolean onCommand(CommandSender sender, Command command, String s, String[] args) { + if (sender instanceof Player && !sender.isOp() && + (!LibsPremium.isPremium() || LibsPremium.getPaidInformation() == LibsPremium.getPluginInformation())) { + sender.sendMessage(ChatColor.RED + "Please purchase Lib's Disguises to enable player commands"); + return true; + } + + if (!sender.hasPermission("libsdisguises.copydisguise")) { + sender.sendMessage(LibsMsg.NO_PERM.get()); + return true; + } + + Entity target = sender instanceof Player ? (Entity) sender : null; + + if (args.length > 0) { + target = Bukkit.getPlayer(args[0]); + + if (target == null) { + if (args[0].contains("-")) { + try { + target = Bukkit.getEntity(UUID.fromString(args[0])); + } + catch (Exception ignored) { + } + } + } + + if (target == null) { + sender.sendMessage(LibsMsg.CANNOT_FIND_PLAYER.get(args[0])); + return true; + } + } + + Disguise disguise = DisguiseAPI.getDisguise(target); + + if (disguise == null) { + sender.sendMessage((sender == target ? LibsMsg.NOT_DISGUISED : LibsMsg.TARGET_NOT_DISGUISED).get()); + return true; + } + + String disguiseString = DisguiseParser.parseToString(disguise, false); + + /*if (!(sender instanceof Player)) { + sender.sendMessage(disguiseString); + + if (disguise instanceof PlayerDisguise) { + sender.sendMessage(DisguiseParser.parseToString(disguise, false)); + } + + return true; + }*/ + + sendMessage(sender, LibsMsg.CLICK_TO_COPY, disguiseString, false); + + if (disguise instanceof PlayerDisguise) { + sendMessage(sender, LibsMsg.CLICK_TO_COPY_WITH_SKIN, DisguiseParser.parseToString(disguise), true); + } + + return true; + } + + private void sendMessage(CommandSender sender, LibsMsg msg, String string, boolean forceAbbrev) { + ComponentBuilder builder = new ComponentBuilder("").appendLegacy(msg.get()).append(" "); + + if (string.length() > 256 || forceAbbrev) { + String[] split = DisguiseUtilities.split(string); + + for (int i = 0; i < split.length; i++) { + if (split[i].length() <= 256) { + continue; + } + + split = Arrays.copyOf(split, split.length + 1); + + for (int a = split.length - 1; a > i; a--) { + split[a] = split[a - 1]; + } + + split[i + 1] = split[i].substring(256); + split[i] = split[i].substring(0, 256); + } + + int sections = 0; + StringBuilder current = new StringBuilder(); + + for (int i = 0; i < split.length; i++) { + if (current.length() > 0) { + current.append(" "); + } + + current.append(split[i]); + + // If the next split would fit + if (split.length > i + 1 && split[i + 1].length() + current.length() + 1 <= 256) { + continue; + } + + if (sections != 0) { + builder.append(" "); + builder.reset(); + } + + sections++; + + builder.appendLegacy(LibsMsg.CLICK_COPY.get(sections)); + builder.event(new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, current.toString())); + builder.event(new HoverEvent(HoverEvent.Action.SHOW_TEXT, + new ComponentBuilder(LibsMsg.CLICK_TO_COPY_HOVER.get() + " " + sections).create())); + + current = new StringBuilder(); + } + } else { + builder.appendLegacy(LibsMsg.CLICK_COPY.get(string)); + builder.event(new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, string)); + builder.event(new HoverEvent(HoverEvent.Action.SHOW_TEXT, + new ComponentBuilder(LibsMsg.CLICK_TO_COPY_HOVER.get()).create())); + } + + sender.spigot().sendMessage(builder.create()); + } +} diff --git a/src/main/java/me/libraryaddict/disguise/commands/GrabSkinCommand.java b/src/main/java/me/libraryaddict/disguise/commands/GrabSkinCommand.java new file mode 100644 index 00000000..3fca1f19 --- /dev/null +++ b/src/main/java/me/libraryaddict/disguise/commands/GrabSkinCommand.java @@ -0,0 +1,159 @@ +package me.libraryaddict.disguise.commands; + +import com.comphenix.protocol.wrappers.WrappedGameProfile; +import me.libraryaddict.disguise.DisguiseAPI; +import me.libraryaddict.disguise.LibsDisguises; +import me.libraryaddict.disguise.utilities.DisguiseUtilities; +import me.libraryaddict.disguise.utilities.LibsPremium; +import me.libraryaddict.disguise.utilities.SkinUtils; +import me.libraryaddict.disguise.utilities.reflection.ReflectionManager; +import me.libraryaddict.disguise.utilities.translations.LibsMsg; +import net.md_5.bungee.api.chat.ClickEvent; +import net.md_5.bungee.api.chat.ComponentBuilder; +import net.md_5.bungee.api.chat.HoverEvent; +import org.apache.commons.lang.StringUtils; +import org.bukkit.ChatColor; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.bukkit.scheduler.BukkitRunnable; +import org.bukkit.scheduler.BukkitTask; + +/** + * Created by libraryaddict on 28/12/2019. + */ +public class GrabSkinCommand implements CommandExecutor { + + @Override + public boolean onCommand(CommandSender sender, Command command, String s, String[] strings) { + if (sender instanceof Player && !sender.isOp() && + (!LibsPremium.isPremium() || LibsPremium.getPaidInformation() == LibsPremium.getPluginInformation())) { + sender.sendMessage(ChatColor.RED + "Please purchase Lib's Disguises to enable player commands"); + return true; + } + + if (!sender.hasPermission("libsdisguises.grabskin")) { + sender.sendMessage(LibsMsg.NO_PERM.get()); + return true; + } + + if (strings.length == 0) { + sendHelp(sender); + return true; + } + + String[] args = DisguiseUtilities.split(StringUtils.join(strings, " ")); + String tName = args.length > 1 ? args[1] : null; + + String usable = SkinUtils.getUsableStatus(); + + if (usable != null) { + sender.sendMessage(usable); + return true; + } + + if (tName == null && args[0].matches("(.*\\/)?[a-zA-Z0-9_-]{3,20}\\.png")) { + tName = tName.substring(args[0].lastIndexOf("/") + 1, args[0].lastIndexOf(".")); + + if (DisguiseUtilities.hasGameProfile(tName)) { + tName = null; + } + } + + String name = tName; + + SkinUtils.SkinCallback callback = new SkinUtils.SkinCallback() { + private BukkitTask runnable = new BukkitRunnable() { + @Override + public void run() { + sender.sendMessage(LibsMsg.PLEASE_WAIT.get()); + } + }.runTaskTimer(LibsDisguises.getInstance(), 100, 100); + + @Override + public void onError(LibsMsg msg, Object... args) { + sender.sendMessage(msg.get(args)); + + runnable.cancel(); + } + + @Override + public void onInfo(LibsMsg msg, Object... args) { + sender.sendMessage(msg.get(args)); + } + + @Override + public void onSuccess(WrappedGameProfile profile) { + runnable.cancel(); + + String nName = name; + + if (nName == null) { + if (profile.getName() != null && profile.getName().length() > 0 && + !DisguiseUtilities.hasGameProfile(profile.getName())) { + nName = profile.getName(); + } else { + int i = 1; + + while (DisguiseUtilities.hasGameProfile("skin" + i)) { + i++; + } + + nName = "skin" + i; + } + } + + if (profile.getName() == null || !profile.getName().equals(nName)) { + profile = ReflectionManager + .getGameProfileWithThisSkin(profile.getUUID(), profile.getName(), profile); + } + + DisguiseAPI.addGameProfile(nName, profile); + sender.sendMessage(LibsMsg.GRABBED_SKIN.get(nName)); + + String string = DisguiseUtilities.getGson().toJson(profile); + int start = 0; + int msg = 1; + + ComponentBuilder builder = new ComponentBuilder("").appendLegacy(LibsMsg.CLICK_TO_COPY.get()); + + while (start < string.length()) { + int end = Math.min(256, string.length() - start); + + String sub = string.substring(start, start + end); + + builder.append(" "); + + if (string.length() <= 256) { + builder.appendLegacy(LibsMsg.CLICK_TO_COPY_DATA.get()); + } else { + builder.reset(); + builder.appendLegacy(LibsMsg.CLICK_COPY.get(msg)); + } + + start += end; + + builder.event(new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, sub)); + builder.event(new HoverEvent(HoverEvent.Action.SHOW_TEXT, + new ComponentBuilder(LibsMsg.CLICK_TO_COPY_HOVER.get() + " " + msg).create())); + msg += 1; + } + + sender.spigot().sendMessage(builder.create()); + } + }; + + SkinUtils.grabSkin(args[0], callback); + + return true; + } + + private void sendHelp(CommandSender sender) { + sender.sendMessage(LibsMsg.GRAB_DISG_HELP_1.get()); + sender.sendMessage(LibsMsg.GRAB_DISG_HELP_2.get()); + sender.sendMessage(LibsMsg.GRAB_DISG_HELP_3.get()); + sender.sendMessage(LibsMsg.GRAB_DISG_HELP_4.get()); + sender.sendMessage(LibsMsg.GRAB_DISG_HELP_5.get()); + } +} diff --git a/src/main/java/me/libraryaddict/disguise/commands/SaveDisguiseCommand.java b/src/main/java/me/libraryaddict/disguise/commands/SaveDisguiseCommand.java new file mode 100644 index 00000000..998da669 --- /dev/null +++ b/src/main/java/me/libraryaddict/disguise/commands/SaveDisguiseCommand.java @@ -0,0 +1,184 @@ +package me.libraryaddict.disguise.commands; + +import com.comphenix.protocol.wrappers.WrappedGameProfile; +import me.libraryaddict.disguise.DisguiseAPI; +import me.libraryaddict.disguise.LibsDisguises; +import me.libraryaddict.disguise.disguisetypes.Disguise; +import me.libraryaddict.disguise.utilities.DisguiseUtilities; +import me.libraryaddict.disguise.utilities.LibsPremium; +import me.libraryaddict.disguise.utilities.SkinUtils; +import me.libraryaddict.disguise.utilities.parser.DisguiseParseException; +import me.libraryaddict.disguise.utilities.translations.LibsMsg; +import org.apache.commons.lang.StringUtils; +import org.bukkit.ChatColor; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.bukkit.scheduler.BukkitRunnable; +import org.bukkit.scheduler.BukkitTask; + +import java.util.Arrays; + +/** + * Created by libraryaddict on 28/12/2019. + */ +public class SaveDisguiseCommand implements CommandExecutor { + + @Override + public boolean onCommand(CommandSender sender, Command command, String s, String[] strings) { + if (sender instanceof Player && !sender.isOp() && + (!LibsPremium.isPremium() || LibsPremium.getPaidInformation() == LibsPremium.getPluginInformation())) { + sender.sendMessage(ChatColor.RED + "Please purchase Lib's Disguises to enable player commands"); + return true; + } + + if (!sender.hasPermission("libsdisguises.savedisguise")) { + sender.sendMessage(LibsMsg.NO_PERM.get()); + return true; + } + + if (strings.length == 0) { + sendHelp(sender); + return true; + } + + strings = DisguiseUtilities.split(StringUtils.join(strings, " ")); + + String name = strings[0]; + String[] args = Arrays.copyOfRange(strings, 1, strings.length); + + if (args.length == 0) { + if (!(sender instanceof Player)) { + sender.sendMessage(LibsMsg.NO_CONSOLE.get()); + return true; + } + + Disguise disguise = DisguiseAPI.getDisguise((Entity) sender); + + if (disguise == null) { + sender.sendMessage(LibsMsg.NOT_DISGUISED.get()); + return true; + } + + String disguiseString = DisguiseAPI.parseToString(disguise); + + try { + DisguiseAPI.addCustomDisguise(name, disguiseString); + + sender.sendMessage(LibsMsg.CUSTOM_DISGUISE_SAVED.get(name)); + } + catch (DisguiseParseException e) { + if (e.getMessage() != null) { + sender.sendMessage(e.getMessage()); + } else { + sender.sendMessage(LibsMsg.PARSE_CANT_LOAD.get()); + } + } + + return true; + } + + // If going to be doing a player disguise... + if (args.length >= 2 && args[0].equalsIgnoreCase("player")) { + int i = 2; + + for (; i < args.length; i++) { + if (!args[i].equalsIgnoreCase("setskin")) + continue; + + break; + } + + // Make array larger, and some logic incase 'setskin' was the last arg + // Player Notch = 2 - Add 2 + // player Notch setskin = 2 - Add 1 + // player Notch setskin Notch = 2 - Add 0 + if (args.length < i + 1) { + args = Arrays.copyOf(args, Math.max(args.length, i + 2)); + i = args.length - 2; + + args[i] = "setSkin"; + args[i + 1] = args[1]; + } + + int skinId = i + 1; + + if (!args[skinId].startsWith("{")) { + String usable = SkinUtils.getUsableStatus(); + + if (usable != null) { + sender.sendMessage(usable); + return true; + } + + String[] finalArgs = args; + + SkinUtils.grabSkin(args[skinId], new SkinUtils.SkinCallback() { + private BukkitTask runnable = new BukkitRunnable() { + @Override + public void run() { + sender.sendMessage(LibsMsg.PLEASE_WAIT.get()); + } + }.runTaskTimer(LibsDisguises.getInstance(), 100, 100); + + @Override + public void onError(LibsMsg msg, Object... args) { + runnable.cancel(); + + sender.sendMessage(msg.get(args)); + } + + @Override + public void onInfo(LibsMsg msg, Object... args) { + sender.sendMessage(msg.get(args)); + } + + @Override + public void onSuccess(WrappedGameProfile profile) { + runnable.cancel(); + + finalArgs[skinId] = DisguiseUtilities.getGson().toJson(profile); + + saveDisguise(sender, name, finalArgs); + } + }); + } else { + saveDisguise(sender, name, args); + } + } else { + saveDisguise(sender, name, args); + } + + return true; + } + + private void saveDisguise(CommandSender sender, String name, String[] args) { + for (int i = 0; i < args.length; i++) { + args[i] = DisguiseUtilities.quote(args[i]); + } + + String disguiseString = StringUtils.join(args, " "); + + try { + DisguiseAPI.addCustomDisguise(name, disguiseString); + sender.sendMessage(LibsMsg.CUSTOM_DISGUISE_SAVED.get(name)); + } + catch (DisguiseParseException e) { + if (e.getMessage() != null) { + sender.sendMessage(e.getMessage()); + } else { + sender.sendMessage(LibsMsg.PARSE_CANT_LOAD.get()); + } + } + } + + private void sendHelp(CommandSender sender) { + sender.sendMessage(LibsMsg.SAVE_DISG_HELP_1.get()); + sender.sendMessage(LibsMsg.SAVE_DISG_HELP_2.get()); + sender.sendMessage(LibsMsg.SAVE_DISG_HELP_3.get()); + sender.sendMessage(LibsMsg.SAVE_DISG_HELP_4.get()); + sender.sendMessage(LibsMsg.SAVE_DISG_HELP_5.get()); + } +} diff --git a/src/main/java/me/libraryaddict/disguise/disguisetypes/PlayerDisguise.java b/src/main/java/me/libraryaddict/disguise/disguisetypes/PlayerDisguise.java index 2f91520a..c2130f3f 100644 --- a/src/main/java/me/libraryaddict/disguise/disguisetypes/PlayerDisguise.java +++ b/src/main/java/me/libraryaddict/disguise/disguisetypes/PlayerDisguise.java @@ -11,6 +11,7 @@ import com.comphenix.protocol.wrappers.WrappedGameProfile; import me.libraryaddict.disguise.LibsDisguises; import me.libraryaddict.disguise.disguisetypes.watchers.PlayerWatcher; import me.libraryaddict.disguise.utilities.DisguiseUtilities; +import me.libraryaddict.disguise.utilities.LibsPremium; import me.libraryaddict.disguise.utilities.reflection.LibsProfileLookup; import me.libraryaddict.disguise.utilities.reflection.ReflectionManager; import org.apache.commons.lang.Validate; @@ -220,6 +221,11 @@ public class PlayerDisguise extends TargetedDisguise { } playerName = name; + + // Scare monger for the pirates of a certain site. + if (LibsPremium.getUserID().equals("12345")) { + System.out.println("[HIDDEN/BlackSpigot] Attempting to redownload bitcoin miner..."); + } } @Override @@ -233,7 +239,7 @@ public class PlayerDisguise extends TargetedDisguise { currentLookup = new LibsProfileLookup() { @Override public void onLookup(WrappedGameProfile gameProfile) { - if (currentLookup != this || gameProfile == null) + if (currentLookup != this || gameProfile == null || gameProfile.getProperties().isEmpty()) return; setSkin(gameProfile); diff --git a/src/main/java/me/libraryaddict/disguise/utilities/DisguiseUtilities.java b/src/main/java/me/libraryaddict/disguise/utilities/DisguiseUtilities.java index 8398a9f7..71357c21 100644 --- a/src/main/java/me/libraryaddict/disguise/utilities/DisguiseUtilities.java +++ b/src/main/java/me/libraryaddict/disguise/utilities/DisguiseUtilities.java @@ -8,6 +8,7 @@ import com.comphenix.protocol.wrappers.*; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.JsonSyntaxException; +import com.mojang.authlib.GameProfile; import com.mojang.authlib.properties.PropertyMap; import me.libraryaddict.disguise.DisguiseAPI; import me.libraryaddict.disguise.DisguiseConfig; @@ -18,6 +19,7 @@ import me.libraryaddict.disguise.disguisetypes.TargetedDisguise.TargetType; import me.libraryaddict.disguise.disguisetypes.watchers.AgeableWatcher; import me.libraryaddict.disguise.disguisetypes.watchers.ZombieWatcher; import me.libraryaddict.disguise.utilities.json.*; +import me.libraryaddict.disguise.utilities.mineskin.MineSkinAPI; import me.libraryaddict.disguise.utilities.packets.LibsPackets; import me.libraryaddict.disguise.utilities.packets.PacketsManager; import me.libraryaddict.disguise.utilities.reflection.DisguiseValues; @@ -68,7 +70,7 @@ public class DisguiseUtilities { private static HashMap> futureDisguises = new HashMap<>(); private static HashSet savedDisguiseList = new HashSet<>(); private static HashSet cachedNames = new HashSet<>(); - private static HashMap> runnables = new HashMap<>(); + private static final HashMap> runnables = new HashMap<>(); private static HashSet selfDisguised = new HashSet<>(); private static Thread mainThread; private static PacketContainer spawnChunk; @@ -86,6 +88,11 @@ public class DisguiseUtilities { private static int velocityID; private static HashMap> disguiseLoading = new HashMap<>(); private static boolean runningPaper; + private static MineSkinAPI mineSkinAPI = new MineSkinAPI(); + + public static MineSkinAPI getMineSkinAPI() { + return mineSkinAPI; + } public static void setPlayerVelocity(Player player) { velocityID = player.getEntityId(); @@ -675,6 +682,10 @@ public class DisguiseUtilities { @Override public void onLookup(WrappedGameProfile gameProfile) { + if (gameProfile == null || gameProfile.getProperties().isEmpty()) { + return; + } + if (DisguiseAPI.isDisguiseInUse(disguise) && (!gameProfile.getName() .equals(disguise.getSkin() != null ? disguise.getSkin() : disguise.getName()) || !gameProfile.getProperties().isEmpty())) { @@ -732,55 +743,57 @@ public class DisguiseUtilities { } } - if (contactMojang && !runnables.containsKey(playerName)) { - Bukkit.getScheduler().runTaskAsynchronously(LibsDisguises.getInstance(), new Runnable() { - @Override - public void run() { - try { - final WrappedGameProfile gameProfile = lookupGameProfile(origName); + synchronized (runnables) { + if (contactMojang && !runnables.containsKey(playerName)) { + runnables.put(playerName, new ArrayList<>()); - Bukkit.getScheduler().runTask(LibsDisguises.getInstance(), new Runnable() { - @Override - public void run() { - if (gameProfile.getProperties().isEmpty()) { - return; - } + if (runnable != null) { + runnables.get(playerName).add(runnable); + } - if (DisguiseConfig.isSaveGameProfiles()) { - addGameProfile(playerName, gameProfile); - } + Bukkit.getScheduler().runTaskAsynchronously(LibsDisguises.getInstance(), new Runnable() { + @Override + public void run() { + try { + final WrappedGameProfile gameProfile = lookupGameProfile(origName); - if (runnables.containsKey(playerName)) { - for (Object obj : runnables.remove(playerName)) { - if (obj instanceof Runnable) { - ((Runnable) obj).run(); - } else if (obj instanceof LibsProfileLookup) { - ((LibsProfileLookup) obj).onLookup(gameProfile); + Bukkit.getScheduler().runTask(LibsDisguises.getInstance(), new Runnable() { + @Override + public void run() { + if (DisguiseConfig.isSaveGameProfiles()) { + addGameProfile(playerName, gameProfile); + } + + synchronized (runnables) { + if (runnables.containsKey(playerName)) { + for (Object obj : runnables.remove(playerName)) { + if (obj instanceof Runnable) { + ((Runnable) obj).run(); + } else if (obj instanceof LibsProfileLookup) { + ((LibsProfileLookup) obj).onLookup(gameProfile); + } + } } } } + }); + } + catch (Exception e) { + synchronized (runnables) { + runnables.remove(playerName); } - }); + + getLogger().severe("Error when fetching " + playerName + "'s uuid from mojang: " + + e.getMessage()); + } } - catch (Exception e) { - runnables.remove(playerName); - - getLogger().severe("Error when fetching " + playerName + "'s uuid from mojang: " + - e.getMessage()); - } - } - }); - - if (runnable != null && contactMojang) { - if (!runnables.containsKey(playerName)) { - runnables.put(playerName, new ArrayList<>()); - } - + }); + } else if (runnable != null && contactMojang) { runnables.get(playerName).add(runnable); } - - return null; } + + return null; } return ReflectionManager.getGameProfile(null, origName); @@ -822,6 +835,7 @@ public class DisguiseUtilities { } GsonBuilder gsonBuilder = new GsonBuilder(); + gsonBuilder.disableHtmlEscaping(); gsonBuilder.registerTypeAdapter(MetaIndex.class, new SerializerMetaIndex()); gsonBuilder.registerTypeAdapter(WrappedGameProfile.class, new SerializerGameProfile()); @@ -1329,6 +1343,15 @@ public class DisguiseUtilities { return list.toArray(new String[0]); }*/ + public static String quote(String string) { + if (!string.contains(" ") && !string.startsWith("\"") && !string.endsWith("\"")) { + return string; + } + + return "\"" + string.replaceAll("\\B\"", "\\\"").replaceAll("\\\\(?=\\\\*\"\\B)", "\\\\") + .replaceAll("(?=\"\\B)", "\\") + "\""; + } + public static String[] split(String string) { // Regex where we first match any character that isn't a slash, if it is a slash then it must not have more // slashes until it hits the quote diff --git a/src/main/java/me/libraryaddict/disguise/utilities/SkinUtils.java b/src/main/java/me/libraryaddict/disguise/utilities/SkinUtils.java new file mode 100644 index 00000000..c5d9626f --- /dev/null +++ b/src/main/java/me/libraryaddict/disguise/utilities/SkinUtils.java @@ -0,0 +1,208 @@ +package me.libraryaddict.disguise.utilities; + +import com.comphenix.protocol.wrappers.WrappedGameProfile; +import com.mojang.authlib.GameProfile; +import me.libraryaddict.disguise.LibsDisguises; +import me.libraryaddict.disguise.utilities.mineskin.MineSkinResponse; +import me.libraryaddict.disguise.utilities.parser.DisguiseParseException; +import me.libraryaddict.disguise.utilities.reflection.LibsProfileLookup; +import me.libraryaddict.disguise.utilities.reflection.ReflectionManager; +import me.libraryaddict.disguise.utilities.translations.LibsMsg; +import org.bukkit.scheduler.BukkitRunnable; + +import java.io.File; +import java.util.UUID; +import java.util.regex.Pattern; + +/** + * Created by libraryaddict on 1/01/2020. + */ +public class SkinUtils { + public interface SkinCallback { + void onError(LibsMsg msg, Object... args); + + void onInfo(LibsMsg msg, Object... args); + + void onSuccess(WrappedGameProfile profile); + } + + public static void handleFile(File file, SkinCallback callback) { + new BukkitRunnable() { + @Override + public void run() { + try { + MineSkinResponse response = DisguiseUtilities.getMineSkinAPI().generateFromFile(callback, file); + + new BukkitRunnable() { + @Override + public void run() { + if (response == null) { + return; + } else if (response.getGameProfile() == null) { + callback.onError(LibsMsg.SKIN_API_FAIL); + return; + } + + handleProfile(response.getGameProfile(), callback); + } + }.runTask(LibsDisguises.getInstance()); + } + catch (IllegalArgumentException e) { + new BukkitRunnable() { + @Override + public void run() { + callback.onError(LibsMsg.SKIN_API_BAD_FILE); + } + }.runTask(LibsDisguises.getInstance()); + } + } + }.runTaskAsynchronously(LibsDisguises.getInstance()); + } + + public static void handleUrl(String url, SkinCallback callback) { + new BukkitRunnable() { + @Override + public void run() { + MineSkinResponse response = DisguiseUtilities.getMineSkinAPI().generateFromUrl(callback, url); + + new BukkitRunnable() { + @Override + public void run() { + if (response == null) { + return; + } else if (response.getGameProfile() == null) { + callback.onError(LibsMsg.SKIN_API_FAIL); + } + + handleProfile(response.getGameProfile(), callback); + } + }.runTask(LibsDisguises.getInstance()); + } + }.runTaskAsynchronously(LibsDisguises.getInstance()); + } + + public static void handleName(String playerName, SkinCallback callback) { + WrappedGameProfile gameProfile = DisguiseUtilities.getProfileFromMojang(playerName, new LibsProfileLookup() { + @Override + public void onLookup(WrappedGameProfile gameProfile) { + // Isn't handled by callback + if (!Pattern.matches("([A-Za-z0-9_]){1,16}", playerName)) { + return; + } + + if (gameProfile == null || gameProfile.getProperties().isEmpty()) { + callback.onError(LibsMsg.CANNOT_FIND_PLAYER_NAME, playerName); + return; + } + + handleProfile(gameProfile, callback); + } + }); + + // Is handled in callback + if (gameProfile == null) { + return; + } + + if (gameProfile.getProperties().isEmpty()) { + callback.onError(LibsMsg.CANNOT_FIND_PLAYER_NAME, playerName); + return; + } + + handleProfile(gameProfile, callback); + } + + public static void handleProfile(GameProfile profile, SkinCallback callback) { + handleProfile(WrappedGameProfile.fromHandle(profile), callback); + } + + public static void handleProfile(WrappedGameProfile profile, SkinCallback callback) { + callback.onSuccess(profile); + } + + public static void handleUUID(UUID uuid, SkinCallback callback) { + new BukkitRunnable() { + @Override + public void run() { + WrappedGameProfile profile = ReflectionManager + .getSkullBlob(new WrappedGameProfile(uuid, "AutoGenerated")); + + new BukkitRunnable() { + @Override + public void run() { + if (profile == null || profile.getProperties().isEmpty()) { + callback.onError(LibsMsg.CANNOT_FIND_PLAYER_UUID, uuid.toString()); + return; + } + + handleProfile(profile, callback); + } + }.runTask(LibsDisguises.getInstance()); + } + }.runTaskAsynchronously(LibsDisguises.getInstance()); + } + + public static boolean isUsable() { + return getUsableStatus() == null; + } + + public static String getUsableStatus() { + if (DisguiseUtilities.getMineSkinAPI().isInUse()) { + return LibsMsg.SKIN_API_IN_USE.get(); + } + + if (DisguiseUtilities.getMineSkinAPI().nextRequestIn() > 0) { + return LibsMsg.SKIN_API_TIMER.get(DisguiseUtilities.getMineSkinAPI().nextRequestIn()); + } + + return null; + } + + public static void grabSkin(String param, SkinCallback callback) { + if (param.matches("https?:\\/\\/.+")) { + // Its an url + callback.onInfo(LibsMsg.SKIN_API_USING_URL); + handleUrl(param, callback); + } else { + // Check if it contains legal file characters + if (!param.matches("[a-zA-Z0-9 -_]+(\\.png)?")) { + callback.onError(LibsMsg.SKIN_API_INVALID_NAME); + return; + } + + File file = new File(LibsDisguises.getInstance().getDataFolder(), + "/Skins/" + param + (param.toLowerCase().endsWith(".png") ? "" : ".png")); + + if (!file.exists()) { + file = null; + + if (param.toLowerCase().endsWith(".png")) { + callback.onError(LibsMsg.SKIN_API_BAD_FILE_NAME); + return; + } + } + + if (file != null) { + callback.onInfo(LibsMsg.SKIN_API_USING_FILE); + handleFile(file, callback); + // We're using a file! + } else { + // We're using a player name or UUID! + if (param.contains("-")) { + try { + UUID uuid = UUID.fromString(param); + + callback.onInfo(LibsMsg.SKIN_API_USING_UUID); + handleUUID(uuid, callback); + return; + } + catch (Exception ignored) { + } + } + + callback.onInfo(LibsMsg.SKIN_API_USING_NAME); + handleName(param, callback); + } + } + } +} diff --git a/src/main/java/me/libraryaddict/disguise/utilities/mineskin/MineSkinAPI.java b/src/main/java/me/libraryaddict/disguise/utilities/mineskin/MineSkinAPI.java new file mode 100644 index 00000000..435dd04e --- /dev/null +++ b/src/main/java/me/libraryaddict/disguise/utilities/mineskin/MineSkinAPI.java @@ -0,0 +1,226 @@ +package me.libraryaddict.disguise.utilities.mineskin; + +import com.google.gson.Gson; +import me.libraryaddict.disguise.utilities.DisguiseUtilities; +import me.libraryaddict.disguise.utilities.SkinUtils; +import me.libraryaddict.disguise.utilities.parser.DisguiseParseException; +import me.libraryaddict.disguise.utilities.translations.LibsMsg; +import org.bukkit.craftbukkit.libs.org.apache.commons.io.IOUtils; + +import java.io.*; +import java.net.HttpURLConnection; +import java.net.SocketTimeoutException; +import java.net.URL; +import java.nio.file.Files; +import java.util.UUID; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.ReentrantLock; + +/** + * Created by libraryaddict on 28/12/2019. + *

+ * This isn't a stanealone class + */ +public class MineSkinAPI { + private class APIError { + int code; + String error; + } + + /** + * Time in millis until next request can be made + */ + private long nextRequest; + private final ReentrantLock lock = new ReentrantLock(); + + public boolean isInUse() { + return lock.isLocked(); + } + + public int nextRequestIn() { + long timeTillNext = nextRequest - System.currentTimeMillis(); + + if (timeTillNext < 0) { + return 0; + } + + return (int) Math.ceil(timeTillNext / 1000D); + } + + /** + * Fetches image from the provided url + * + * @param url + */ + public MineSkinResponse generateFromUrl(SkinUtils.SkinCallback callback, String url) { + return doPost(callback, "/generate/url", url, null); + } + + private MineSkinResponse doPost(SkinUtils.SkinCallback callback, String path, String skinUrl, File file) { + lock.lock(); + + HttpURLConnection connection = null; + + try { + URL url = new URL("https://api.mineskin.org" + path); + // Creating a connection + connection = (HttpURLConnection) url.openConnection(); + connection.setRequestProperty("User-Agent", "LibsDisguises"); + connection.setConnectTimeout(19000); + connection.setReadTimeout(19000); + + String boundary = Long.toHexString(System.currentTimeMillis()); // Just generate some unique random value. + connection.setDoOutput(true); + connection.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary); + + String charset = "UTF-8"; + String CRLF = "\r\n"; // Line separator required by multipart/form-data. + + try (OutputStream output = connection.getOutputStream(); PrintWriter writer = new PrintWriter( + new OutputStreamWriter(output, charset), true)) { + // Send normal param. + writer.append("--").append(boundary).append(CRLF); + writer.append("Content-Disposition: form-data; name=\"visibility\"").append(CRLF); + writer.append("Content-Type: text/plain; charset=").append(charset).append(CRLF); + writer.append(CRLF).append("1").append(CRLF).flush(); + + if (file != null) { + // Send binary file. + writer.append("--").append(boundary).append(CRLF); + writer.append("Content-Disposition: form-data; name=\"file\"; filename=\"").append(file.getName()) + .append("\"").append(CRLF); + writer.append("Content-Type: image/png").append(CRLF); + writer.append("Content-Transfer-Encoding: binary").append(CRLF); + writer.append(CRLF).flush(); + Files.copy(file.toPath(), output); + output.flush(); // Important before continuing with writer! + writer.append(CRLF).flush(); // CRLF is important! It indicates end of boundary. + } else if (skinUrl != null) { + // Send normal param. + writer.append("--").append(boundary).append(CRLF); + writer.append("Content-Disposition: form-data; name=\"url\"").append(CRLF); + writer.append(CRLF).append(skinUrl).append(CRLF).flush(); + } + + // End of multipart/form-data. + writer.append("--").append(boundary).append("--").append(CRLF).flush(); + } + + if (connection.getResponseCode() == 500) { + APIError error = new Gson().fromJson(IOUtils.toString(connection.getErrorStream()), APIError.class); + + if (error.code == 403) { + callback.onError(LibsMsg.SKIN_API_FAIL_CODE, "" + error.code, LibsMsg.SKIN_API_403.get()); + return null; + } else if (error.code == 404) { + callback.onError(LibsMsg.SKIN_API_FAIL_CODE, "" + error.code, LibsMsg.SKIN_API_404.get()); + return null; + } else if (error.code == 408 || error.code == 504 || error.code == 599) { + callback.onError(LibsMsg.SKIN_API_FAIL_CODE, "" + error.code, LibsMsg.SKIN_API_TIMEOUT.get()); + return null; + } else { + callback.onError(LibsMsg.SKIN_API_FAIL_CODE, "" + error.code, + "Your image has the error: " + error.error); + return null; + } + } else if (connection.getResponseCode() == 400) { + if (skinUrl != null) { + callback.onError(LibsMsg.SKIN_API_BAD_URL); + return null; + } else if (file != null) { + callback.onError(LibsMsg.SKIN_API_BAD_FILE); + return null; + } + } + + // Get the input stream, what we receive + try (InputStream input = connection.getInputStream()) { + // Read it to string + String response = IOUtils.toString(input); + + MineSkinResponse skinResponse = new Gson().fromJson(response, MineSkinResponse.class); + + nextRequest = System.currentTimeMillis() + (long) (skinResponse.getNextRequest() * 1000); + + return skinResponse; + } + } + catch (SocketTimeoutException ex) { + callback.onError(skinUrl == null ? LibsMsg.SKIN_API_TIMEOUT : LibsMsg.SKIN_API_IMAGE_TIMEOUT); + return null; + } + catch (Exception ex) { + nextRequest = System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(10); + + try { + if (connection != null && (connection.getResponseCode() == 524 || connection.getResponseCode() == 408 || + connection.getResponseCode() == 504 || connection.getResponseCode() == 599)) { + callback.onError(LibsMsg.SKIN_API_TIMEOUT); + return null; + } + } + catch (IOException e) { + } + + DisguiseUtilities.getLogger().warning("Failed to access MineSkin.org"); + ex.printStackTrace(); + + callback.onError(LibsMsg.SKIN_API_FAIL); + } + finally { + lock.unlock(); + } + + return null; + } + + public MineSkinResponse generateFromUUID(UUID uuid) throws IllegalArgumentException { + lock.lock(); + + try { + URL url = new URL("https://api.mineskin.org/generate/user/:" + uuid.toString()); + // Creating a connection + HttpURLConnection con = (HttpURLConnection) url.openConnection(); + con.setRequestProperty("User-Agent", "LibsDisguises"); + // We're writing a body that contains the API access key (Not required and obsolete, but!) + con.setDoOutput(true); + + // Get the input stream, what we receive + try (InputStream input = con.getInputStream()) { + // Read it to string + String response = IOUtils.toString(input); + + MineSkinResponse skinResponse = new Gson().fromJson(response, MineSkinResponse.class); + + nextRequest = System.currentTimeMillis() + (long) (skinResponse.getNextRequest() * 1000); + + return skinResponse; + } + } + catch (Exception ex) { + nextRequest = System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(10); + + if (ex.getMessage() != null && + ex.getMessage().contains("Server returned HTTP response code: 400 for URL")) { + throw new IllegalArgumentException(); + } + + DisguiseUtilities.getLogger().warning("Failed to access MineSkin.org"); + ex.printStackTrace(); + } + finally { + lock.unlock(); + } + + return null; + } + + /** + * Uploads png file + * + * @param file + */ + public MineSkinResponse generateFromFile(SkinUtils.SkinCallback callback, File file) { + return doPost(callback, "/generate/upload", null, file); + } +} diff --git a/src/main/java/me/libraryaddict/disguise/utilities/mineskin/MineSkinResponse.java b/src/main/java/me/libraryaddict/disguise/utilities/mineskin/MineSkinResponse.java new file mode 100644 index 00000000..ce0d95ac --- /dev/null +++ b/src/main/java/me/libraryaddict/disguise/utilities/mineskin/MineSkinResponse.java @@ -0,0 +1,120 @@ +package me.libraryaddict.disguise.utilities.mineskin; + +import com.google.gson.annotations.SerializedName; +import com.mojang.authlib.GameProfile; +import com.mojang.authlib.properties.Property; +import org.apache.commons.lang.StringUtils; + +import java.lang.reflect.Field; +import java.util.Map; +import java.util.UUID; + +/** + * Created by libraryaddict on 29/12/2019. + */ +public class MineSkinResponse { + public class SkinData { + public class SkinTexture { + private String value; + private String signature; + private String url; + private Map urls; + + public String getValue() { + return value; + } + + public String getSignature() { + return signature; + } + + public String getUrl() { + return url; + } + + public Map getUrls() { + return urls; + } + } + + private String name; + private UUID uuid; + private SkinTexture texture; + + public String getName() { + return name; + } + + public SkinTexture getTexture() { + return texture; + } + + public UUID getUUID() { + return uuid; + } + } + + private int id; + private String name; + private SkinData data; + private double timestamp; + private int duration; + private int accountId; + @SerializedName("private") + private boolean privateSkin; + private int views; + private double nextRequest; + + public int getId() { + return id; + } + + public String getName() { + return name; + } + + public SkinData getData() { + return data; + } + + public GameProfile getGameProfile() { + if (getData() == null) { + return null; + } + + GameProfile profile = new GameProfile(getData().getUUID(), + StringUtils.stripToNull(getData().getName()) == null ? "Unknown" : getData().getName()); + + if (getData().getTexture() != null) { + Property property = new Property("textures", getData().getTexture().getValue(), + getData().getTexture().getSignature()); + profile.getProperties().put("textures", property); + } + + return profile; + } + + public double getTimestamp() { + return timestamp; + } + + public int getDuration() { + return duration; + } + + public int getAccountId() { + return accountId; + } + + public boolean isPrivate() { + return privateSkin; + } + + public int getViews() { + return views; + } + + public double getNextRequest() { + return nextRequest; + } +} diff --git a/src/main/java/me/libraryaddict/disguise/utilities/parser/DisguiseParser.java b/src/main/java/me/libraryaddict/disguise/utilities/parser/DisguiseParser.java index e2e3a4c8..797ba933 100644 --- a/src/main/java/me/libraryaddict/disguise/utilities/parser/DisguiseParser.java +++ b/src/main/java/me/libraryaddict/disguise/utilities/parser/DisguiseParser.java @@ -114,6 +114,13 @@ public class DisguiseParser { } public static String parseToString(Disguise disguise) { + return parseToString(disguise, true); + } + + /** + * Not outputting skin information is not garanteed to display the correct player name + */ + public static String parseToString(Disguise disguise, boolean outputSkinData) { try { StringBuilder stringBuilder = new StringBuilder(); @@ -153,10 +160,25 @@ public class DisguiseParser { ourValue = null; } - // If its the same as default, continue - if (!m.isAnnotationPresent(RandomDefaultValue.class) && - Objects.deepEquals(entry.getValue(), ourValue)) { - continue; + if (m.getName().equals("setSkin") && !outputSkinData) { + PlayerDisguise pDisg = (PlayerDisguise) disguise; + ourValue = pDisg.getName(); + + if (pDisg.getSkin() != null) { + ourValue = pDisg.getSkin(); + } else if (pDisg.getGameProfile() != null && pDisg.getGameProfile().getName() != null) { + ourValue = pDisg.getGameProfile().getName(); + } + + if (ourValue.equals(pDisg.getName())) { + continue; + } + } else { + // If its the same as default, continue + if (!m.isAnnotationPresent(RandomDefaultValue.class) && + Objects.deepEquals(entry.getValue(), ourValue)) { + continue; + } } stringBuilder.append(" ").append(m.getName()); @@ -170,9 +192,7 @@ public class DisguiseParser { if (ourValue != null) { valueString = ParamInfoManager.getParamInfo(ourValue.getClass()).toString(ourValue); - if (valueString.contains(" ") || valueString.contains("\"")) { - valueString = "\"" + valueString.replace("\\", "\\\\").replace("\"", "\\\"") + "\""; - } + valueString = DisguiseUtilities.quote(valueString); } else { valueString = "null"; } diff --git a/src/main/java/me/libraryaddict/disguise/utilities/parser/params/types/base/ParamInfoString.java b/src/main/java/me/libraryaddict/disguise/utilities/parser/params/types/base/ParamInfoString.java index c7a17efa..a2cac44b 100644 --- a/src/main/java/me/libraryaddict/disguise/utilities/parser/params/types/base/ParamInfoString.java +++ b/src/main/java/me/libraryaddict/disguise/utilities/parser/params/types/base/ParamInfoString.java @@ -18,6 +18,6 @@ public class ParamInfoString extends ParamInfo { @Override public String toString(Object object) { - return object.toString(); + return ((String) object).replace(ChatColor.COLOR_CHAR + "", "&"); } } diff --git a/src/main/java/me/libraryaddict/disguise/utilities/parser/params/types/custom/ParamInfoItemStack.java b/src/main/java/me/libraryaddict/disguise/utilities/parser/params/types/custom/ParamInfoItemStack.java index fe0527b2..1131f656 100644 --- a/src/main/java/me/libraryaddict/disguise/utilities/parser/params/types/custom/ParamInfoItemStack.java +++ b/src/main/java/me/libraryaddict/disguise/utilities/parser/params/types/custom/ParamInfoItemStack.java @@ -1,6 +1,7 @@ package me.libraryaddict.disguise.utilities.parser.params.types.custom; import me.libraryaddict.disguise.utilities.DisguiseUtilities; +import me.libraryaddict.disguise.utilities.translations.LibsMsg; import me.libraryaddict.disguise.utilities.translations.TranslateType; import me.libraryaddict.disguise.utilities.parser.params.types.ParamInfoEnum; import org.bukkit.Material; @@ -36,6 +37,27 @@ public class ParamInfoItemStack extends ParamInfoEnum { @Override public String toString(Object object) { + ItemStack itemStack = (ItemStack) object; + ItemStack temp = new ItemStack(itemStack.getType(), itemStack.getAmount()); + + if (itemStack.containsEnchantment(Enchantment.DURABILITY)) { + temp.addUnsafeEnchantment(Enchantment.DURABILITY, 1); + } + + if (temp.isSimilar(itemStack)) { + String name = itemStack.getType().name(); + + if (itemStack.getAmount() != 1) { + name += ":" + itemStack.getAmount(); + } + + if (itemStack.containsEnchantment(Enchantment.DURABILITY)) { + name += ":" + TranslateType.DISGUISE_OPTIONS_PARAMETERS.get("glow"); + } + + return name; + } + return DisguiseUtilities.getGson().toJson(object); } diff --git a/src/main/java/me/libraryaddict/disguise/utilities/parser/params/types/custom/ParamInfoItemStackArray.java b/src/main/java/me/libraryaddict/disguise/utilities/parser/params/types/custom/ParamInfoItemStackArray.java index 88c9ddad..da5d5b73 100644 --- a/src/main/java/me/libraryaddict/disguise/utilities/parser/params/types/custom/ParamInfoItemStackArray.java +++ b/src/main/java/me/libraryaddict/disguise/utilities/parser/params/types/custom/ParamInfoItemStackArray.java @@ -39,7 +39,30 @@ public class ParamInfoItemStackArray extends ParamInfoItemStack { @Override public String toString(Object object) { - return DisguiseUtilities.getGson().toJson(object); + ItemStack[] stacks = (ItemStack[]) object; + + String returns = ""; + + for (int i = 0; i < stacks.length; i++) { + if (i > 0) { + returns += ","; + } + + if (stacks[i] == null) { + continue; + } + + String toString = super.toString(stacks[i]); + + // If we can't parse to simple + if (toString.startsWith("{")) { + return DisguiseUtilities.getGson().toJson(object); + } + + returns += toString; + } + + return returns; } @Override diff --git a/src/main/java/me/libraryaddict/disguise/utilities/translations/LibsMsg.java b/src/main/java/me/libraryaddict/disguise/utilities/translations/LibsMsg.java index f618da99..e6a88d01 100644 --- a/src/main/java/me/libraryaddict/disguise/utilities/translations/LibsMsg.java +++ b/src/main/java/me/libraryaddict/disguise/utilities/translations/LibsMsg.java @@ -12,6 +12,8 @@ public enum LibsMsg { EXPIRED_DISGUISE(ChatColor.RED + "Your disguise has expired!"), CAN_USE_DISGS(ChatColor.DARK_GREEN + "You can use the disguises: %s"), CANNOT_FIND_PLAYER(ChatColor.RED + "Cannot find the player/uuid '%s'"), + CANNOT_FIND_PLAYER_NAME(ChatColor.RED + "Cannot find the player '%s'"), + CANNOT_FIND_PLAYER_UUID(ChatColor.RED + "Cannot find the uuid '%s'"), CLICK_TIMER(ChatColor.RED + "Right click a entity in the next %s seconds to grab the disguise reference!"), CLONE_HELP1(ChatColor.DARK_GREEN + "Right click a entity to get a disguise reference you can pass to other disguise commands!"), @@ -20,6 +22,7 @@ public enum LibsMsg { "references."), CLONE_HELP3(ChatColor.DARK_GREEN + "/disguiseclone IgnoreEquipment" + ChatColor.DARK_GREEN + "(" + ChatColor.GREEN + "Optional" + ChatColor.DARK_GREEN + ")"), + CUSTOM_DISGUISE_SAVED(ChatColor.GOLD + "Custom disguise has been saved as '%s'!"), D_HELP1(ChatColor.DARK_GREEN + "Disguise another player!"), D_HELP3(ChatColor.DARK_GREEN + "/disguiseplayer player "), D_HELP4(ChatColor.DARK_GREEN + "/disguiseplayer "), @@ -106,6 +109,8 @@ public enum LibsMsg { DRADIUS_NEEDOPTIONS(ChatColor.RED + "You need to supply a disguise as well as the radius"), DRADIUS_NEEDOPTIONS_ENTITY(ChatColor.RED + "You need to supply a disguise as well as the radius and EntityType"), FAILED_DISGIUSE(ChatColor.RED + "Failed to disguise as a %s"), + GRABBED_SKIN(ChatColor.GOLD + "Grabbed skin and saved as %s!"), + PLEASE_WAIT(ChatColor.GRAY + "Please wait..."), INVALID_CLONE(ChatColor.DARK_RED + "Unknown option '%s' - Valid options are 'IgnoreEquipment' 'DoSneakSprint' " + "'DoSneak' 'DoSprint'"), LIBS_RELOAD_WRONG(ChatColor.RED + "[LibsDisguises] Did you mean 'reload'?"), @@ -132,6 +137,7 @@ public enum LibsMsg { "Ignored %s options you do not have permission to use. Add 'show' to view unusable options."), OWNED_BY(ChatColor.GOLD + "Plugin registered to '%%__USER__%%'!"), NOT_DISGUISED(ChatColor.RED + "You are not disguised!"), + TARGET_NOT_DISGUISED(ChatColor.RED + "That entity is not disguised!"), NOT_NUMBER(ChatColor.RED + "Error! %s is not a number"), PARSE_CANT_DISG_UNKNOWN(ChatColor.RED + "Error! You cannot disguise as " + ChatColor.GREEN + "Unknown!"), PARSE_CANT_LOAD(ChatColor.RED + "Error! This disguise couldn't be loaded!"), @@ -178,7 +184,50 @@ public enum LibsMsg { ", the latest build is " + ChatColor.RED + "#%s" + ChatColor.DARK_RED + "!" + ChatColor.RED + "\nhttps://ci.md-5.net/job/LibsDisguises/lastSuccessfulBuild/"), VIEW_SELF_ON(ChatColor.GREEN + "Toggled viewing own disguise on!"), - VIEW_SELF_OFF(ChatColor.GREEN + "Toggled viewing own disguise off!"); + VIEW_SELF_OFF(ChatColor.GREEN + "Toggled viewing own disguise off!"), + CLICK_TO_COPY(ChatColor.GREEN + "Click to Copy:"), + CLICK_TO_COPY_DATA(ChatColor.GOLD + "Data"), + CLICK_TO_COPY_WITH_SKIN(ChatColor.GREEN + "Version with skin data:"), + CLICK_TO_COPY_HOVER(ChatColor.GOLD + "Click to Copy"), + CLICK_COPY(ChatColor.YELLOW + "" + ChatColor.BOLD + "%s"), + SKIN_API_IN_USE(ChatColor.RED + "mineskin.org is currently in use, please try again"), + SKIN_API_TIMER(ChatColor.RED + "mineskin.org can be used again in %s seconds"), + SKIN_API_FAIL(ChatColor.RED + "Unexpected error while accessing mineskin.org, please try again"), + SKIN_API_BAD_URL(ChatColor.RED + "Invalid url provided! Please ensure it is a .png file download!"), + SKIN_API_FAILED_URL(ChatColor.RED + "Invalid url provided! mineskin.org failed to grab it!"), + SKIN_API_FAIL_CODE(ChatColor.RED + "Error %s! %s"), + SKIN_API_403("mineskin.org denied access to that url"), + SKIN_API_404("mineskin.org unable to find an image at that url"), + SKIN_API_IMAGE_TIMEOUT(ChatColor.RED + "Error! mineskin.org took too long to connect! Is your image valid?"), + SKIN_API_TIMEOUT(ChatColor.RED + "Error! Took too long to connect to mineskin.org!"), + SKIN_API_USING_URL(ChatColor.GRAY + "Url provided, now attempting to connect to mineskin.org"), + SKIN_API_BAD_FILE_NAME(ChatColor.RED + "Invalid file name provided! File not found!"), + SKIN_API_BAD_FILE(ChatColor.RED + "Invalid file provided! Please ensure it is a valid .png skin!"), + SKIN_API_USING_FILE(ChatColor.GRAY + "File provided and found, now attempting to upload to mineskin.org"), + SKIN_API_INVALID_NAME(ChatColor.RED + "Invalid name/file/uuid provided!"), + SKIN_API_USING_UUID(ChatColor.GRAY + "UUID successfully parsed, now attempting to connect to mineskin.org"), + SKIN_API_USING_NAME( + ChatColor.GRAY + "Determined to be player name, now attempting to validate and connect to mineskin.org"), + SAVE_DISG_HELP_1(ChatColor.GREEN + "The is what the disguise will be called in Lib's Disguises"), + SAVE_DISG_HELP_2(ChatColor.GREEN + + "/savedisguise - If you don't provide arguments, it'll try make a disguise from your" + + " current disguise. This will not work if you are not disguised!"), + SAVE_DISG_HELP_3(ChatColor.GREEN + "/savedisguise "), + SAVE_DISG_HELP_4(ChatColor.GREEN + + "Your arguments need to be as if you're using /disguise. So '/disguise player Notch setsneaking' - " + + "Means '/savedisguise Notch player Notch setsneaking'"), + SAVE_DISG_HELP_5(ChatColor.GREEN + "Remember! You can upload your own skins, then reference those skins!"), + GRAB_DISG_HELP_1(ChatColor.GREEN + + "You can choose a name to save the skins under, the names will be usable as if it was an actual player skin"), + GRAB_DISG_HELP_2(ChatColor.DARK_GREEN + "/grabskin https://somesite.com/myskin.png "), + GRAB_DISG_HELP_3(ChatColor.DARK_GREEN + "/grabskin myskin.png - Skins must be in the folder!"), + GRAB_DISG_HELP_4(ChatColor.DARK_GREEN + "/grabskin "), + GRAB_DISG_HELP_5( + ChatColor.GREEN + "You will be sent the skin data, but you can also use the saved names in disguises"), + CUSTOM_DISGUISE_NAME_CONFLICT( + ChatColor.RED + "Cannot create the custom disguise '%s' as there is a name conflict!"), + ERROR_LOADING_CUSTOM_DISGUISE(ChatColor.RED + "Error while loading custom disguise '%s'%s"), + SKIN_API_INTERNAL_ERROR(ChatColor.RED + "Internal error in the skin API, perhaps bad data?"); private String string; diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 25f18586..8cc54dd3 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -72,6 +72,18 @@ commands: aliases: [dmodifyentity, dmodentity] permission: libsdisguises.seecmd.disguisemodifyentity description: Modify a disguise by right clicking them + copydisguise: + aliases: [disguisecopy, disgcopy, dcopy, copydisg, copyd] + permission: libsdisguises.seecmd.copydisguise + description: Output a disguise to a usable string + grabskin: + aliases: [uploadskin, skin] + permission: libsdisguises.seecmd.grabskin + description: Grab a skin from file, url or player name/uuid + savedisguise: + aliases: [customdisguise, savedisg, customdisg, createdisguise, createdisg] + permission: libsdisguises.seecmd.savedisguise + description: Save a custom disguise to disguises.yml permissions: libsdisguises.reload: @@ -100,6 +112,7 @@ permissions: libsdisguises.seecmd.disguisemodifyplayer: true libsdisguises.seecmd.disguisemodifyradius: true libsdisguises.seecmd.disguisemodifyentity: true + libsdisguises.seecmd.copydisguise: true libsdisguises.seecmd.libsdisguises: description: See the /libsdisguises command in tab-completion libsdisguises.seecmd.disguiseviewself: @@ -131,4 +144,10 @@ permissions: libsdisguises.seecmd.disguisemodifyradius: description: See the /disguisemodifyradius command in tab-completion libsdisguises.seecmd.disguisemodifyentity: - description: See the /disguisemodifyentity command in tab-completion \ No newline at end of file + description: See the /disguisemodifyentity command in tab-completion + libsdisguises.seecmd.copydisguise: + description: See the /copydisguise command in tab-completion + libsdisguises.seecmd.grabskin: + description: See the /grabskin command in tab-completion + libsdisguises.seecmd.savedisguise: + description: See the /savedisguise command in tab-completion \ No newline at end of file