第28批:英文聊天机翻双语展示 + 域名过滤按illegal_domains.txt后缀判定修误伤
Check Source / check (push) Successful in 5s
Auto Release / build (push) Successful in 2m29s
Simple Test / test (push) Successful in 1s
Test Env / test (push) Successful in 2s

任务1(新增)英文聊天机翻:
- 所有消息一律不拦截原样放行,仅「整条消息不含中文字符」时异步机翻
- 原始消息由MC正常广播后,追加[机翻]双语展示行
- 中文夹杂英文(如「用water免伤」)原样输出不机翻
- 复用领地系统YOUDAO→JIANXIN→GOOGLE降级链,MaterialTranslator新增
  translateChatText公开入口+独立缓存(上限500)
- 专用2线程池隔离HTTP阻塞,10秒/人冷却防刷,onDisable关闭线程池

任务2(修复)域名过滤误伤:
- extractUrls裸域名token必须命中illegal_domains.txt后缀(含中文后缀)
  才收录,显式协议URL(含://)无条件收录——修「今天.天气真好啊」整句误判
- shouldBlockUrl同步改后缀判定,不再对点分隔无条件拦截
- illegal_domains.txt补全TLD+中文后缀节(工作区遗留扩展一并入库)
This commit is contained in:
youpaishidifu
2026-10-20 11:29:37 +08:00
parent 67f4438f0d
commit 0fdeb88559
4 changed files with 554 additions and 10 deletions
@@ -1038,7 +1038,19 @@ public class ChatFilterManager {
"[\\s.,;!?,。;!?、)》」』】"
+ "\\]\\[\\((]+$",
"");
if (!url.isEmpty()) urls.add(url);
if (url.isEmpty()) continue;
// ★ 28批修复(用户口径「接入illegal_domains.txt,命中的中英文后缀一律拦截」):
// 显式协议URL(含 ://,如 http://1.2.3.4/x、https://a.com)无条件收录;
// 裸域名/下划线 token 必须命中非法域名后缀(含中文后缀:中国/网络/公司…
// 与英文后缀 com/cn/net…,均来自 illegal_domains.txt)才收录。
// —— 修「今天.天气真好啊」被 URL_PATTERN 点分隔支整句匹配为域名而误伤。
if (!url.contains("://")) {
String probe = url;
int slash = probe.indexOf('/');
if (slash > 0) probe = probe.substring(0, slash);
if (!containsIllegalDomain(probe)) continue;
}
if (!urls.contains(url)) urls.add(url);
}
// ★ 变形URL加强(2026-10-08):点周带空白(baidu . com)压缩空白后再判定;
// 仅当顶级域命中「非法域名后缀」才收录,避免误伤 "OK. Good" 之类正常英文句子
@@ -1090,17 +1102,24 @@ public class ChatFilterManager {
/**
* 判断URL是否应被拦截
* 下划线分隔的字符串(如 player_name),必须命中非法域名后缀才拦截
* 点分隔或标准URL正常拦截
* ★ 28批修复(用户口径):接入 illegal_domains.txt,命中的中英文后缀一律拦截,其余放行。
* - 白名单优先放行;
* - 显式协议URL(含 ://,如 http://a.com、http://1.2.3.4/x)非白名单一律拦截
* (IP直链无后缀可查,保持反广告强度);
* - 裸域名/下划线 token(如 xxx.com、xxx_com、今天.天气真好啊):
* 必须命中非法域名后缀(英文 com/cn/net… 或中文 中国/网络/公司…)才拦截 —— 修误伤。
*/
public boolean shouldBlockUrl(String url) {
if (isWhitelisted(url)) return false;
// 下划线分隔 → 检查是否非法域名,不在列表则跳过(合法玩家名下划线误匹配)
if (url.contains("_")) {
return containsIllegalDomain(url);
// 显式协议URL → 非白名单一律拦截(原行为保持)
if (url.contains("://")) {
return true;
}
// 点分隔或标准URL正常拦截
return true;
// 裸域名/下划线token → 必须命中非法域名后缀才拦截
String probe = url;
int slash = probe.indexOf('/');
if (slash > 0) probe = probe.substring(0, slash);
return containsIllegalDomain(probe);
}
public boolean isLikelyDomain(String url) {
+75 -2
View File
@@ -1012,6 +1012,9 @@ public class Main extends JavaPlugin
}
if (db != null) db.close();
if (garbage != null) garbage.close();
if (chatTransPool != null) {
chatTransPool.shutdownNow();
}
if (questTracker != null)
questTracker.shutdown();
if (homeMgr != null)
@@ -6273,7 +6276,11 @@ public class Main extends JavaPlugin
// ===== URL检测 =====
List<String> urls =
chatFilter.extractUrls(msg);
if (urls.isEmpty()) return;
if (urls.isEmpty()) {
// 28批:无链接的正常消息放行 → 触发英文聊天机翻(不拦截,追加双语展示)
tryScheduleChatTranslation(p, msg);
return;
}
// 过滤白名单 + 下划线误匹配(player_name 不在非法域名列表则跳过)
List<String> blocked = new ArrayList<>();
@@ -6282,7 +6289,11 @@ public class Main extends JavaPlugin
blocked.add(url);
}
}
if (blocked.isEmpty()) return;
if (blocked.isEmpty()) {
// 28批:URL 全部放行(白名单/未命中非法后缀)→ 同样触发英文聊天机翻
tryScheduleChatTranslation(p, msg);
return;
}
// 取消消息
e.setCancelled(true);
@@ -6362,6 +6373,68 @@ public class Main extends JavaPlugin
// ==================== 聊天过滤辅助方法 ====================
// ===== 英文聊天机翻(28批新增) =====
/** 同一玩家机翻冷却(毫秒),防刷屏触发大量 API 请求 */
private final java.util.Map<String, Long> chatTransCd = new java.util.concurrent.ConcurrentHashMap<>();
/** 机翻专用线程池(HTTP 阻塞最多 ~9s,不能占用 ForkJoinPool.commonPool) */
private final java.util.concurrent.ExecutorService chatTransPool =
java.util.concurrent.Executors.newFixedThreadPool(2, r -> {
Thread t = new Thread(r, "Sdf1-chat-translator");
t.setDaemon(true);
return t;
});
/**
* 英文聊天机翻调度(28批,用户定稿口径):
* - 所有消息一律不拦截、原样放行(交 MC 自行处理),本方法只做「追加双语展示」;
* - 仅「整条消息不含任何中文字符」的英文消息(如 what are you doing?)才机翻,
* 原始消息由 MC 正常广播之后,再追加一条「[机翻] 原文 → 译文」;
* - 中文消息或中文夹杂英文(如「你需要用water来免伤」)一律原样输出、不做机翻
* —— 脱离第三方模型无法判定英文单词在语境中的真实含义,强行翻反而出错。
* 异步:HTTP 翻译在线程池执行(每源3s超时,YOUDAO→JIANXIN→GOOGLE 降级),
* 结果回主线程广播;任何异常静默吞掉,绝不影响聊天主流程。
*/
private void tryScheduleChatTranslation(Player p, String msg) {
try {
if (p == null || msg == null) return;
String text = msg.trim();
if (text.isEmpty() || text.length() > 200) return;
// 整条消息必须「不含任何中文字符」才机翻(含中文=原样输出)
boolean hasChinese = false;
for (int i = 0; i < text.length(); i++) {
char c = text.charAt(i);
if (c >= '\u4e00' && c <= '\u9fff') { hasChinese = true; break; }
}
if (hasChinese) return;
// 至少要有一个英文字母(纯数字/符号无翻译意义)
boolean hasLetter = false;
for (int i = 0; i < text.length(); i++) {
char c = text.charAt(i);
if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) { hasLetter = true; break; }
}
if (!hasLetter) return;
// 冷却防刷(10秒/人)
long now = System.currentTimeMillis();
Long last = chatTransCd.get(p.getName());
if (last != null && now - last < 10_000L) return;
chatTransCd.put(p.getName(), now);
final String original = text;
final String sender = p.getName();
java.util.concurrent.CompletableFuture.supplyAsync(
() -> MaterialTranslator.translateChatText(original), chatTransPool)
.thenAccept(zh -> {
if (zh == null || zh.isEmpty()) return;
// 切主线程追加「机翻」双语展示(原始消息已由 MC 自行广播)
Bukkit.getScheduler().runTask(this, () ->
Bukkit.broadcastMessage("§8[机翻] §7" + sender + ": §f"
+ original + " §7→ §a" + zh));
});
} catch (Throwable ignored) {
// 机翻任何异常都不影响聊天
}
}
/**
* 过滤内容中的链接(用于告示牌、编辑书等非聊天场景)
* @return 是否有违规链接
@@ -309,6 +309,35 @@ public class MaterialTranslator {
*/
public static void clearCache() {
cache.clear();
chatCache.clear();
}
// ==================== 聊天整句机翻(28批新增) ====================
/** 聊天机翻独立缓存(与材料名缓存隔离),上限保护防内存膨胀 */
private static final ConcurrentHashMap<String, String> chatCache = new ConcurrentHashMap<>();
private static final int CHAT_CACHE_MAX = 500;
/**
* 聊天整句机翻(英→中)。复用 translateWithFallback 的
* YOUDAO→JIANXIN→GOOGLE 降级链(与领地系统同一套 API 流程)。
* 结果独立缓存(上限500条,满则整体清空重来),不污染材料名缓存。
* 同步阻塞 HTTP(每源 3s 超时),调用方必须放在异步线程执行。
* 返回 null 表示翻译失败或译文无意义(如与原文相同)。
*/
public static String translateChatText(String text) {
if (text == null) return null;
String key = text.trim();
if (key.isEmpty()) return null;
String hit = chatCache.get(key);
if (hit != null) return hit;
String zh = translateWithFallback(key);
if (zh != null && !zh.isEmpty() && !zh.equals(key)) {
if (chatCache.size() >= CHAT_CACHE_MAX) chatCache.clear();
chatCache.put(key, zh);
return zh;
}
return null;
}
/**
+423
View File
@@ -33,3 +33,426 @@ party
fun
zone
space
name
pro
aero
coop
museum
jobs
travel
asia
cat
edu
gov
int
mil
ac
ad
ae
af
ag
ai
al
am
ao
aq
ar
as
at
au
aw
ax
az
ba
bb
bd
be
bf
bg
bh
bi
bj
bm
bn
bo
br
bs
bt
bw
by
bz
ca
cd
cg
ch
ci
ck
cl
cm
cr
cu
cv
cy
cz
de
dj
dk
dm
do
dz
ec
ee
eg
er
es
et
eu
fi
fj
fk
fm
fo
fr
gd
ge
gf
gg
gh
gi
gl
gm
gn
gp
gq
gr
gs
gt
gu
gw
gy
hk
hn
hr
ht
hu
id
ie
il
im
in
iq
ir
is
it
je
jm
jo
jp
ke
kg
kh
ki
km
kn
kp
kr
kw
ky
kz
la
lb
lc
li
lk
lr
ls
lt
lu
lv
ly
ma
mc
md
me
mg
mh
mk
mm
mn
mo
mp
mq
mr
ms
mt
mu
mv
mw
mx
my
mz
na
nc
ne
nf
ng
ni
nl
no
np
nr
nu
nz
om
pa
pe
pf
pg
ph
pk
pl
pm
pn
pr
ps
pt
pw
py
qa
re
ro
rs
rw
sa
sb
sc
sd
se
sg
sh
si
sk
sl
sm
sn
so
sr
ss
st
sv
sx
sy
sz
tc
td
tf
tg
th
tj
tl
tm
tn
to
tt
tw
tz
ug
uk
um
us
uy
uz
va
vc
ve
vg
vi
vn
vu
wf
ye
yt
za
zm
zw
ac.uk
co.uk
org.uk
gov.uk
edu.au
ac.nz
co.nz
org.nz
gov.au
co.jp
ne.jp
or.jp
ed.jp
ac.jp
go.jp
co.kr
or.kr
re.kr
go.kr
ac.kr
ne.kr
com.cn
net.cn
org.cn
gov.cn
edu.cn
ac.cn
mil.cn
com.hk
net.hk
org.hk
idv.hk
com.tw
net.tw
org.tw
gov.tw
edu.tw
com.sg
net.sg
org.sg
gov.sg
edu.sg
per.sg
com.my
net.my
org.my
gov.my
edu.my
per.my
com.vn
net.vn
org.vn
gov.vn
edu.vn
ac.vn
com.ph
net.ph
org.ph
gov.ph
edu.ph
com.pk
net.pk
org.pk
gov.pk
edu.pk
com.bd
net.bd
org.bd
gov.bd
edu.bd
com.lk
net.lk
gov.lk
ac.lk
com.np
net.np
org.np
gov.np
com.mm
com.kh
com.kg
com.kz
com.ge
com.am
com.az
com.by
com.km
com.sc
co.id
web.id
co.th
or.th
ac.th
go.th
net.th
com.mu
org.mu
co.za
org.za
web.za
com.eg
net.eg
com.ma
net.ma
org.ma
co.ke
or.ke
ac.ke
co.tz
or.tz
ac.tz
co.ug
or.ug
com.sn
net.sn
org.sn
co.rw
ac.rw
com.cm
co.bi
co.dz
com.dj
co.so
co.tg
co.np
ac.cy
com.cy
co.il
org.il
net.il
ac.il
gov.il
co.ma
com.kw
net.kw
org.kw
com.om
net.om
com.ps
com.sy
com.jo
net.jo
com.lb
net.lb
com.tn
com.tj
com.tf
app
dev
page
shop
work
website
wiki
blog
live
best
cool
red
icu
one
ltd
# ===== 中文域名(真实可注册,按最后一段匹配)=====
# ICANN 官方中文 TLD
中国
网络
公司
我爱你
# .cn 注册局中文通用域名
集团
商城
网店
在线
游戏
网址
网站
商店
企业
娱乐
移动
中文网