diff --git a/src/main/java/Sdf1_game/BondBridge.java b/src/main/java/Sdf1_game/BondBridge.java index c14ca32..0fce063 100644 --- a/src/main/java/Sdf1_game/BondBridge.java +++ b/src/main/java/Sdf1_game/BondBridge.java @@ -1,79 +1,65 @@ package Sdf1_game; -import org.bukkit.Bukkit; -import org.bukkit.plugin.Plugin; - -import java.lang.reflect.Field; -import java.lang.reflect.Method; - public class BondBridge { private Object bondManager; - private Method addBondsMethod; + private Object sdf1Plugin; private boolean hooked = false; public boolean hook(Main plugin) { - Plugin sdf1 = Bukkit.getPluginManager() - .getPlugin("Sdf1_login"); - - if (sdf1 == null || !sdf1.isEnabled()) { + try { + // 使用 Class.forName 避免 JVM 直接加载 Sdf1_login.Main.class + // 如果 Main 中有 Vault import,直接 getClassLoader().loadClass 会导致 NoClassDefFoundError + Class mainClass = Class.forName("Sdf1_login.Main"); + sdf1Plugin = mainClass.getField("INSTANCE").get(null); + } catch (ClassNotFoundException e) { plugin.getLogger().warning( - "[快问快答] 未找到Sdf1_login," - + "债券奖励不可用"); + "[快问快答] 未找到Sdf1_login类,债券奖励不可用"); + return false; + } catch (Exception e) { + plugin.getLogger().warning( + "[快问快答] 获取Sdf1_login实例失败: " + e.getMessage()); + return false; + } + + if (sdf1Plugin == null) { + plugin.getLogger().warning( + "[快问快答] Sdf1_login 实例为空"); return false; } try { - bondManager = findManager(sdf1); + bondManager = findManager(); if (bondManager == null) { plugin.getLogger().warning( "[快问快答] 找不到BondManager"); return false; } - - addBondsMethod = - bondManager.getClass() - .getMethod("addBonds", - String.class, - int.class, - String.class, - String.class, - String.class, - String.class); - hooked = true; plugin.getLogger().info( "[快问快答] 债券桥接成功"); return true; } catch (Exception e) { plugin.getLogger().warning( - "[快问快答] 桥接失败: " - + e.getMessage()); + "[快问快答] 桥接失败: " + e.getMessage()); return false; } } - /** 查询玩家当前债券余额 */ + + /** + * 查询玩家当前债券余额 + * 使用反射避免触发 Vault class loading + */ public int getBonds(String playerName) { - if (!isHooked()) return 0; + if (!isHooked() || sdf1Plugin == null) return 0; try { - org.bukkit.plugin.Plugin plug = - org.bukkit.Bukkit - .getPluginManager() - .getPlugin("Sdf1_login"); - if (plug == null) return 0; - - Object bondManager = - plug.getClass() - .getMethod("getBondManager") - .invoke(plug); - - Object result = - bondManager.getClass() - .getMethod("getBonds", - String.class) - .invoke(bondManager, - playerName); + Class mainClass = Class.forName("Sdf1_login.Main"); + java.lang.reflect.Method getBM = mainClass.getMethod("getBondManager"); + Object bm = getBM.invoke(sdf1Plugin); + java.lang.reflect.Method getBondsM = bm.getClass() + .getMethod("getBonds", String.class); + Object result = getBondsM.invoke(bm, playerName); return (int) result; } catch (Exception e) { return 0; @@ -81,34 +67,36 @@ public class BondBridge { } - private Object findManager(Plugin sdf1) { - Class c = sdf1.getClass(); + /** + * findManager: 尝试多种方式获取 BondManager 实例 + * 完全使用反射,避免触发 Main.class 的 Vault 依赖 + */ + private Object findManager() throws Exception { + Class mainClass = Class.forName("Sdf1_login.Main"); - // 策略1: getBondManager() + // 策略1: getBondManager() 方法 try { - Method m = c.getMethod("getBondManager"); - Object r = m.invoke(sdf1); + java.lang.reflect.Method m = mainClass.getMethod("getBondManager"); + Object r = m.invoke(sdf1Plugin); if (r != null) return r; - } catch (Exception ignored) { + } catch (NoSuchMethodException ignored) { } // 策略2: 字段 bondManager try { - Field f = c.getDeclaredField( - "bondManager"); + java.lang.reflect.Field f = mainClass.getDeclaredField("bondManager"); f.setAccessible(true); - Object r = f.get(sdf1); + Object r = f.get(sdf1Plugin); if (r != null) return r; - } catch (Exception ignored) { + } catch (NoSuchFieldException ignored) { } // 策略3: 遍历字段按类型名查找 try { - for (Field f : c.getDeclaredFields()) { - if (f.getType().getSimpleName() - .equals("BondManager")) { + for (java.lang.reflect.Field f : mainClass.getDeclaredFields()) { + if (f.getType().getSimpleName().equals("BondManager")) { f.setAccessible(true); - Object r = f.get(sdf1); + Object r = f.get(sdf1Plugin); if (r != null) return r; } } @@ -118,13 +106,15 @@ public class BondBridge { return null; } - public boolean addBonds(String player, - int amount, - String reason) { - if (!hooked) return false; + public boolean addBonds(String player, int amount, String reason) { + if (!hooked || bondManager == null) return false; try { - int r = (int) addBondsMethod.invoke( - bondManager, + java.lang.reflect.Method m = bondManager.getClass() + .getMethod("addBonds", + String.class, int.class, + String.class, String.class, + String.class, String.class); + int r = (int) m.invoke(bondManager, player, amount, "quiz_reward", "", "快问快答", reason); diff --git a/src/main/java/Sdf1_game/UpdateChecker.java b/src/main/java/Sdf1_game/UpdateChecker.java index c6ff6c1..518e479 100644 --- a/src/main/java/Sdf1_game/UpdateChecker.java +++ b/src/main/java/Sdf1_game/UpdateChecker.java @@ -13,9 +13,6 @@ import java.net.HttpURLConnection; import java.net.URL; import java.security.SecureRandom; import java.security.cert.X509Certificate; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicReference; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLContext; @@ -48,18 +45,30 @@ public class UpdateChecker { private static final String GITHUB_REPO = "Sdf1_games"; private static final String GITEE_OWNER = "nihaoshidifu"; private static final String GITEE_REPO = "sdf1_game"; + private static final String GITEA_OWNER = "youpaishidifu"; + private static final String GITEA_REPO = "Sdf1_game"; - // API 地址(使用releases/latest获取最新版本) + // API 地址(按优先级顺序) + // 第一路:自己的 Gitea + private static final String GITEA_API = + "https://git.ypshidifu.cn/api/v1/repos/" + GITEA_OWNER + "/" + GITEA_REPO + "/releases/latest"; + // 第二路:GitHub private static final String GITHUB_API = "https://api.github.com/repos/" + GITHUB_OWNER + "/" + GITHUB_REPO + "/releases/latest"; + // 第三路:Gitee private static final String GITEE_API = "https://gitee.com/api/v5/repos/" + GITEE_OWNER + "/" + GITEE_REPO + "/releases/latest"; + // 第四路:GitCode + private static final String GITCODE_API = + "https://gitcode.com/api/v5/repos/" + GITHUB_OWNER + "/" + GITHUB_REPO + "/releases?per_page=1"; - // 第三路备选:宝塔静态HTML + // 第五路备选:宝塔静态HTML private static final String FALLBACK_HTML = "https://caoyuan.ypshidifu.cn/update_check.html"; // 仓库页面链接 + private static final String GITEA_LINK = + "https://git.ypshidifu.cn/" + GITEA_OWNER + "/" + GITEA_REPO + "/releases"; private static final String GITHUB_LINK = "https://github.com/" + GITHUB_OWNER + "/" + GITHUB_REPO + "/releases"; private static final String GITEE_LINK = @@ -157,72 +166,66 @@ public class UpdateChecker { plugin.getLogger().info("[Sdf1_game] 启动自动检查更新..."); } - // ★ 双通道并行执行,一路成功掐断另一路 - AtomicBoolean cancelled = new AtomicBoolean(false); - AtomicReference ghResult = new AtomicReference<>(); - AtomicReference geResult = new AtomicReference<>(); - CountDownLatch latch = new CountDownLatch(2); + // 按优先级依次尝试: git.ypshidifu.cn → GitHub → Gitee → GitCode → BaoTa + String[][] channels = { + {GITEA_API, "git.ypshidifu.cn", GITEA_LINK}, + {GITHUB_API, "GitHub", GITHUB_LINK}, + {GITEE_API, "Gitee", GITEE_LINK}, + }; - // 通道1:GitHub - Thread ghThread = new Thread(() -> { - if (!cancelled.get()) { - ghResult.set(tryChannel(GITHUB_API, "GitHub", GITHUB_LINK)); - if (ghResult.get().isSuccess()) { - cancelled.set(true); // 成功则取消另一路 - } + CheckResult chosen = null; + String lastError = ""; + + // 前三路: releases/latest 格式 (返回对象) + for (String[] ch : channels) { + CheckResult result = tryChannel(ch[0], ch[1], ch[2]); + if (result.isSuccess()) { + chosen = result; + break; } - latch.countDown(); - }); - ghThread.setDaemon(true); - - // 通道2:Gitee - Thread geThread = new Thread(() -> { - if (!cancelled.get()) { - geResult.set(tryChannel(GITEE_API, "Gitee", GITEE_LINK)); - if (geResult.get().isSuccess()) { - cancelled.set(true); // 成功则取消另一路 - } + if (result.error != null) { + lastError = result.error; } - latch.countDown(); - }); - geThread.setDaemon(true); - - ghThread.start(); - geThread.start(); - - try { - latch.await(); // 等待两路都完成 - } catch (InterruptedException ignored) { } - CheckResult gh = ghResult.get(); - CheckResult ge = geResult.get(); + // GitCode: releases 数组格式 (返回数组) + if (chosen == null) { + CheckResult gdResult = tryGitCodeChannel(); + if (gdResult.isSuccess()) { + chosen = gdResult; + } else { + if (gdResult.error != null) { + lastError = gdResult.error; + } + } + } - // 选择成功的结果 - CheckResult chosen = null; - - if (gh != null && gh.isSuccess()) { - chosen = gh; - } else if (ge != null && ge.isSuccess()) { - chosen = ge; - } else { - // ★ 双通道均失败,尝试第三路备选(宝塔静态HTML) - plugin.getLogger().info("[Sdf1_game] GitHub/Gitee 均失败,尝试第三路备选 BaoTa..."); + // 第五路: 宝塔静态 HTML + if (chosen == null) { + plugin.getLogger().info("[Sdf1_game] 前四路均失败,尝试第五路备选 BaoTa..."); CheckResult fbResult = tryFallbackChannel(); - if (fbResult != null && fbResult.isSuccess()) { + if (fbResult.isSuccess()) { chosen = fbResult; - plugin.getLogger().info("[Sdf1_game] 第三路备选 BaoTa 检查成功: " + fbResult.version); + plugin.getLogger().info("[Sdf1_game] 第五路备选 BaoTa 检查成功: " + fbResult.version); } else { failCount++; StringBuilder sb = new StringBuilder(); - if (gh != null && gh.error != null) { - sb.append("§7GitHub: §c").append(gh.error); + sb.append("§7git.ypshidifu.cn: §c").append(lastError != null ? lastError : "未知错误"); + + // 输出各通道失败信息 + for (String[] ch : channels) { + CheckResult r = tryChannel(ch[0], ch[1], ch[2]); + if (!r.isSuccess() && r.error != null) { + if (sb.length() > 0) sb.append("\n"); + sb.append("§7").append(ch[1]).append(": §c").append(r.error); + } } - if (ge != null && ge.error != null) { + CheckResult gdTest = tryGitCodeChannel(); + if (!gdTest.isSuccess() && gdTest.error != null) { if (sb.length() > 0) sb.append("\n"); - sb.append("§7Gitee: §c").append(ge.error); + sb.append("§7GitCode: §c").append(gdTest.error); } - if (fbResult != null && fbResult.error != null) { + if (fbResult != null && !fbResult.isSuccess() && fbResult.error != null) { if (sb.length() > 0) sb.append("\n"); sb.append("§7BaoTa: §c").append(fbResult.error); } @@ -247,13 +250,17 @@ public class UpdateChecker { } return; } + } else { + failCount = 0; } // 至少一个通道成功 - failCount = 0; + if (chosen != null) { + failCount = 0; + } // 检查禁用信号 - if (isDisableSignal(chosen.version)) { + if (chosen != null && isDisableSignal(chosen.version)) { autoCheckDisabled = true; saveDisabledState(); plugin.getLogger().info( @@ -266,8 +273,8 @@ public class UpdateChecker { } // 手动调用:成功才解除熔断和禁用 - if (!isAuto) { - manualSuccess = true; // 标记手动成功,不再执行后续自动检查 + if (!isAuto && chosen != null) { + manualSuccess = true; if (circuitTripped) { circuitTripped = false; } @@ -281,13 +288,11 @@ public class UpdateChecker { final String lnk = chosen.link; final String rv = chosen.version; - // ★ 自动检查也记录日志(含下载链接 + 颜色提醒) - if (isAuto) { + if (isAuto && chosen != null) { boolean hasUpdate = isNewer(rv, currentVersion); String status = hasUpdate ? "(有新版本!)" : "(已是最新)"; - // 使用Bukkit.getConsoleSender().sendMessage()输出彩色日志 Bukkit.getConsoleSender().sendMessage("§a[Sdf1_game] §e更新检测成功 via: " + src + " | 本地: " + currentVersion + " | 远程: " + rv + " §7" + status); if (hasUpdate) { @@ -295,27 +300,21 @@ public class UpdateChecker { } } - Bukkit.getScheduler().runTask(plugin, () -> { - if (sender == null) return; + if (chosen != null && sender != null) { + Bukkit.getScheduler().runTask(plugin, () -> { + sender.sendMessage("§e[Sdf1_game] [更新] 检测成功 via: §a" + src); + sender.sendMessage("§e[Sdf1_game] [更新] 本地版本: §f" + currentVersion); + sender.sendMessage("§e[Sdf1_game] [更新] 远程版本: §f" + rv); - sender.sendMessage("§e[Sdf1_game] [更新] 检测成功 via: §a" + src); - sender.sendMessage("§e[Sdf1_game] [更新] 本地版本: §f" + currentVersion); - sender.sendMessage("§e[Sdf1_game] [更新] 远程版本: §f" + rv); - - if (isNewer(rv, currentVersion)) { - sender.sendMessage("§c[Sdf1_game] [更新] 发现新版本: " + rv); - sender.sendMessage("§a[Sdf1_game] 请前往仓库下载更新:"); - sender.sendMessage("§a§l" + lnk); - } else { - sender.sendMessage("§a[Sdf1_game] [更新] 已是最新版本"); - } - - // 显示另一通道的失败原因 - CheckResult other = (src.equals("GitHub")) ? ge : gh; - if (other != null && !other.isSuccess() && other.error != null) { - sender.sendMessage("§7[Sdf1_game] [更新] " + other.source + " 不可用: " + other.error); - } - }); + if (isNewer(rv, currentVersion)) { + sender.sendMessage("§c[Sdf1_game] [更新] 发现新版本: " + rv); + sender.sendMessage("§a[Sdf1_game] 请前往仓库下载更新:"); + sender.sendMessage("§a§l" + lnk); + } else { + sender.sendMessage("§a[Sdf1_game] [更新] 已是最新版本"); + } + }); + } } // ========== 禁用信号判断 ========== @@ -484,6 +483,153 @@ public class UpdateChecker { } } + /** + * 第四路: GitCode releases API (返回数组格式 [{...}]) + * 接口: api.gitcode.com/api/v5/repos/{owner}/{repo}/releases?per_page=1 + */ + private CheckResult tryGitCodeChannel() { + HttpURLConnection conn = null; + try { + URL url = new URL(GITCODE_API); + conn = (HttpURLConnection) url.openConnection(); + conn.setRequestMethod("GET"); + conn.setRequestProperty("User-Agent", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 " + + "(KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36"); + conn.setConnectTimeout(TIMEOUT_MS); + conn.setReadTimeout(TIMEOUT_MS); + conn.setInstanceFollowRedirects(true); + + int code = conn.getResponseCode(); + if (code == 404) { + return new CheckResult(null, "GitCode", GITHUB_LINK, "GitCode: 未找到 (HTTP 404)"); + } + if (code == 403) { + return new CheckResult(null, "GitCode", GITHUB_LINK, "GitCode: 被限流 (HTTP 403)"); + } + if (code != 200) { + return new CheckResult(null, "GitCode", GITHUB_LINK, "GitCode: HTTP " + code); + } + + BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream())); + StringBuilder sb = new StringBuilder(); + String line; + while ((line = reader.readLine()) != null) { + sb.append(line); + } + reader.close(); + + String json = sb.toString(); + + // GitCode 返回数组格式: [{"name":"v1.1",...}] + // 检测 '[' 开头 + String trimmed = json.trim(); + if (trimmed.startsWith("[")) { + // 找匹配的 ']' 和中间的对象 + int closeIdx = findMatchingBracket(trimmed, 0); + if (closeIdx > 0) { + String objStr = trimmed.substring(0, closeIdx + 1); + return parseJsonString(objStr, "GitCode", GITHUB_LINK); + } + } else { + // 如果是普通对象,直接用已有逻辑 + return parseJsonString(trimmed, "GitCode", GITHUB_LINK); + } + + return new CheckResult(null, "GitCode", GITHUB_LINK, "GitCode: 无法解析数组格式"); + + } catch (java.net.SocketTimeoutException e) { + return new CheckResult(null, "GitCode", GITHUB_LINK, "GitCode: 连接超时"); + } catch (java.net.ConnectException e) { + return new CheckResult(null, "GitCode", GITHUB_LINK, "GitCode: 连接被拒绝"); + } catch (java.net.UnknownHostException e) { + return new CheckResult(null, "GitCode", GITHUB_LINK, "GitCode: DNS解析失败"); + } catch (Exception e) { + return new CheckResult(null, "GitCode", GITHUB_LINK, "GitCode: " + e.getMessage()); + } finally { + if (conn != null) conn.disconnect(); + } + } + + /** + * 从数组 "[{...}]" 中提取第一个对象的 JSON 字符串并解析 + */ + private CheckResult parseFromArrayJson(String arrJson, String source, String link) { + String trimmed = arrJson.trim(); + if (!trimmed.startsWith("[")) { + return new CheckResult(null, source, link, source + ": 预期数组格式"); + } + int closeIdx = findMatchingBracket(trimmed, 0); + if (closeIdx < 0) { + return new CheckResult(null, source, link, source + ": 括号不匹配"); + } + String firstObj = trimmed.substring(1, closeIdx).trim(); + // 去掉可能的尾部逗号 + while (firstObj.endsWith(",")) { + firstObj = firstObj.substring(0, firstObj.length() - 1).trim(); + } + return parseJsonString(firstObj, source, link); + } + + /** + * 解析 JSON 字符串(可能是对象或数组) + */ + private CheckResult parseJsonString(String json, String source, String link) { + String trimmed = json.trim(); + + // 如果是数组 [...], 提取第一个对象 + if (trimmed.startsWith("[")) { + return parseFromArrayJson(trimmed, source, link); + } + + // 对象格式: {"tag_name": "..."} + int idx = trimmed.indexOf("\"tag_name\""); + if (idx >= 0) { + int colon = trimmed.indexOf(":", idx); + int q1 = trimmed.indexOf("\"", colon + 1); + int q2 = trimmed.indexOf("\"", q1 + 1); + if (q2 > q1) { + return new CheckResult(cleanTag(trimmed.substring(q1 + 1, q2)), source, link, null); + } + } + + // 备用: "name" 字段 + idx = trimmed.indexOf("\"name\""); + if (idx >= 0) { + int colon = trimmed.indexOf(":", idx); + int q1 = trimmed.indexOf("\"", colon + 1); + int q2 = trimmed.indexOf("\"", q1 + 1); + if (q2 > q1) { + return new CheckResult(cleanTag(trimmed.substring(q1 + 1, q2)), source, link, null); + } + } + + return new CheckResult(null, source, link, source + ": 无法解析版本号"); + } + + /** + * 查找匹配的右括号(支持嵌套括号) + * 从索引 i 处的 '[' 找到对应的 ']' + */ + private int findMatchingBracket(String json, int i) { + if (i >= json.length() || json.charAt(i) != '[') return -1; + int depth = 1; + boolean inString = false; + for (int j = i + 1; j < json.length(); j++) { + char c = json.charAt(j); + if (c == '"' && (j == 0 || json.charAt(j - 1) != '\\')) { + inString = !inString; + } + if (inString) continue; + if (c == '[') depth++; + else if (c == ']') { + depth--; + if (depth == 0) return j; + } + } + return -1; + } + private String parseTagName(String json) { // tags API 返回数组: [{"name":"v1.1",...}] // releases API 返回对象: {"tag_name":"v1.1"}