第41批收尾:正则层残留 Unicode 转义全部字面化(可见字符写字面量/不可见字符用具名常量)
Check Source / check (push) Successful in 5s
Auto Release / build (push) Successful in 2m5s
Simple Test / test (push) Successful in 2s
Test Env / test (push) Successful in 1s

This commit is contained in:
youpaishidifu
2026-10-22 13:05:46 +08:00
parent 855e45cb7f
commit a5cb5afba7
6 changed files with 81 additions and 53 deletions
+2 -2
View File
@@ -1301,8 +1301,8 @@ public class AreaProtection implements Listener {
List<String> result = new ArrayList<>();
if (s == null || s.isEmpty()) return result;
String trimmed = s.trim();
// ★ 支持JSON数组格式(PHP端存储格式),如 ["速度","急迫"] 或 ["\\u8bd5\\u70bc..."]
// (PHP 端 JSON 存储时中文会被转义成 \\uXXXX 形式,此处展示的是 PHP 侧原样)
// ★ 支持JSON数组格式(PHP端存储格式),如 ["速度","急迫"]
// (PHP 端 JSON 存储时中文会被转义成 U+XXXX 形式,例如 "试" 对应 U+8BD5,此处展示的是 PHP 侧原样)
if (trimmed.startsWith("[") && trimmed.endsWith("]")) {
try {
String inner = trimmed.substring(1, trimmed.length() - 1);
@@ -689,7 +689,9 @@ public class ChatFilterManager {
/** 点周带空白的变形域名: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,}");
"(?i)(?:[\\p{L}0-9](?:[\\p{L}0-9\\-]*[\\p{L}0-9])?[\\s"
+ UnicodeChars.IDEOGRAPHIC_SPACE_TEXT + "]*\\.)+[\\s"
+ UnicodeChars.IDEOGRAPHIC_SPACE_TEXT + "]*[\\p{L}]{2,}");
private static final Pattern COLOR_CODES =
Pattern.compile(
"§[0-9a-fk-orA-FK-OR]");
+2 -2
View File
@@ -181,7 +181,7 @@ public class DurationParser {
/** 中文日期+时间:2026年10月8日 14:43(“日”已界定日期边界,时间可紧贴) */
private static final Pattern DT_CN = Pattern.compile(
"(\\d{4})年(\\d{1,2})月(\\d{1,2})日(?:[ T\\u3000]+)?"
"(\\d{4})年(\\d{1,2})月(\\d{1,2})日(?:[ T" + UnicodeChars.IDEOGRAPHIC_SPACE_TEXT + "]+)?"
+ "(\\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
@@ -189,7 +189,7 @@ public class DurationParser {
* 防止 2026-1-8 14:43 被粘连误读成「日=81、时=4」 */
private static final Pattern DT_SEP = Pattern.compile(
"(?<!\\d)(\\d{4}|\\d{2})[-/.](\\d{1,2})[-/.](\\d{1,2})"
+ "[ T\\u3000]+(\\d{1,2}):(\\d{2})(?::(\\d{2}))?(?!\\d|[A-Za-z])");
+ "[ T" + UnicodeChars.IDEOGRAPHIC_SPACE_TEXT + "]+(\\d{1,2}):(\\d{2})(?::(\\d{2}))?(?!\\d|[A-Za-z])");
/** 两位年份无时间:26-10-08
* lookbehind 防止吃掉 4 位年份的后半段(2026-07-09 不能被读成 07-09) */
@@ -69,7 +69,8 @@ public class MaintenanceManager implements Listener {
"&c&l[服务器维护] &7服务器正在维护中,请稍后再试&7(结束时间: &e{结束时间}&7)";
private static final Pattern NUM_PAT = Pattern.compile("\\d+");
private static final Pattern DUR_PAT = Pattern.compile("(\\d+(?:\\.\\d+)?)\\s*([a-zA-Z\\u4e00-\\u9fa5]*)");
/** 时长单位正则:单位部分匹配「字母 + 汉字区(一~龥)」,例:10秒 / 5分 / 2小时 */
private static final Pattern DUR_PAT = Pattern.compile("(\\d+(?:\\.\\d+)?)\\s*([a-zA-Z一-龥]*)");
private static final Pattern AMP_COLOR_PAT = Pattern.compile("&([0-9a-fk-orA-FK-OR])");
private final Main plugin;
@@ -701,8 +702,7 @@ public class MaintenanceManager implements Listener {
String body = s.replaceAll("^[=\\-_::·*#\\s]+|[=\\-_::·*#\\s]+$", "");
if (body.indexOf('=') >= 0) return false;
// 剥离各类括号与装饰符号(含全角【】〔〕「」『』()),只留标题正文
String stripped = s.replaceAll("[\\[\\]\\u3010\\u3011\\u3014\\u3015\\u300c\\u300d\\u300e\\u300f"
+ "\\uff08\\uff09()\\-=_::·*#\\s]+", "");
String stripped = s.replaceAll("[\\[\\]【】〔〕「」『』()()\\-=_::·*#\\s]+", "");
String low = stripped.toLowerCase(Locale.ROOT);
// 精确匹配常见写法;再兜底 endsWith 兼容「玩家白名单」「ALLOWLIST」这类变体标题
//(有了上面的 = 门槛,endsWith 已经不会再吞掉控制段键值对)
@@ -781,7 +781,7 @@ public class MaintenanceManager implements Listener {
s = s.substring(0, c).trim();
}
// 列表装饰前缀:- > * · •(合法 MC 名不会以这些字符开头)
String noDecor = s.replaceFirst("^[-*>\\u00b7\\u2022]\\s+", "");
String noDecor = s.replaceFirst("^[-*>·•]\\s+", "");
if (!noDecor.equals(s)) {
warn(log, "白名单条目前缀符号已忽略: [" + s + "] -> [" + noDecor + "]");
s = noDecor;
@@ -801,7 +801,7 @@ public class MaintenanceManager implements Listener {
/** 去掉肉眼看不见却会让匹配失败的字符:零宽/BOM/不间断空格/全角空格 */
private static String stripInvisible(String s) {
if (s == null) return null;
return s.replaceAll("[\\u200B-\\u200D\\u2060\\uFEFF\\u00A0\\u3000]", "");
return s.replaceAll(UnicodeChars.INVISIBLE_STRIP_CLASS, "");
}
/** 行内注释起点(# / # / //),返回 -1 表示没有 */
+65 -42
View File
@@ -3,8 +3,8 @@ package Sdf1_login;
/**
* Unicode 特殊字符常量表。
*
* <p>背景:项目里原先大量使用 {@code \\uXXXX} 形式的 Unicode 转义字面量,
* 读代码的人必须逐个心算码位才能看懂(例如 {@code \\u00a7} 其实是 MC 颜色码 §)。
* <p>背景:项目里原先大量使用 Unicode 转义字面量(写成 U+XXXX 形式),
* 读代码的人必须逐个心算码位才能看懂(例如 U+00A7 其实是 MC 颜色码 §)。
* 尤其不可见字符(零宽空格、BOM、全角空格…)直接写成字面量后
* 在编辑器里根本"看不见",反而更难排查。</p>
*
@@ -16,9 +16,10 @@ package Sdf1_login;
* 统一引用本类的具名常量,既可读又不会因"看不见"而误判。</li>
* </ul>
*
* <p>注意:正则表达式字符串里的 {@code \\uXXXX} 属于<b>正则层</b>转义
* <p>注意:正则表达式字符串里的 Unicode 转义属于<b>正则层</b>转义
* (Java 字符串里存的是字面反斜杠+u,由正则引擎在运行期解释),
* 改写会改变正则语义,故保持原样不动。</p>
* 改写会改变正则语义。本项目已把正则层里<b>可见</b>的转义全部改写成
* 真实字符或本类的具名常量;<b>不可见</b>的转义则通过本类常量参与拼接。</p>
*/
public final class UnicodeChars {
@@ -75,118 +76,118 @@ public final class UnicodeChars {
// ==================== 区间常量(不可见/边界判定用) ====================
/** CJK 基本汉字区起点 U+4E00(一) */
public static final char CJK_START = '\u4E00';
public static final char CJK_START = '一';
/** CJK 基本汉字区终点 U+9FFF */
public static final char CJK_END = '\u9FFF';
public static final char CJK_END = '鿿';
/** CJK 兼容汉字常用终点 U+9FA5(龥) */
public static final char CJK_COMPAT_END = '\u9FA5';
public static final char CJK_COMPAT_END = '龥';
/** CJK 扩展 A 区起点 U+3400(㐀) */
public static final char CJK_EXT_A_START = '\u3400';
public static final char CJK_EXT_A_START = '㐀';
/** CJK 扩展 A 区终点 U+4DBF(䶿) */
public static final char CJK_EXT_A_END = '\u4DBF';
public static final char CJK_EXT_A_END = '䶿';
/** 全角 ASCII 起点 U+FF01(!) */
public static final char FULLWIDTH_START = '\uFF01';
public static final char FULLWIDTH_START = '!';
/** 全角 ASCII 终点 U+FF5E(~) */
public static final char FULLWIDTH_END = '\uFF5E';
public static final char FULLWIDTH_END = '~';
/** 全角数字 0 U+FF10 */
public static final char FULLWIDTH_ZERO = '\uFF10';
public static final char FULLWIDTH_ZERO = '0';
/** 全角数字 9 U+FF19 */
public static final char FULLWIDTH_NINE = '\uFF19';
public static final char FULLWIDTH_NINE = '9';
/** 全角大写 A U+FF21 */
public static final char FULLWIDTH_A_UPPER = '\uFF21';
public static final char FULLWIDTH_A_UPPER = 'A';
/** 全角大写 Z U+FF3A */
public static final char FULLWIDTH_Z_UPPER = '\uFF3A';
public static final char FULLWIDTH_Z_UPPER = 'Z';
/** 全角小写 a U+FF41 */
public static final char FULLWIDTH_A_LOWER = '\uFF41';
public static final char FULLWIDTH_A_LOWER = 'a';
/** 全角小写 z U+FF5A */
public static final char FULLWIDTH_Z_LOWER = '\uFF5A';
public static final char FULLWIDTH_Z_LOWER = 'z';
/** 全角左括号 U+FF08 */
public static final char FULLWIDTH_LPAREN = '\uFF08';
public static final char FULLWIDTH_LPAREN = '(';
/** 全角右括号 U+FF09 */
public static final char FULLWIDTH_RPAREN = '\uFF09';
public static final char FULLWIDTH_RPAREN = ')';
/** 全角井号 U+FF03 */
public static final char FULLWIDTH_HASH = '\uFF03';
public static final char FULLWIDTH_HASH = '#';
/** 全角逗号 U+FF0C */
public static final char FULLWIDTH_COMMA = '\uFF0C';
public static final char FULLWIDTH_COMMA = ',';
/** 全角分号 U+FF1B */
public static final char FULLWIDTH_SEMICOLON = '\uFF1B';
public static final char FULLWIDTH_SEMICOLON = ';';
/** 全角冒号 U+FF1A */
public static final char FULLWIDTH_COLON = '\uFF1A';
public static final char FULLWIDTH_COLON = ':';
/** 全角句点 U+FF0E */
public static final char FULLWIDTH_STOP = '\uFF0E';
public static final char FULLWIDTH_STOP = '.';
/** 全角波浪号 U+FF5E */
public static final char FULLWIDTH_TILDE = '\uFF5E';
public static final char FULLWIDTH_TILDE = '~';
/** 半角片假名中点 U+FF65 */
public static final char HALFWIDTH_KATAKANA_MIDDLE_DOT = '\uFF65';
public static final char HALFWIDTH_KATAKANA_MIDDLE_DOT = '・';
/** 小型句点 U+FE52 */
public static final char SMALL_FULL_STOP = '\uFE52';
public static final char SMALL_FULL_STOP = '﹒';
/** 二点引导符 U+2025 */
public static final char TWO_DOT_LEADER = '\u2025';
public static final char TWO_DOT_LEADER = '‥';
/** 水平省略号 U+2026 */
public static final char HORIZONTAL_ELLIPSIS = '\u2026';
public static final char HORIZONTAL_ELLIPSIS = '…';
/** 中点 U+00B7 */
public static final char MIDDLE_DOT = '\u00B7';
public static final char MIDDLE_DOT = '·';
/** 项目符号 U+2022 */
public static final char BULLET = '\u2022';
public static final char BULLET = '•';
/** 表意句点 U+3002 */
public static final char IDEOGRAPHIC_STOP = '\u3002';
public static final char IDEOGRAPHIC_STOP = '。';
/** 表意逗号 U+3001 */
public static final char IDEOGRAPHIC_COMMA = '\u3001';
public static final char IDEOGRAPHIC_COMMA = '、';
/** 表意数字零 U+3007(〇) */
public static final char IDEOGRAPHIC_ZERO = '\u3007';
public static final char IDEOGRAPHIC_ZERO = '〇';
/** 左黑 lenticular 方括号 U+3010(【) */
public static final char LEFT_BLACK_LENTICULAR = '\u3010';
public static final char LEFT_BLACK_LENTICULAR = '【';
/** 右黑 lenticular 方括号 U+3011(】) */
public static final char RIGHT_BLACK_LENTICULAR = '\u3011';
public static final char RIGHT_BLACK_LENTICULAR = '】';
/** 左 tortoise shell 括号 U+3014(〔) */
public static final char LEFT_TORTOISE_SHELL = '\u3014';
public static final char LEFT_TORTOISE_SHELL = '〔';
/** 右 tortoise shell 括号 U+3015(〕) */
public static final char RIGHT_TORTOISE_SHELL = '\u3015';
public static final char RIGHT_TORTOISE_SHELL = '〕';
/** 左直角引号 U+300C(「) */
public static final char LEFT_CORNER_BRACKET = '\u300C';
public static final char LEFT_CORNER_BRACKET = '「';
/** 右直角引号 U+300D」(」) */
public static final char RIGHT_CORNER_BRACKET = '\u300D';
public static final char RIGHT_CORNER_BRACKET = '」';
/** 左白直角引号 U+300E(『) */
public static final char LEFT_WHITE_CORNER_BRACKET = '\u300E';
public static final char LEFT_WHITE_CORNER_BRACKET = '『';
/** 右白直角引号 U+300F(』) */
public static final char RIGHT_WHITE_CORNER_BRACKET = '\u300F';
public static final char RIGHT_WHITE_CORNER_BRACKET = '』';
// ==================== 常用字符串常量 ====================
@@ -214,4 +215,26 @@ public final class UnicodeChars {
* 含:零宽空格、零宽非连接、零宽连接、字连接符、BOM、软连字符。
*/
public static final String INVISIBLE_CHARS = "\u200B\u200C\u200D\u2060\uFEFF\u00AD";
/** U+200B 零宽空格的字符串版 */
public static final String ZERO_WIDTH_SPACE_TEXT = "\u200B";
/** U+200D 零宽连接符的字符串版 */
public static final String ZERO_WIDTH_JOINER_TEXT = "\u200D";
/** U+2060 字连接符的字符串版 */
public static final String WORD_JOINER_TEXT = "\u2060";
/** U+00A0 不间断空格的字符串版 */
public static final String NBSP_TEXT = "\u00A0";
/**
* 需要剔除的不可见字符(正则字符类形式,净化玩家输入用)。
* 含:零宽空格~零宽连接(区间)、字连接符、BOM、不间断空格、全角空格。
* <p>这里用具名常量拼接,而不是转义写法——这样源码里能直接看出
* 每个字符的用途,读代码的人不必去心算码位。</p>
*/
public static final String INVISIBLE_STRIP_CLASS =
"[" + ZERO_WIDTH_SPACE_TEXT + "-" + ZERO_WIDTH_JOINER_TEXT
+ WORD_JOINER_TEXT + BOM_TEXT + NBSP_TEXT + IDEOGRAPHIC_SPACE_TEXT + "]";
}
+4 -1
View File
@@ -3990,6 +3990,8 @@ public class WebManager {
* 解码JSON中的 Unicode转义序列
*/
private static String decodeUnicodeEscapes(String str) {
// 这里的 "\\u" 是「反斜杠 + u」两个字符(PHP 端 JSON 的转义标记),
// 不是 Java 的 Unicode 转义——探测原始字符串里是否带转义标记。
if (str == null || !str.contains("\\u")) return str;
try {
StringBuilder sb = new StringBuilder(str.length());
@@ -6000,9 +6002,10 @@ public class WebManager {
}
}
/** 解码JSON中的 Unicode 转义(\\uXXXX 形式,如 \\u00a7 → §) */
/** 解码JSON中的 Unicode 转义(U+XXXX 形式,如 U+00A7 即 §) */
private String decodeJsonUnicode(String s) {
if (s == null || s.isEmpty()) return s;
// 同上:这里探测的是 JSON 里的反斜杠+u 转义标记,不是 Java Unicode 转义。
if (!s.contains("\\u")) return s;
StringBuilder sb = new StringBuilder(s.length());
for (int i = 0; i < s.length(); i++) {