import com.google.gson.JsonObject; import com.google.gson.JsonParser; import io.izzel.taboolib.PluginLoader; import org.bukkit.Bukkit; import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.util.NumberConversions; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.lang.reflect.Method; import java.net.URL; import java.net.URLClassLoader; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.Objects; import java.util.Optional; import java.util.UUID; import java.util.zip.ZipFile; /** * @Author 坏黑 * @Since 2019-07-05 9:03 */ public abstract class Plugin extends JavaPlugin { /** * 版本信息获取地址 * 优先采用国内地址 * 防止部分机器封禁海外访问 */ public static final String[][] URL = { { "https://skymc.oss-cn-shanghai.aliyuncs.com/plugins/latest.json", "https://skymc.oss-cn-shanghai.aliyuncs.com/plugins/TabooLib.jar" }, { "https://api.github.com/repos/Bkm016/TabooLib/releases/latest", "https://github.com/Bkm016/TabooLib/releases/latest/download/TabooLib.jar", }, }; protected static Plugin plugin; protected static File libFile = new File("TabooLib.jar"); /** * 插件在初始化过程中出现错误 * 将在 onLoad 方法下关闭插件 */ protected static boolean initFailed; static { init(); } @Override public final void onLoad() { if (initFailed) { setEnabled(false); return; } plugin = this; PluginLoader.addPlugin(this); PluginLoader.load(this); try { onLoading(); } catch (Throwable t) { t.printStackTrace(); } } @Override public final void onEnable() { if (initFailed) { return; } PluginLoader.start(this); try { onStarting(); } catch (Throwable t) { t.printStackTrace(); } Bukkit.getScheduler().runTask(this, () -> { PluginLoader.active(this); onActivated(); }); } @Override public final void onDisable() { if (initFailed) { return; } PluginLoader.stop(this); try { onStopping(); } catch (Throwable t) { t.printStackTrace(); } } /** * 代替 JavaPlugin 本身的 onLoad 方法 */ public void onLoading() { } /** * 代替 JavaPlugin 本身的 onEnable 方法 */ public void onStarting() { } /** * 代替 JavaPlugin 本身的 onDisable 方法 */ public void onStopping() { } /** * 当服务端完全启动时执行该方法 * 完全启动指 "控制台可以输入命令且得到反馈时" *

* 使用 @TSchedule 同样可以代替该方法 */ public void onActivated() { } /** * 检查 TabooLib 是否已经被载入 * 跳过 TabooLib 主类的初始化过程 */ public static boolean isLoaded() { try { return Class.forName("io.izzel.taboolib.TabooLib", false, Bukkit.class.getClassLoader()) != null; } catch (Throwable ignored) { } return false; } /** * 获取 TabooLib 当前运行版本 */ public static double getVersion() { try { ZipFile zipFile = new ZipFile(libFile); return NumberConversions.toDouble(readFully(zipFile.getInputStream(zipFile.getEntry("version")), StandardCharsets.UTF_8)); } catch (Throwable t) { t.printStackTrace(); } return -1; } /** * 获取 TabooLib 当前最新版本 * 获取内容为版本号+访问地址+下载地址 */ public static String[] getNewVersion() { for (String[] url : URL) { String read = readFromURL(url[0]); if (read == null) { continue; } try { JsonObject jsonObject = (JsonObject) new JsonParser().parse(read); if (jsonObject.has("tag_name")) { return new String[] {jsonObject.get("tag_name").getAsString(), url[0], url[1]}; } } catch (Throwable t) { t.printStackTrace(); } } return null; } /** * 将文件读取至内存中 * 读取后不会随着插件的卸载而卸载 * 请在执行前判断是否已经被读取 * 防止出现未知错误 */ private static void addToPath(File file) { try { Method method = URLClassLoader.class.getDeclaredMethod("addURL", URL.class); method.setAccessible(true); method.invoke(Bukkit.class.getClassLoader(), file.toURI().toURL()); } catch (Throwable e) { e.printStackTrace(); } } private static boolean downloadFile(String in, File file) { try (InputStream inputStream = new URL(in).openStream(); BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream)) { toFile(bufferedInputStream, file); return true; } catch (Throwable t) { t.printStackTrace(); } return false; } private static File toFile(String in, File file) { try (FileWriter fileWriter = new FileWriter(file); BufferedWriter bufferedWriter = new BufferedWriter(fileWriter)) { bufferedWriter.write(in); bufferedWriter.flush(); } catch (Exception ignored) { } return file; } private static File toFile(InputStream inputStream, File file) { try (FileOutputStream fos = new FileOutputStream(file); BufferedOutputStream bos = new BufferedOutputStream(fos)) { byte[] buf = new byte[1024]; int len; while ((len = inputStream.read(buf)) > 0) { bos.write(buf, 0, len); } bos.flush(); } catch (Exception ignored) { } return file; } private static String readFromURL(String in, String def) { return Optional.ofNullable(readFromURL(in)).orElse(def); } private static String readFromURL(String in) { try (InputStream inputStream = new URL(in).openStream(); BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream)) { return new String(readFully(bufferedInputStream)); } catch (Throwable t) { t.printStackTrace(); } return null; } private static String readFully(InputStream inputStream, Charset charset) throws IOException { return new String(readFully(inputStream), charset); } private static byte[] readFully(InputStream inputStream) throws IOException { ByteArrayOutputStream stream = new ByteArrayOutputStream(); byte[] buf = new byte[1024]; int len = 0; while ((len = inputStream.read(buf)) > 0) { stream.write(buf, 0, len); } return stream.toByteArray(); } private static Class getMainClass() { File file = file(new File("plugins/TabooLib/temp/" + UUID.randomUUID())); try { ZipFile zipFile = new ZipFile(toFile(Plugin.class.getProtectionDomain().getCodeSource().getLocation().openStream(), file)); return Class.forName(YamlConfiguration.loadConfiguration(new InputStreamReader(zipFile.getInputStream(zipFile.getEntry("plugin.yml")))).getString("main")); } catch (Throwable t) { t.printStackTrace(); } return null; } private static Class getCaller(Class obj) { try { return Class.forName(Thread.currentThread().getStackTrace()[3].getClassName(), false, obj.getClassLoader()); } catch (ClassNotFoundException ignored) { } return null; } private static File file(File file) { if (!file.exists()) { folder(file); try { file.createNewFile(); } catch (Throwable t) { t.printStackTrace(); } } return file; } private static File folder(File file) { if (!file.exists()) { String filePath = file.getPath(); int index = filePath.lastIndexOf(File.separator); String folderPath; File folder; if ((index >= 0) && (!(folder = new File(filePath.substring(0, index))).exists())) { folder.mkdirs(); } } return file; } private static void deepDelete(File file) { if (!file.exists()) { return; } if (file.isFile()) { file.delete(); return; } for (File file1 : Objects.requireNonNull(file.listFiles())) { deepDelete(file1); } file.delete(); } private static void init() { // 检查 TabooLib 文件是否存在 if (!libFile.exists()) { // 本地资源检测 InputStream resourceAsStream = Plugin.class.getClassLoader().getResourceAsStream("TabooLib.jar"); if (resourceAsStream != null) { // 写入文件 toFile(resourceAsStream, libFile); } // 在线资源下载 else if (!download()) { return; } } // 检查 TabooLib 运行版本是否正常 else { double version = getVersion(); // 本地版本获取失败 if (version == -1) { // 重新下载文件,如果下载失败则停止加载 if (!download()) { return; } } Class mainClass = getMainClass(); if (mainClass != null && mainClass.isAnnotationPresent(Version.class)) { double requireVersion = mainClass.getAnnotation(Version.class).value(); // 依赖版本高于当前运行版本 if (requireVersion > version) { // 获取版本信息 String[] newVersion = getNewVersion(); if (newVersion == null) { close(); return; } // 检查依赖版本是否合理 // 如果插件使用不合理的版本则跳过下载防止死循环 // 并跳过插件加载 if (requireVersion > NumberConversions.toInt(newVersion[0])) { Bukkit.getConsoleSender().sendMessage("§4[TabooLib] §c版本 §4" + requireVersion + " §c无法获取."); initFailed = true; return; } Bukkit.getConsoleSender().sendMessage("§f[TabooLib] §7正在重新下载资源文件."); if (downloadFile(newVersion[2], libFile)) { // 如果资源下载成功则重启服务器 restart(); } return; } } } // 检查 TabooLib 是否已经被加载 if (!isLoaded()) { // 将 TabooLib 通过 Bukkit.class 类加载器加载至内存中供其他插件使用 // 并保证在热重载过程中不会被 Bukkit 卸载 addToPath(libFile); // 初始化 TabooLib 主类 try { Class.forName("io.izzel.taboolib.TabooLib"); } catch (Throwable t) { t.printStackTrace(); } } // 清理临时文件 deepDelete(new File("plugins/TabooLib/temp")); } private static boolean download() { Bukkit.getConsoleSender().sendMessage("§f[TabooLib] §7正在下载资源文件."); String[] newVersion = getNewVersion(); if (newVersion == null || !downloadFile(newVersion[2], libFile)) { close(); return false; } return true; } private static void close() { Bukkit.getConsoleSender().sendMessage("§4[TabooLib] §c"); Bukkit.getConsoleSender().sendMessage("§4[TabooLib] §c#################### 错误 ####################"); Bukkit.getConsoleSender().sendMessage("§4[TabooLib] §c"); Bukkit.getConsoleSender().sendMessage("§4[TabooLib] §c 初始化 §4TabooLib §c失败!"); Bukkit.getConsoleSender().sendMessage("§4[TabooLib] §c 无法获取版本信息或下载时出现错误."); Bukkit.getConsoleSender().sendMessage("§4[TabooLib] §c"); Bukkit.getConsoleSender().sendMessage("§4[TabooLib] §c 请检查网络连接."); Bukkit.getConsoleSender().sendMessage("§4[TabooLib] §c 服务端将在 5 秒后继续启动."); Bukkit.getConsoleSender().sendMessage("§4[TabooLib] §c"); Bukkit.getConsoleSender().sendMessage("§4[TabooLib] §c#################### 错误 ####################"); Bukkit.getConsoleSender().sendMessage("§4[TabooLib] §c"); try { Thread.sleep(5000L); } catch (Throwable t) { t.printStackTrace(); } initFailed = true; } private static void restart() { Bukkit.getConsoleSender().sendMessage("§4[TabooLib] §c"); Bukkit.getConsoleSender().sendMessage("§4[TabooLib] §c#################### 警告 ####################"); Bukkit.getConsoleSender().sendMessage("§4[TabooLib] §c"); Bukkit.getConsoleSender().sendMessage("§4[TabooLib] §c 初始化 §4TabooLib §c失败!"); Bukkit.getConsoleSender().sendMessage("§4[TabooLib] §c 当前运行的版本低于插件所需版本."); Bukkit.getConsoleSender().sendMessage("§4[TabooLib] §c"); Bukkit.getConsoleSender().sendMessage("§4[TabooLib] §c 已下载最新版."); Bukkit.getConsoleSender().sendMessage("§4[TabooLib] §c 服务端将在 5 秒后重新启动."); Bukkit.getConsoleSender().sendMessage("§4[TabooLib] §c"); Bukkit.getConsoleSender().sendMessage("§4[TabooLib] §c#################### 警告 ####################"); Bukkit.getConsoleSender().sendMessage("§4[TabooLib] §c"); try { Thread.sleep(5000L); } catch (Throwable t) { t.printStackTrace(); } initFailed = true; Bukkit.shutdown(); } public static Plugin getPlugin() { return plugin; } public static File getLibFile() { return libFile; } @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface Version { double value(); } }