第十八批:cfg.json语法有效值空改继承不判损毁(回退表格/默认、真损毁才退md、每轮重查json、jar无模板日志友好化) + 新增/banget点名全量封禁查询(user/ip自动识别、遍历getEntries防NAME型API失效)
This commit is contained in:
@@ -59,6 +59,7 @@ public class BanBroadcastManager {
|
||||
private long startCutoffMs = 0; // 播报起点(毫秒):只播该时刻之后创建的封禁
|
||||
private boolean startCutoffAuto = true; // 起点未配置 → 自动取插件数据目录创建时间(=插件安装时间)
|
||||
private long autoCutoffMs = -1; // 自动起点缓存(-1=未取)
|
||||
private boolean jsonSyntaxOk = false; // json 文本语法是否有效(区分「语法坏」与「语法好但值空」)
|
||||
|
||||
public BanBroadcastManager(Main plugin) {
|
||||
this.plugin = plugin;
|
||||
@@ -92,6 +93,7 @@ public class BanBroadcastManager {
|
||||
|
||||
/** 配置文件丢失就补一份(自愈) */
|
||||
private void ensureConfigExists() {
|
||||
jsonFile = findJsonConfig(); // ★ 每轮重查:运行中新增/删除的 json 都要跟上
|
||||
try {
|
||||
if (jsonFile != null) {
|
||||
if (!banDir.exists()) banDir.mkdirs();
|
||||
@@ -122,12 +124,27 @@ public class BanBroadcastManager {
|
||||
mode = 1;
|
||||
startCutoffAuto = true;
|
||||
startCutoffMs = 0;
|
||||
if (jsonFile == null) jsonFile = findJsonConfig();
|
||||
jsonFile = findJsonConfig(); // ★ 每轮重查(不再只在 null 时查一次)
|
||||
if (jsonFile != null && jsonFile.exists()) {
|
||||
String txt = readFile(jsonFile);
|
||||
if (parseJsonText(txt)) { log("已加载 json 配置: " + jsonFile.getName()); return; }
|
||||
if (jsonSyntaxOk) {
|
||||
// ★ 第十八批:语法有效但间隔值为空(文档语义「表格/json 二者选一填」)
|
||||
// → 继承 json 里已解析的播报模式/播报起点,间隔回退 md 表格
|
||||
// (只取间隔,绝不覆盖已继承的模式/起点)或出厂默认,不判损毁。
|
||||
boolean iv = false;
|
||||
if (mdFile != null && mdFile.exists()) {
|
||||
String md0 = readFile(mdFile);
|
||||
if (md0 != null) iv = parseIntervalTable(md0);
|
||||
}
|
||||
normalize();
|
||||
log("json 配置语法有效,已继承模式/起点(间隔回退"
|
||||
+ (iv ? "md 表格" : "出厂默认") + "): " + jsonFile.getName());
|
||||
return;
|
||||
}
|
||||
// 语法真损坏 → 备份+移除,继续回退 md 分支(不再就此 return)
|
||||
backupAndReset(jsonFile, jsonFile.getName());
|
||||
return;
|
||||
jsonFile = null;
|
||||
}
|
||||
if (!mdFile.exists()) { ensureConfigExists(); }
|
||||
String md = readFile(mdFile);
|
||||
@@ -160,8 +177,10 @@ public class BanBroadcastManager {
|
||||
* @return true = json 配置生效;false = 视为未配置,调用方回退表格
|
||||
*/
|
||||
private boolean parseJsonText(String txt) {
|
||||
jsonSyntaxOk = false;
|
||||
try {
|
||||
JsonObject o = JsonParser.parseString(txt).getAsJsonObject();
|
||||
jsonSyntaxOk = true; // 语法解析成功(值是否为空另行判定)
|
||||
boolean hasIntervalKey = o.has("最小间隔") || o.has("最大间隔")
|
||||
|| o.has("轮播最低间隔") || o.has("轮播最大间隔");
|
||||
boolean anyInterval = false;
|
||||
@@ -219,6 +238,27 @@ public class BanBroadcastManager {
|
||||
return any;
|
||||
}
|
||||
|
||||
/** 只从 md 表格取间隔值(不碰播报模式/播报起点 —— 继承 json 时仅回退间隔用) */
|
||||
private boolean parseIntervalTable(String md) {
|
||||
boolean any = false;
|
||||
for (String line : md.split("\n")) {
|
||||
String t = line.trim();
|
||||
if (!t.startsWith("|") || t.startsWith("| ---") || t.startsWith("|---")) continue;
|
||||
String[] cells = t.split("\\|");
|
||||
if (cells.length < 4) continue;
|
||||
String key = cells[1].trim();
|
||||
String val = cells[cells.length - 2].trim();
|
||||
if (key.equals("轮播最低间隔")) { minInterval = toSec(val); any = true; }
|
||||
else if (key.equals("轮播最大间隔")) { maxInterval = toSec(val); any = true; }
|
||||
}
|
||||
if (any) {
|
||||
if (minInterval < 0) minInterval = 0;
|
||||
if (maxInterval < 0) maxInterval = 0;
|
||||
if (maxInterval > 0 && minInterval > maxInterval) maxInterval = minInterval;
|
||||
}
|
||||
return any;
|
||||
}
|
||||
|
||||
/** 播报模式文本/布尔/数字 → 0/1/2 */
|
||||
private int parseMode(String s) {
|
||||
if (s == null) return 1;
|
||||
@@ -457,6 +497,11 @@ public class BanBroadcastManager {
|
||||
if (f != null && f.exists()) f.delete();
|
||||
plugin.saveResource("ban/" + resourceName, false);
|
||||
log("已释放一份新配置: " + resourceName);
|
||||
} catch (IllegalArgumentException e) {
|
||||
// jar 里没有同名模板(如用户自建 cfg.json):原文件已备份并移除,
|
||||
// 不算失败 —— 调用方会继续回退 md / 出厂默认
|
||||
warn("jar 内无模板资源 ban/" + resourceName
|
||||
+ ",原文件已备份并移除,回退其它配置源");
|
||||
} catch (Exception e) {
|
||||
warn("备份/重置失败: " + e.getMessage());
|
||||
}
|
||||
|
||||
@@ -1942,6 +1942,114 @@ public class Main extends JavaPlugin
|
||||
sender.sendMessage(sb.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* /banget 点名查询封禁:全量原生 banlist(不应用播报起点过滤,
|
||||
* 插件安装之前的历史封禁也能查到),替代原版 /banlist 一屏全量打印。
|
||||
* 用法:/banget <玩家名|IP>(自动识别)| /banget user <名> | /banget ip <IP>
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private void doBanGet(CommandSender sender, String[] args) {
|
||||
if (!isPrivileged(sender)) {
|
||||
sender.sendMessage("§c权限不足");
|
||||
return;
|
||||
}
|
||||
String kind = null; // user / ip / null=自动识别
|
||||
String target = null;
|
||||
if (args.length == 1) {
|
||||
String a = args[0].toLowerCase();
|
||||
if (a.equals("user") || a.equals("ip")) {
|
||||
sender.sendMessage("§c请指定查询目标: /banget " + a + " <玩家名或IP>");
|
||||
return;
|
||||
}
|
||||
target = args[0];
|
||||
} else if (args.length >= 2) {
|
||||
String a = args[0].toLowerCase();
|
||||
if (!a.equals("user") && !a.equals("ip")) {
|
||||
sender.sendMessage("§c用法: /banget [user|ip] <玩家名或IP>");
|
||||
return;
|
||||
}
|
||||
kind = a;
|
||||
target = args[1];
|
||||
} else {
|
||||
sender.sendMessage("§c用法: /banget [user|ip] <玩家名或IP>");
|
||||
sender.sendMessage("§7示例: §f/banget Notch §7| §f/banget user Notch"
|
||||
+ " §7| §f/banget ip 127.0.0.1");
|
||||
return;
|
||||
}
|
||||
boolean ipMode = kind != null ? kind.equals("ip") : isIpTarget(target);
|
||||
|
||||
sender.sendMessage("§8========== §e封禁查询: §f" + target
|
||||
+ (ipMode ? " §7[IP]" : " §7[玩家名]") + " §8==========");
|
||||
int hits = 0;
|
||||
int expiredHits = 0;
|
||||
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm");
|
||||
try {
|
||||
org.bukkit.BanList bl = Bukkit.getBanList(ipMode
|
||||
? org.bukkit.BanList.Type.IP : org.bukkit.BanList.Type.NAME);
|
||||
// ★ 全量查询:不应用播报起点,历史封禁照查;
|
||||
// getEntries() 实际返回 Set,按 Collection 中转强转(防 ClassCastException)
|
||||
for (Object oo : (java.util.Collection<?>) bl.getEntries()) {
|
||||
org.bukkit.BanEntry e = (org.bukkit.BanEntry) oo;
|
||||
if (!banEntryMatches(e, target)) continue;
|
||||
hits++;
|
||||
Date created = e.getCreated();
|
||||
Date exp = e.getExpiration();
|
||||
boolean expired = exp != null
|
||||
&& exp.getTime() <= System.currentTimeMillis();
|
||||
if (expired) expiredHits++;
|
||||
String reason = e.getReason();
|
||||
if (reason == null || reason.trim().isEmpty()) {
|
||||
reason = "违反服务器规则";
|
||||
}
|
||||
String src;
|
||||
try { src = e.getSource(); } catch (Throwable t) { src = null; }
|
||||
if (src == null || src.trim().isEmpty()) src = "未知";
|
||||
sender.sendMessage((expired ? "§8⌛ §f" : "§c☠ §f")
|
||||
+ target + " §7- " + reason);
|
||||
sender.sendMessage("§7 封于§f"
|
||||
+ (created != null ? df.format(created) : "未知时间")
|
||||
+ " §8| " + (expired ? "§a已过期"
|
||||
: (exp != null ? "§7至§f" + df.format(exp) : "§c永久"))
|
||||
+ " §8| §7来源§f" + src);
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
sender.sendMessage("§c查询封禁名单失败: " + ex.getMessage());
|
||||
return;
|
||||
}
|
||||
if (hits == 0) {
|
||||
sender.sendMessage("§7未查到相关封禁(全量查询,含插件安装前的历史数据)");
|
||||
} else {
|
||||
sender.sendMessage("§7共§f" + hits + "§7条记录"
|
||||
+ (expiredHits > 0
|
||||
? " §8(其中§f" + expiredHits + "§8条已过期)" : ""));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* banget 目标匹配:getTarget() 字符串直比 + GameProfile[id=..,name=..] 兜底。
|
||||
* (NAME 型 getBanEntry(String) 新 API 已不可靠,故遍历 getEntries 自行匹配)
|
||||
*/
|
||||
private boolean banEntryMatches(org.bukkit.BanEntry e, String target) {
|
||||
try {
|
||||
String t = e.getTarget();
|
||||
if (t != null && t.equalsIgnoreCase(target)) return true;
|
||||
} catch (Throwable ignored) { }
|
||||
try {
|
||||
Object bt = e.getBanTarget();
|
||||
if (bt == null) return false;
|
||||
String bs = String.valueOf(bt);
|
||||
if (bs.equalsIgnoreCase(target)) return true;
|
||||
int ni = bs.indexOf("name=");
|
||||
if (ni >= 0) {
|
||||
String nm = bs.substring(ni + 5);
|
||||
int end = nm.indexOf(']');
|
||||
if (end >= 0) nm = nm.substring(0, end);
|
||||
if (nm.equalsIgnoreCase(target)) return true;
|
||||
}
|
||||
} catch (Throwable ignored) { }
|
||||
return false;
|
||||
}
|
||||
|
||||
private long asLong(Object o) {
|
||||
if (o == null) return 0;
|
||||
if (o instanceof Number) return ((Number) o).longValue();
|
||||
@@ -4243,7 +4351,7 @@ public class Main extends JavaPlugin
|
||||
private static final Set<String> ADMIN_ONLY = new HashSet<>(
|
||||
java.util.Arrays.asList(
|
||||
// 封禁 / 改判 / 注销
|
||||
"tempban", "tempbanip", "banip", "whois", "clear",
|
||||
"tempban", "tempbanip", "banip", "banget", "whois", "clear",
|
||||
"cleartake", "清空队列",
|
||||
// 账号管理
|
||||
"删除", "删除账号", "通过", "拒绝", "accept", "deny",
|
||||
@@ -8026,6 +8134,12 @@ public class Main extends JavaPlugin
|
||||
return true;
|
||||
}
|
||||
|
||||
// ===== /banget 点名查询封禁(全量 banlist,含插件安装前的历史封禁) =====
|
||||
if (cmd.getName().equalsIgnoreCase("banget")) {
|
||||
doBanGet(sender, args);
|
||||
return true;
|
||||
}
|
||||
|
||||
String sub = (args.length > 0) ? args[0].toLowerCase() : "";
|
||||
|
||||
// ===== 在 onCommand 方法体内,最开头加: =====
|
||||
@@ -10316,6 +10430,13 @@ public class Main extends JavaPlugin
|
||||
}
|
||||
return list;
|
||||
}
|
||||
// ★ /banget 点名查询封禁:第一层 user/ip 提示
|
||||
if (cmd.getName().equalsIgnoreCase("banget")) {
|
||||
if (args.length == 1) {
|
||||
return filterTab(Arrays.asList("user", "ip"), args[0]);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
if (cmd.getName().equalsIgnoreCase(
|
||||
"sdf1_login")
|
||||
&& args.length == 1) {
|
||||
|
||||
@@ -184,6 +184,9 @@ commands:
|
||||
who:
|
||||
description: 查看在线玩家列表
|
||||
usage: /who
|
||||
banget:
|
||||
description: 点名查询封禁(全量,含插件安装前的历史封禁)
|
||||
usage: /banget [user|ip] <玩家名或IP>
|
||||
清空队列:
|
||||
description: 清空DB队列中未回应的web同步任务(管理员)
|
||||
usage: /清空队列
|
||||
|
||||
Reference in New Issue
Block a user