446 lines
15 KiB
Java
446 lines
15 KiB
Java
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() {
|
|
}
|
|
|
|
/**
|
|
* 当服务端完全启动时执行该方法
|
|
* 完全启动指 "控制台可以输入命令且得到反馈时"
|
|
* <p>
|
|
* 使用 @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();
|
|
|
|
}
|
|
}
|