From da8890b30aaea07a99982a246753efe4e5932c4c Mon Sep 17 00:00:00 2001 From: Izzel_Aliz Date: Sun, 29 Apr 2018 15:44:01 +0800 Subject: [PATCH] AsmClassTransformer --- .idea/dictionaries/csh20.xml | 1 + .../ilummc/tlib/inject/TConfigInjector.java | 15 ++ .../tlib/inject/TDependencyInjector.java | 6 +- .../java/com/ilummc/tlib/nms/ActionBar.java | 50 ++++++ .../ilummc/tlib/resources/TLocaleLoader.java | 3 +- .../ilummc/tlib/util/asm/AsmClassLoader.java | 13 ++ .../tlib/util/asm/AsmClassTransformer.java | 151 +++++++++++++++++- .../skymc/taboolib/display/ActionUtils.java | 42 +---- .../me/skymc/taboolib/display/TitleUtils.java | 39 +++-- .../skymc/taboolib/fileutils/ConfigUtils.java | 14 +- .../skymc/taboolib/fileutils/FileUtils.java | 21 ++- .../me/skymc/taboolib/update/UpdateTask.java | 22 ++- 12 files changed, 285 insertions(+), 92 deletions(-) create mode 100644 src/main/java/com/ilummc/tlib/nms/ActionBar.java create mode 100644 src/main/java/com/ilummc/tlib/util/asm/AsmClassLoader.java diff --git a/.idea/dictionaries/csh20.xml b/.idea/dictionaries/csh20.xml index 31d1c02..0dc0321 100644 --- a/.idea/dictionaries/csh20.xml +++ b/.idea/dictionaries/csh20.xml @@ -2,6 +2,7 @@ autoload + craftbukkit mvdw papi sendable diff --git a/src/main/java/com/ilummc/tlib/inject/TConfigInjector.java b/src/main/java/com/ilummc/tlib/inject/TConfigInjector.java index 00cb178..532e916 100644 --- a/src/main/java/com/ilummc/tlib/inject/TConfigInjector.java +++ b/src/main/java/com/ilummc/tlib/inject/TConfigInjector.java @@ -66,6 +66,21 @@ public class TConfigInjector { return null; } + public static void reloadConfig(Plugin plugin, Object object) { + try { + Config config = object.getClass().getAnnotation(Config.class); + Validate.notNull(config); + File file = new File(plugin.getDataFolder(), config.name()); + Map map = ConfigUtils.confToMap(ConfigUtils.loadYaml(plugin, file)); + Object obj = ConfigUtils.mapToObj(map, object); + if (!config.readOnly()) saveConfig(plugin, obj); + } catch (NullPointerException e) { + TLocale.Logger.warn("CONFIG.LOAD-FAIL-NO-ANNOTATION", plugin.toString(), object.getClass().getSimpleName()); + } catch (Exception e) { + TLocale.Logger.warn("CONFIG.LOAD-FAIL", plugin.toString(), object.getClass().getSimpleName()); + } + } + public static Object unserialize(Plugin plugin, Class clazz) { try { Config config = clazz.getAnnotation(Config.class); diff --git a/src/main/java/com/ilummc/tlib/inject/TDependencyInjector.java b/src/main/java/com/ilummc/tlib/inject/TDependencyInjector.java index 09a8a0a..4cf3744 100644 --- a/src/main/java/com/ilummc/tlib/inject/TDependencyInjector.java +++ b/src/main/java/com/ilummc/tlib/inject/TDependencyInjector.java @@ -72,11 +72,7 @@ public class TDependencyInjector { obj, object -> { try { - Object newObj = TConfigInjector.loadConfig(plugin, object.getClass()); - for (Field f : newObj.getClass().getDeclaredFields()) { - f.setAccessible(true); - f.set(obj, f.get(newObj)); - } + TConfigInjector.reloadConfig(plugin, object); TLocale.Logger.info("CONFIG.RELOAD-SUCCESS", plugin.toString(), config.name()); } catch (Exception ignored) { TLocale.Logger.warn("CONFIG.RELOAD-FAIL", plugin.toString(), config.name()); diff --git a/src/main/java/com/ilummc/tlib/nms/ActionBar.java b/src/main/java/com/ilummc/tlib/nms/ActionBar.java new file mode 100644 index 0000000..5dc685f --- /dev/null +++ b/src/main/java/com/ilummc/tlib/nms/ActionBar.java @@ -0,0 +1,50 @@ +package com.ilummc.tlib.nms; + +import com.ilummc.tlib.util.asm.AsmClassTransformer; +import me.skymc.taboolib.TabooLib; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; + +public abstract class ActionBar { + + private static ActionBar instance; + + static { + if (TabooLib.getVerint() > 11100) { + instance = (ActionBar) AsmClassTransformer.builder().from(Impl_1_12.class).fromVersion("v1_12_R1") + .toVersion(Bukkit.getServer().getClass().getName().split("\\.")[3]).build().transform(); + } else { + instance = (ActionBar) AsmClassTransformer.builder().from(Impl_1_8.class).fromVersion("v1_8_R3") + .toVersion(Bukkit.getServer().getClass().getName().split("\\.")[3]).build().transform(); + } + System.out.println(instance.getClass()); + } + + public static void sendActionBar(Player player, String text) { + instance.send(player, text); + } + + public abstract void send(Player player, String text); + + public static class Impl_1_8 extends ActionBar { + + @Override + public void send(Player player, String text) { + net.minecraft.server.v1_8_R3.ChatComponentText component = new net.minecraft.server.v1_8_R3.ChatComponentText(text); + net.minecraft.server.v1_8_R3.PacketPlayOutChat packet = new net.minecraft.server.v1_8_R3.PacketPlayOutChat(component, (byte) 2); + ((org.bukkit.craftbukkit.v1_8_R3.entity.CraftPlayer) player).getHandle().playerConnection.sendPacket(packet); + } + } + + public static class Impl_1_12 extends ActionBar { + + @Override + public void send(Player player, String text) { + net.minecraft.server.v1_12_R1.ChatComponentText component = new net.minecraft.server.v1_12_R1.ChatComponentText(text); + net.minecraft.server.v1_12_R1.PacketPlayOutChat packet = new net.minecraft.server.v1_12_R1.PacketPlayOutChat(component, + net.minecraft.server.v1_12_R1.ChatMessageType.a((byte) 2)); + ((org.bukkit.craftbukkit.v1_12_R1.entity.CraftPlayer) player).getHandle().playerConnection.sendPacket(packet); + + } + } +} diff --git a/src/main/java/com/ilummc/tlib/resources/TLocaleLoader.java b/src/main/java/com/ilummc/tlib/resources/TLocaleLoader.java index c979109..5947c3b 100644 --- a/src/main/java/com/ilummc/tlib/resources/TLocaleLoader.java +++ b/src/main/java/com/ilummc/tlib/resources/TLocaleLoader.java @@ -72,6 +72,8 @@ public class TLocaleLoader { TLocaleInstance localeInstance = new TLocaleInstance(plugin); localeInstance.load(configuration); map.put(plugin.getName(), localeInstance); + TLib.getTLib().getLogger().info(Strings.replaceWithOrder(TLib.getTLib().getInternalLang().getString("SUCCESS-LOADING-LANG"), + plugin.getName(), lang, String.valueOf(localeInstance.size()))); } File finalFile = file; String finalLang = lang; @@ -84,7 +86,6 @@ public class TLocaleLoader { TLib.getTLib().getLogger().info(Strings.replaceWithOrder(TLib.getTLib().getInternalLang().getString("SUCCESS-LOADING-LANG"), plugin.getName(), finalLang, String.valueOf(localeInstance.size()))); }); - TLib.getTLib().getLogger().info(Strings.replaceWithOrder(TLib.getTLib().getInternalLang().getString("SUCCESS-LOADING-LANG"), plugin.getName(), lang)); } } catch (Exception e) { TLib.getTLib().getLogger().error(Strings.replaceWithOrder(TLib.getTLib().getInternalLang().getString("ERROR-LOADING-LANG"), diff --git a/src/main/java/com/ilummc/tlib/util/asm/AsmClassLoader.java b/src/main/java/com/ilummc/tlib/util/asm/AsmClassLoader.java new file mode 100644 index 0000000..0b58031 --- /dev/null +++ b/src/main/java/com/ilummc/tlib/util/asm/AsmClassLoader.java @@ -0,0 +1,13 @@ +package com.ilummc.tlib.util.asm; + +public class AsmClassLoader extends ClassLoader { + + public AsmClassLoader() { + super(AsmClassLoader.class.getClassLoader()); + } + + public Class createNewClass(String name, byte[] arr) { + return defineClass(name, arr, 0, arr.length, AsmClassLoader.class.getProtectionDomain()); + } + +} diff --git a/src/main/java/com/ilummc/tlib/util/asm/AsmClassTransformer.java b/src/main/java/com/ilummc/tlib/util/asm/AsmClassTransformer.java index 5bd04d8..b4da8a9 100644 --- a/src/main/java/com/ilummc/tlib/util/asm/AsmClassTransformer.java +++ b/src/main/java/com/ilummc/tlib/util/asm/AsmClassTransformer.java @@ -1,4 +1,153 @@ package com.ilummc.tlib.util.asm; -public class AsmClassTransformer { + +import org.bukkit.Bukkit; +import org.objectweb.asm.*; + +import java.io.IOException; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.util.Map; + +public class AsmClassTransformer extends ClassVisitor implements Opcodes { + + private final Class from; + + private final String fromVer, toVer; + + private final ClassWriter writer; + + private String newClassName, prevName; + + private AsmClassTransformer(Class from, String fromVer, String toVer, ClassWriter classWriter) { + super(Opcodes.ASM6, classWriter); + writer = classWriter; + this.from = from; + this.fromVer = fromVer; + this.toVer = toVer; + } + + public static Builder builder() { + return new Builder().toVersion(Bukkit.getServer().getClass().getName().split("\\.")[3]); + } + + public Object transform() { + try { + ClassReader classReader = new ClassReader(from.getResourceAsStream("/" + from.getName().replace('.', '/') + ".class")); + newClassName = from.getName() + "_TabooLibRemap_" + this.hashCode() + "_" + toVer; + prevName = from.getName().replace('.', '/'); + classReader.accept(this, ClassReader.SKIP_DEBUG); + Class clazz = new AsmClassLoader().createNewClass(newClassName, writer.toByteArray()); + Field field = from.getClassLoader().getClass().getDeclaredField("classes"); + field.setAccessible(true); + ((Map>) field.get(from.getClassLoader())).put(newClassName, clazz); + Constructor constructor = clazz.getDeclaredConstructor(); + constructor.setAccessible(true); + return constructor.newInstance(); + } catch (IOException | NoSuchFieldException | IllegalAccessException | InstantiationException | NoSuchMethodException | InvocationTargetException e) { + e.printStackTrace(); + return null; + } + } + + @Override + public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { + super.visit(version, access, newClassName.replace('.', '/'), replace(signature), + prevName, replace(interfaces)); + } + + @Override + public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) { + MethodVisitor visitor = super.visitMethod(access, name, replace(descriptor), replace(signature), replace(exceptions)); + return new AsmMethodTransformer(visitor); + } + + @Override + public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) { + return super.visitField(access, name, replace(descriptor), replace(signature), value); + } + + @Override + public void visitInnerClass(String name, String outerName, String innerName, int access) { + super.visitInnerClass(replace(name), outerName, replace(name).substring(outerName.length() + 1), access); + } + + private String replace(String text) { + if (text != null) + return text.replace("net/minecraft/server/" + fromVer, "net/minecraft/server/" + toVer) + .replace("org/bukkit/craftbukkit/" + fromVer, "org/bukkit/craftbukkit/" + toVer) + .replace(prevName, newClassName.replace('.', '/')); + else return null; + } + + private String[] replace(String[] text) { + if (text != null) { + for (int i = 0; i < text.length; i++) { + text[i] = replace(text[i]); + } + return text; + } else return null; + } + + public static class Builder { + + private Class from; + + private String fromVersion, toVersion; + + public Builder from(Class clazz) { + this.from = clazz; + return this; + } + + public Builder fromVersion(String ver) { + fromVersion = ver; + return this; + } + + public Builder toVersion(String ver) { + toVersion = ver; + return this; + } + + public AsmClassTransformer build() { + return new AsmClassTransformer(from, fromVersion, toVersion, new ClassWriter(ClassWriter.COMPUTE_MAXS)); + } + + } + + private class AsmMethodTransformer extends MethodVisitor { + + AsmMethodTransformer(MethodVisitor visitor) { + super(Opcodes.ASM6, visitor); + } + + @Override + public void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface) { + super.visitMethodInsn(opcode, replace(owner), name, replace(descriptor), isInterface); + } + + @Override + public void visitLdcInsn(Object value) { + if (value instanceof String) + super.visitLdcInsn(replace((String) value)); + else super.visitLdcInsn(value); + } + + @Override + public void visitTypeInsn(int opcode, String type) { + super.visitTypeInsn(opcode, replace(type)); + } + + @Override + public void visitFieldInsn(int opcode, String owner, String name, String descriptor) { + super.visitFieldInsn(opcode, replace(owner), name, replace(descriptor)); + } + + @Override + public void visitLocalVariable(String name, String descriptor, String signature, Label start, Label end, int index) { + super.visitLocalVariable(name, replace(descriptor), replace(signature), start, end, index); + } + } } diff --git a/src/main/java/me/skymc/taboolib/display/ActionUtils.java b/src/main/java/me/skymc/taboolib/display/ActionUtils.java index 38524f8..aa99905 100644 --- a/src/main/java/me/skymc/taboolib/display/ActionUtils.java +++ b/src/main/java/me/skymc/taboolib/display/ActionUtils.java @@ -1,52 +1,20 @@ package me.skymc.taboolib.display; -import java.lang.reflect.Constructor; - +import com.ilummc.tlib.nms.ActionBar; import org.bukkit.entity.Player; -import me.skymc.taboolib.TabooLib; -import me.skymc.taboolib.nms.NMSUtils; - /** * @author Bkm016 * @since 2018-04-26 */ public class ActionUtils { - - private static Class Packet = NMSUtils.getNMSClass("Packet"); - private static Class ChatComponentText = NMSUtils.getNMSClass("ChatComponentText"); - private static Class ChatMessageType = NMSUtils.getNMSClass("ChatMessageType"); - private static Class PacketPlayOutChat = NMSUtils.getNMSClass("PacketPlayOutChat"); - private static Class IChatBaseComponent = NMSUtils.getNMSClass("IChatBaseComponent"); - + public static void send(Player player, String action) { - if (player == null) { + if (player == null) return; - } try { - Object ab = ChatComponentText.getConstructor(String.class).newInstance(action); - Constructor ac = null; - Object abPacket = null; - if (TabooLib.getVerint() > 11100) { - ac = PacketPlayOutChat.getConstructor(IChatBaseComponent, ChatMessageType); - abPacket = ac.newInstance(ab, ChatMessageType.getMethod("a", Byte.TYPE).invoke(null, (byte) 2)); - } else { - ac = PacketPlayOutChat.getConstructor(IChatBaseComponent, Byte.TYPE); - abPacket = ac.newInstance(ab, (byte) 2); - } - sendPacket(player, abPacket); - } - catch (Exception ignored) { - } - } - - private static void sendPacket(Player player, Object packet) { - try { - Object handle = player.getClass().getMethod("getHandle", new Class[0]).invoke(player); - Object playerConnection = handle.getClass().getField("playerConnection").get(handle); - playerConnection.getClass().getMethod("sendPacket", Packet).invoke(playerConnection, packet); - } - catch (Exception ignored) { + ActionBar.sendActionBar(player, action); + } catch (Throwable ignored) { } } } diff --git a/src/main/java/me/skymc/taboolib/display/TitleUtils.java b/src/main/java/me/skymc/taboolib/display/TitleUtils.java index 1589457..28cfb42 100644 --- a/src/main/java/me/skymc/taboolib/display/TitleUtils.java +++ b/src/main/java/me/skymc/taboolib/display/TitleUtils.java @@ -1,39 +1,37 @@ package me.skymc.taboolib.display; -import java.lang.reflect.Constructor; -import java.lang.reflect.Method; - +import me.skymc.taboolib.nms.NMSUtils; import org.bukkit.entity.Player; -import me.skymc.taboolib.nms.NMSUtils; +import java.lang.reflect.Constructor; /** * @author Bkm016 * @since 2018-04-26 */ public class TitleUtils { - - private static Class Packet = NMSUtils.getNMSClass("Packet"); - private static Class PacketPlayOutTitle = NMSUtils.getNMSClass("PacketPlayOutTitle"); - private static Class IChatBaseComponent = NMSUtils.getNMSClass("IChatBaseComponent"); - private static Class EnumTitleAction = PacketPlayOutTitle.getDeclaredClasses()[0]; - + + private static Class Packet = NMSUtils.getNMSClass("Packet"); + private static Class PacketPlayOutTitle = NMSUtils.getNMSClass("PacketPlayOutTitle"); + private static Class IChatBaseComponent = NMSUtils.getNMSClass("IChatBaseComponent"); + private static Class EnumTitleAction = PacketPlayOutTitle.getDeclaredClasses()[0]; + public static void sendTitle(Player p, String title, String subtitle, int fadein, int stay, int fadeout) { - sendTitle(p, title, fadein, stay, fadeout, subtitle, fadein, stay, fadeout); + sendTitle(p, title, fadein, stay, fadeout, subtitle, fadein, stay, fadeout); } - + public static void sendTitle(Player p, String title, int fadeint, int stayt, int fadeoutt, String subtitle, int fadeinst, int stayst, int fadeoutst) { - if (p == null) { - return; - } - try { + if (p == null) { + return; + } + try { if (title != null) { Object times = EnumTitleAction.getField("TIMES").get(null); Object chatTitle = IChatBaseComponent.getDeclaredClasses()[0].getMethod("a", String.class).invoke(null, "{\"text\":\"" + title + "\"}"); Constructor subtitleConstructor = PacketPlayOutTitle.getConstructor(EnumTitleAction, IChatBaseComponent, Integer.TYPE, Integer.TYPE, Integer.TYPE); Object titlePacket = subtitleConstructor.newInstance(times, chatTitle, fadeint, stayt, fadeoutt); sendPacket(p, titlePacket); - + times = EnumTitleAction.getField("TITLE").get(null); chatTitle = IChatBaseComponent.getDeclaredClasses()[0].getMethod("a", String.class).invoke(null, "{\"text\":\"" + title + "\"}"); subtitleConstructor = PacketPlayOutTitle.getConstructor(EnumTitleAction, IChatBaseComponent); @@ -46,18 +44,17 @@ public class TitleUtils { Constructor subtitleConstructor = PacketPlayOutTitle.getConstructor(EnumTitleAction, IChatBaseComponent, Integer.TYPE, Integer.TYPE, Integer.TYPE); Object subtitlePacket = subtitleConstructor.newInstance(times, chatSubtitle, fadeinst, stayst, fadeoutst); sendPacket(p, subtitlePacket); - + times = EnumTitleAction.getField("SUBTITLE").get(null); chatSubtitle = IChatBaseComponent.getDeclaredClasses()[0].getMethod("a", String.class).invoke(null, "{\"text\":\"" + subtitle + "\"}"); subtitleConstructor = PacketPlayOutTitle.getConstructor(EnumTitleAction, IChatBaseComponent, Integer.TYPE, Integer.TYPE, Integer.TYPE); subtitlePacket = subtitleConstructor.newInstance(times, chatSubtitle, fadeinst, stayst, fadeoutst); sendPacket(p, subtitlePacket); } - } - catch (Exception ignored) { + } catch (Exception ignored) { } } - + private static void sendPacket(Player player, Object packet) { try { Object handle = player.getClass().getDeclaredMethod("getHandle").invoke(player); diff --git a/src/main/java/me/skymc/taboolib/fileutils/ConfigUtils.java b/src/main/java/me/skymc/taboolib/fileutils/ConfigUtils.java index b05ce00..d6ab8da 100644 --- a/src/main/java/me/skymc/taboolib/fileutils/ConfigUtils.java +++ b/src/main/java/me/skymc/taboolib/fileutils/ConfigUtils.java @@ -3,6 +3,7 @@ package me.skymc.taboolib.fileutils; import com.google.common.collect.Maps; import com.google.common.io.Files; import com.ilummc.tlib.TLib; +import com.ilummc.tlib.bean.Property; import com.ilummc.tlib.util.Ref; import org.bukkit.configuration.ConfigurationSection; import org.bukkit.configuration.MemoryConfiguration; @@ -86,13 +87,20 @@ public class ConfigUtils { } } + @SuppressWarnings("unchecked") public static T mapToObj(Map map, T obj) { Class clazz = obj.getClass(); map.forEach((string, value) -> Ref.getFieldBySerializedName(clazz, string).ifPresent(field -> { if (!field.isAccessible()) field.setAccessible(true); try { - field.set(obj, value); + if (Property.class.isAssignableFrom(field.getType())) { + Property property = (Property) field.get(obj); + if (property != null) property.set(value); + else field.set(obj, Property.of(value)); + } else { + field.set(obj, value); + } } catch (IllegalAccessException ignored) { } })); @@ -108,7 +116,9 @@ public class ConfigUtils { for (Field field : Ref.getDeclaredFields(object.getClass(), excludedModifiers, false)) { try { if (!field.isAccessible()) field.setAccessible(true); - map.put(Ref.getSerializedName(field), field.get(object)); + Object obj = field.get(object); + if (obj instanceof Property) obj = ((Property) obj).get(); + map.put(Ref.getSerializedName(field), obj); } catch (IllegalAccessException ignored) { } } diff --git a/src/main/java/me/skymc/taboolib/fileutils/FileUtils.java b/src/main/java/me/skymc/taboolib/fileutils/FileUtils.java index 3f28a21..8c45a4f 100644 --- a/src/main/java/me/skymc/taboolib/fileutils/FileUtils.java +++ b/src/main/java/me/skymc/taboolib/fileutils/FileUtils.java @@ -1,22 +1,14 @@ package me.skymc.taboolib.fileutils; -import java.io.BufferedInputStream; -import java.io.BufferedReader; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; +import ch.njol.util.Closeable; +import me.skymc.taboolib.message.MsgUtils; + +import java.io.*; import java.net.HttpURLConnection; import java.net.URL; import java.net.URLConnection; import java.nio.channels.FileChannel; -import ch.njol.util.Closeable; -import me.skymc.taboolib.message.MsgUtils; - public class FileUtils { public static String ip() { @@ -247,6 +239,11 @@ public class FileUtils { } return null; } + + public static String getStringFromURL(String url, String def) { + String s = getStringFromURL(url, 1024); + return s == null ? def : s; + } /** * 下载文件 diff --git a/src/main/java/me/skymc/taboolib/update/UpdateTask.java b/src/main/java/me/skymc/taboolib/update/UpdateTask.java index 0c1c5f1..a37ccbd 100644 --- a/src/main/java/me/skymc/taboolib/update/UpdateTask.java +++ b/src/main/java/me/skymc/taboolib/update/UpdateTask.java @@ -1,20 +1,20 @@ package me.skymc.taboolib.update; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import org.bukkit.scheduler.BukkitRunnable; - +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; import me.skymc.taboolib.Main; import me.skymc.taboolib.TabooLib; import me.skymc.taboolib.fileutils.FileUtils; import me.skymc.taboolib.message.MsgUtils; +import org.bukkit.scheduler.BukkitRunnable; /** * @author sky * @since 2018年2月23日 下午10:39:14 */ public class UpdateTask { + + private static final String API = "https://api.github.com/repos/Bkm016/TabooLib/releases/latest"; /** * 检测更新 @@ -27,14 +27,10 @@ public class UpdateTask { if (!Main.getInst().getConfig().getBoolean("UPDATE-CHECK")) { return; } - String value = FileUtils.getStringFromURL("https://github.com/Bkm016/TabooLib/releases", 1024); - if (value == null) { - return; - } - Pattern pattern = Pattern.compile(""); - Matcher matcher = pattern.matcher(value); - if (matcher.find()) { - double newVersion = Double.valueOf(matcher.group(1)); + String value = FileUtils.getStringFromURL(API, "{}"); + JsonObject json = new JsonParser().parse(value).getAsJsonObject(); + if (json.entrySet().size() > 0) { + double newVersion = Double.parseDouble(json.get("tag_name").getAsString()); if (TabooLib.getPluginVersion() >= newVersion) { MsgUtils.send("插件已是最新版, 无需更新!"); }