|
16 | 16 | import org.bstats.bukkit.Metrics; |
17 | 17 | import org.jetbrains.annotations.NotNull; |
18 | 18 |
|
19 | | -import java.io.BufferedReader; |
| 19 | +import javax.naming.directory.Attribute; |
| 20 | +import javax.naming.directory.Attributes; |
| 21 | +import javax.naming.directory.DirContext; |
| 22 | +import javax.naming.directory.InitialDirContext; |
20 | 23 | import java.io.IOException; |
21 | | -import java.io.InputStreamReader; |
22 | | -import java.net.HttpURLConnection; |
23 | 24 | import java.net.URI; |
24 | | -import java.net.URL; |
25 | | -import java.nio.charset.StandardCharsets; |
26 | 25 | import java.time.Duration; |
27 | 26 | import java.time.LocalDateTime; |
28 | 27 | import java.time.ZoneId; |
29 | 28 | import java.time.ZonedDateTime; |
30 | 29 | import java.time.format.DateTimeFormatter; |
31 | 30 | import java.util.ArrayList; |
| 31 | +import java.util.Hashtable; |
32 | 32 | import java.util.Objects; |
33 | 33 | import java.util.concurrent.TimeUnit; |
34 | 34 | import java.net.http.HttpClient; |
@@ -192,6 +192,76 @@ private void eewTest(int flag) { |
192 | 192 | Bukkit.broadcastMessage("§eWarning: This is an Earthquake Early Warning test."); |
193 | 193 | } |
194 | 194 |
|
| 195 | + private String fetchVersionFromDnsTxt() throws Exception { |
| 196 | + Hashtable<String, String> env = new Hashtable<>(); |
| 197 | + env.put("java.naming.factory.initial", "com.sun.jndi.dns.DnsContextFactory"); |
| 198 | + // 也可以指定 resolver,例如 Cloudflare:env.put("java.naming.provider.url", "dns://1.1.1.1"); |
| 199 | + DirContext ctx = new InitialDirContext(env); |
| 200 | + |
| 201 | + Attributes attrs = ctx.getAttributes("mceew.mtf.edu.kg", new String[]{"TXT"}); |
| 202 | + Attribute txt = attrs.get("TXT"); |
| 203 | + if (txt == null || txt.size() == 0) return null; |
| 204 | + |
| 205 | + // 一个域名可能有多条 TXT,这里遍历找包含 version= 的那条 |
| 206 | + for (int i = 0; i < txt.size(); i++) { |
| 207 | + String record = String.valueOf(txt.get(i)); |
| 208 | + |
| 209 | + // JNDI 返回的 TXT 可能自带引号,先去掉 |
| 210 | + record = record.replace("\"", "").trim(); |
| 211 | + |
| 212 | + // 允许记录里包含多个键值,例如: foo=bar version=1.2.3 |
| 213 | + // 但你目前是单值:version=1.2.3 |
| 214 | + int idx = record.indexOf("version="); |
| 215 | + if (idx >= 0) { |
| 216 | + String v = record.substring(idx + "version=".length()).trim(); |
| 217 | + |
| 218 | + // 如果后面还有空格/分号之类,切掉 |
| 219 | + int cut = v.indexOf(' '); |
| 220 | + if (cut > 0) v = v.substring(0, cut); |
| 221 | + cut = v.indexOf(';'); |
| 222 | + if (cut > 0) v = v.substring(0, cut); |
| 223 | + |
| 224 | + // 只保留数字和点(防御性) |
| 225 | + v = v.replaceAll("[^0-9.]", ""); |
| 226 | + return v; |
| 227 | + } |
| 228 | + } |
| 229 | + return null; |
| 230 | + } |
| 231 | + |
| 232 | + private int compareSemver(String a, String b) { |
| 233 | + int[] av = parseSemver(a); |
| 234 | + int[] bv = parseSemver(b); |
| 235 | + |
| 236 | + int n = Math.max(av.length, bv.length); |
| 237 | + for (int i = 0; i < n; i++) { |
| 238 | + int ai = i < av.length ? av[i] : 0; |
| 239 | + int bi = i < bv.length ? bv[i] : 0; |
| 240 | + if (ai != bi) return Integer.compare(ai, bi); |
| 241 | + } |
| 242 | + return 0; |
| 243 | + } |
| 244 | + |
| 245 | + private int[] parseSemver(String v) { |
| 246 | + if (v == null) return new int[]{0, 0, 0}; |
| 247 | + v = v.trim(); |
| 248 | + |
| 249 | + // 防御:只留 x.y.z 数字点 |
| 250 | + v = v.replaceAll("[^0-9.]", ""); |
| 251 | + if (v.isEmpty()) return new int[]{0, 0, 0}; |
| 252 | + |
| 253 | + String[] parts = v.split("\\."); |
| 254 | + int[] out = new int[parts.length]; |
| 255 | + for (int i = 0; i < parts.length; i++) { |
| 256 | + try { |
| 257 | + out[i] = Integer.parseInt(parts[i].isEmpty() ? "0" : parts[i]); |
| 258 | + } catch (NumberFormatException e) { |
| 259 | + out[i] = 0; |
| 260 | + } |
| 261 | + } |
| 262 | + return out; |
| 263 | + } |
| 264 | + |
195 | 265 | private boolean canReceive(Player player, String node) { |
196 | 266 | return player.hasPermission("mceew.notify.all") && player.hasPermission(node); |
197 | 267 | } |
@@ -251,32 +321,31 @@ private void mceewScheduler(boolean first) { |
251 | 321 | } |
252 | 322 |
|
253 | 323 | private void updater() { |
254 | | - StringBuilder rawData = new StringBuilder(); |
255 | 324 | try { |
256 | | - URL url = new URL("https://tenkyuchimata.github.io/MCEEW/version.json"); |
257 | | - HttpURLConnection connection = (HttpURLConnection) url.openConnection(); |
258 | | - connection.setConnectTimeout(5000); |
259 | | - connection.setReadTimeout(5000); |
260 | | - if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) { |
261 | | - throw new IOException(); |
| 325 | + // 1) 从 DNS TXT 读取版本号(格式:version=x.x.x) |
| 326 | + String apiVersion = fetchVersionFromDnsTxt(); // 例如 "2.6.2" |
| 327 | + if (apiVersion == null || apiVersion.isBlank()) { |
| 328 | + throw new IOException("Empty version from DNS TXT"); |
262 | 329 | } |
263 | | - try (BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8))) { |
264 | | - String line; |
265 | | - while ((line = reader.readLine()) != null) { |
266 | | - rawData.append(line); |
267 | | - } |
268 | | - JsonObject json = JsonParser.parseString(rawData.toString()).getAsJsonObject(); |
269 | | - String apiVersion = json.get("version").getAsString(); |
270 | | - if (Integer.parseInt(apiVersion.replaceAll("\\.", "")) > Integer.parseInt(version.replaceAll("-b.*", "").replaceAll("\\.", ""))) { |
271 | | - getLogger().warning("New plugin version v" + apiVersion + " detected, Please download a new version from https://acg.kr/mceew"); |
272 | | - } else { |
273 | | - getLogger().info("You are running the latest plugin version."); |
274 | | - } |
| 330 | + |
| 331 | + // 2) 本地版本号清洗(去掉 -bxxx 之类后缀) |
| 332 | + String localVersion = version.replaceAll("-b.*", ""); |
| 333 | + |
| 334 | + // 3) 版本比较(语义化比较,避免 2.10.0 vs 2.6.9 这种出错) |
| 335 | + int cmp = compareSemver(apiVersion, localVersion); |
| 336 | + |
| 337 | + if (cmp > 0) { |
| 338 | + getLogger().warning("New plugin version v" + apiVersion |
| 339 | + + " detected, Please download a new version from https://acg.kr/mceew"); |
| 340 | + } else { |
| 341 | + getLogger().info(String.format("Plugin is up to date. Current version: v%s", apiVersion)); |
275 | 342 | } |
276 | | - } catch (IOException e) { |
277 | | - getLogger().warning("Failed to check for plugin updates."); |
| 343 | + |
| 344 | + } catch (Exception e) { |
| 345 | + getLogger().warning("Failed to check for plugin updates via DNS TXT."); |
278 | 346 | getLogger().warning(String.valueOf(e)); |
279 | 347 | } |
| 348 | + |
280 | 349 | if (currentConfig > configVersion) { |
281 | 350 | getLogger().warning("Configuration update detected, please delete the MCEEW configuration file to update it."); |
282 | 351 | } |
|
0 commit comments