From 881fb85408eda1c1fe32f85f04ca4160e2625d2f Mon Sep 17 00:00:00 2001 From: youpaishidifu <1@ypshidifu.cn> Date: Thu, 8 Oct 2026 15:41:46 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E4=B8=89=E5=A4=84bug?= =?UTF-8?q?=EF=BC=9A=E6=97=B6=E9=97=B4=E8=A7=A3=E6=9E=90=E6=94=AF=E6=8C=81?= =?UTF-8?q?26-10-08=2014:43=E7=9F=AD=E6=A8=AA=E7=BA=BF=E6=A0=BC=E5=BC=8F?= =?UTF-8?q?=E3=80=81=E7=A6=81=E8=A8=80=E6=9C=9F=E9=97=B4=E5=B1=8F=E8=94=BD?= =?UTF-8?q?=E4=B8=80=E5=88=87=E7=A7=81=E4=BF=A1=E6=94=B6=E5=8F=91=E3=80=81?= =?UTF-8?q?URL=E6=8B=A6=E6=88=AAbaidu=20.=20com=E7=A9=BA=E6=A0=BC=E5=8F=98?= =?UTF-8?q?=E5=BD=A2=E7=BB=95=E8=BF=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/Sdf1_login/ChatFilterManager.java | 25 +++++- src/main/java/Sdf1_login/DurationParser.java | 76 ++++++++++++++++++- src/main/java/Sdf1_login/Main.java | 68 ++++++++++++++++- 3 files changed, 163 insertions(+), 6 deletions(-) diff --git a/src/main/java/Sdf1_login/ChatFilterManager.java b/src/main/java/Sdf1_login/ChatFilterManager.java index 10d4858..fc14bbb 100644 --- a/src/main/java/Sdf1_login/ChatFilterManager.java +++ b/src/main/java/Sdf1_login/ChatFilterManager.java @@ -652,7 +652,15 @@ public class ChatFilterManager { + "[_.][\\p{L}]{2,}"); private static final Pattern DOT_VARIANTS = Pattern.compile( - "[\uff0e\u3002\u2025\u2026\u00b7]"); + "[\uff0e\u3002\u2025\u2026\u00b7\uff65\ufe52]"); + /** 零宽/不可见字符:变形URL归一前剔除(如 baidu.<零宽空格>com) */ + private static final Pattern INVISIBLE = + Pattern.compile( + "[\u200b\u200c\u200d\u2060\ufeff\u00ad]"); + /** 点周带空白的变形域名:baidu . com(2026-10-08 加强拦截) */ + private static final Pattern SPACED_DOT_URL = + Pattern.compile( + "(?i)(?:[\\p{L}0-9](?:[\\p{L}0-9\\-]*[\\p{L}0-9])?[\\s\\u3000]*\\.)+[\\s\\u3000]*[\\p{L}]{2,}"); private static final Pattern COLOR_CODES = Pattern.compile( "\u00a7[0-9a-fk-orA-FK-OR]"); @@ -977,6 +985,8 @@ public class ChatFilterManager { // ========== URL检测 ========== private String normalizeMessage(String msg) { + msg = INVISIBLE.matcher(msg) + .replaceAll(""); msg = COLOR_CODES.matcher(msg) .replaceAll(""); msg = DOT_VARIANTS.matcher(msg) @@ -999,6 +1009,19 @@ public class ChatFilterManager { ""); if (!url.isEmpty()) urls.add(url); } + // ★ 变形URL加强(2026-10-08):点周带空白(baidu . com)压缩空白后再判定; + // 仅当顶级域命中「非法域名后缀」才收录,避免误伤 "OK. Good" 之类正常英文句子 + Matcher m2 = SPACED_DOT_URL.matcher(normalized); + while (m2.find()) { + String token = m2.group(); + if (token.indexOf(' ') < 0 && token.indexOf('\t') < 0 + && token.indexOf('\u3000') < 0) continue; // 无空白 = 常规域名,上面已提取 + String compact = token.replaceAll("[\\s\u3000]+", "").toLowerCase(); + int dot = compact.lastIndexOf('.'); + if (dot < 0) continue; + if (!illegalDomainSuffixes.contains(compact.substring(dot + 1))) continue; + if (!urls.contains(compact)) urls.add(compact); + } return urls; } diff --git a/src/main/java/Sdf1_login/DurationParser.java b/src/main/java/Sdf1_login/DurationParser.java index 9337f22..5271395 100644 --- a/src/main/java/Sdf1_login/DurationParser.java +++ b/src/main/java/Sdf1_login/DurationParser.java @@ -69,8 +69,15 @@ public class DurationParser { throws IllegalArgumentException { if (input == null || input.trim().isEmpty()) throw new IllegalArgumentException("时长为空"); + String in = input.trim(); + + // ★ 0) 日期+时间(绝对):26-10-08 14:43 / 2026-10-08 14:43 / 2026年10月8日 14:43 + // 必须在去空格之前解析:否则日期与时间粘连(26-10-08 14:43 → 26-10-0814:43)无法区分 + long dt = parseDateTime(in); + if (dt >= 0) return dt; + // ★ 先把英文数字词换成阿拉伯数字(one min -> 1 min) - String s = normalizeEnglishNumber(input.trim()).replace(" ", ""); + String s = normalizeEnglishNumber(in).replace(" ", ""); // 1) 纯数字 → Unix 秒时间戳 if (s.matches("\\d+")) { @@ -170,6 +177,73 @@ public class DurationParser { } } + // ===== 日期+时间绝对解析(2026-10-08 新增:支持 26-10-08 14:43 写法) ===== + + /** 中文日期+时间:2026年10月8日 14:43(“日”已界定日期边界,时间可紧贴) */ + private static final Pattern DT_CN = Pattern.compile( + "(\\d{4})年(\\d{1,2})月(\\d{1,2})日(?:[ T\\u3000]+)?" + + "(\\d{1,2}):(\\d{2})(?::(\\d{2}))?(?!\\d|[A-Za-z])"); + + /** 分隔日期+时间:26-10-08 14:43 / 2026-10-08 14:43 / 2026.10.08T14:43:00 + * 两位年份按 20xx 解释;日期与时间之间必须有空格或 T, + * 防止 2026-1-8 14:43 被粘连误读成「日=81、时=4」 */ + private static final Pattern DT_SEP = Pattern.compile( + "(? 23 || M > 59 || S > 59) + throw new IllegalArgumentException("无效的时间: " + h + ":" + mi); + return new int[]{H, M, S}; + } + + /** 绝对日期+时间(t[0..2] 为 -1 表示 00:00:00) */ + private static long buildDateTimeMillis(int y, int mo, int d, int[] t) { + if (mo < 1 || mo > 12 || d < 1 || d > 31) + throw new IllegalArgumentException("无效的日期: " + y + "-" + mo + "-" + d); + Calendar c = Calendar.getInstance(); + c.set(y, mo - 1, d, + t[0] < 0 ? 0 : t[0], + t[1] < 0 ? 0 : t[1], + t[2] < 0 ? 0 : t[2]); + c.set(Calendar.MILLISECOND, 0); + return c.getTimeInMillis(); + } + private static long parseRelative(String s) { // 按「数字 + 单位」逐段匹配并累加 StringBuilder units = new StringBuilder(); diff --git a/src/main/java/Sdf1_login/Main.java b/src/main/java/Sdf1_login/Main.java index 41b7048..c935059 100644 --- a/src/main/java/Sdf1_login/Main.java +++ b/src/main/java/Sdf1_login/Main.java @@ -3447,16 +3447,76 @@ public class Main extends JavaPlugin // 告示牌通过 onSignChange 拦截,编辑书通过 onPlayerEditBook 拦截 // 私信通过 PlayerCommandPreprocessEvent 拦截(/tell, /msg, /w, /r) + /** 私信命令标签(/r 回复类单独处理),2026-10-08 扩展命名空间与别名 */ + private static final Set TELL_CMDS = new HashSet<>(java.util.Arrays.asList( + "tell", "msg", "w", "whisper", "pm")); + /** 最近私信会话:本玩家 → 对方玩家(供 /r 定位接收方,禁言收发屏蔽用) */ + private final java.util.Map lastTellPeer = + new java.util.concurrent.ConcurrentHashMap<>(); + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) public void onPrivateMessage(org.bukkit.event.player.PlayerCommandPreprocessEvent e) { Player p = e.getPlayer(); - String cmd = e.getMessage().toLowerCase(); - boolean isTellCmd = cmd.startsWith("/tell ") || cmd.startsWith("/msg ") || cmd.startsWith("/w ") || cmd.startsWith("/r "); - if (!isTellCmd) return; + String raw = e.getMessage(); + // ★ 解析命令标签(兼容 /minecraft:msg 命名空间、连续空格) + String[] tok = raw.trim().split("\\s+"); + if (tok.length == 0) return; + String label = tok[0].startsWith("/") ? tok[0].substring(1) : tok[0]; + label = label.toLowerCase(java.util.Locale.ROOT); + int colon = label.indexOf(':'); + if (colon >= 0) label = label.substring(colon + 1); + boolean isTellCmd = TELL_CMDS.contains(label); + boolean isReplyCmd = label.equals("r") || label.equals("reply"); + if (!isTellCmd && !isReplyCmd) return; if (isFrozen(p)) return; if (chatFilter == null || !chatFilter.isEnabled()) return; + + // ===== 禁言屏蔽(2026-10-08):禁言期间屏蔽一切私信收发 ===== + // 1) 发送方被禁言 → 拦 + if (chatFilter.isMuted(p.getName())) { + e.setCancelled(true); + String r = chatFilter.getMuteReason(p.getName()); + long expire = chatFilter.getMuteExpire(p.getName()); + StringBuilder sb = new StringBuilder("§c§l[私信] §f你已被禁言,禁言期间无法发送私信"); + if (r != null && !r.isEmpty()) sb.append("\n§7原因: §f").append(r); + if (expire > 0) sb.append("\n§7到期: §f") + .append(new java.text.SimpleDateFormat("yyyy年M月d日 HH点mm分") + .format(new java.util.Date(expire))); + p.sendMessage(sb.toString()); + return; + } + // 2) 解析接收方(/r 取最近会话对方) + Player peer = null; + if (isTellCmd && tok.length >= 2 && !tok[1].startsWith("@")) { + peer = org.bukkit.Bukkit.getPlayerExact(tok[1]); + if (peer == null) peer = org.bukkit.Bukkit.getPlayer(tok[1]); + if (peer == null) { + try { + peer = org.bukkit.Bukkit.getPlayer(java.util.UUID.fromString(tok[1])); + } catch (Exception ignore) { + } + } + } else if (isReplyCmd) { + String prev = lastTellPeer.get(p.getName()); + if (prev != null) peer = org.bukkit.Bukkit.getPlayerExact(prev); + } + // 3) 接收方被禁言 → 拦(禁言期间任何人不得私信被禁言者) + if (peer != null && chatFilter.isMuted(peer.getName())) { + e.setCancelled(true); + p.sendMessage("§c§l[私信] §f对方 §e" + peer.getName() + " §f已被禁言,暂时无法接收私信"); + return; + } + if (chatFilter.isPlayerWhitelisted(p.getName())) return; - if (filterLinkInContent(p, e.getMessage())) e.setCancelled(true); + if (filterLinkInContent(p, raw)) { + e.setCancelled(true); + return; + } + // 记录会话(供 /r 判断接收方);链接被拦则不记 + if (peer != null) { + lastTellPeer.put(p.getName(), peer.getName()); + lastTellPeer.put(peer.getName(), p.getName()); + } } @EventHandler