Skip to content

Commit cff2c03

Browse files
authored
Merge pull request #569 from XyLuoDYS/feat/AntiRepeat
[2.5.1] 完善消息拦截系统
2 parents 7a8421f + f50f7d7 commit cff2c03

9 files changed

Lines changed: 150 additions & 6 deletions

File tree

‎gradle.properties‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
group=me.arasple.mc.trchat
2-
version=2.5.0
2+
version=2.5.1
33
kotlin.incremental=true
44
kotlin.incremental.java=true
55
kotlin.caching.enabled=true

‎project/runtime-bukkit/src/main/kotlin/me/arasple/mc/trchat/module/conf/file/Settings.kt‎

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,32 @@ object Settings {
3636
@ConfigNode("Chat.Cooldown", "settings.yml")
3737
val chatCooldown = ConfigNodeTransfer<String, Long> { parseMillis() }
3838

39-
@ConfigNode("Chat.Anti-Repeat", "settings.yml")
39+
@ConfigNode("Chat.Interception.Anti-Repeat.Similarity", "settings.yml")
4040
var chatSimilarity = 0.85
4141
private set
4242

43+
@ConfigNode("Chat.Interception.Anti-Repeat.Max-Per-Period", "settings.yml")
44+
var chatSimilarityMaxPerPeriod = 0
45+
private set
46+
47+
@ConfigNode("Chat.Interception.Anti-Repeat.Period", "settings.yml")
48+
val chatSimilarityPeriod = ConfigNodeTransfer<String, Long> { parseMillis() }
49+
50+
@ConfigNode("Chat.Interception.Anti-High-frequency.Max-Per-Period", "settings.yml")
51+
var chatHighFrequencyMaxPerPeriod = 0
52+
private set
53+
54+
@ConfigNode("Chat.Interception.Anti-High-frequency.Period", "settings.yml")
55+
val chatHighFrequencyPeriod = ConfigNodeTransfer<String, Long> { parseMillis() }
56+
57+
@ConfigNode("Chat.Interception.Anti-Duplicate-Phrase.Max-Repeat", "settings.yml")
58+
var chatDuplicatePhraseMaxRepeat = 0
59+
private set
60+
61+
@ConfigNode("Chat.Interception.Anti-Duplicate-Phrase.Whitelist", "settings.yml")
62+
var chatDuplicatePhraseWhitelist = emptyList<String>()
63+
private set
64+
4365
@ConfigNode("Chat.Length-Limit", "settings.yml")
4466
var chatLengthLimit = 100
4567
private set

‎project/runtime-bukkit/src/main/kotlin/me/arasple/mc/trchat/module/display/ChatSession.kt‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ class ChatSession(val player: Player) {
2626
var lastChannel: Channel? = null
2727
var lastPublicMessage = ""
2828
var lastPrivateMessage = ""
29+
var similarCountInPeriod = 0
30+
var similarPeriodStart = 0L
31+
var totalCountInPeriod = 0
32+
var totalPeriodStart = 0L
2933
var lastPrivateTo = ""
3034
var cancelChat = false
3135
@JvmField

‎project/runtime-bukkit/src/main/kotlin/me/arasple/mc/trchat/module/display/channel/Channel.kt‎

Lines changed: 90 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -280,10 +280,30 @@ open class Channel(
280280
}
281281
}
282282
if (!player.hasPermission("trchat.bypass.repeat")) {
283-
val lastMessage = player.session.lastPublicMessage
283+
val session = player.session
284+
val lastMessage = session.lastPublicMessage
284285
if (Settings.chatSimilarity > 0 && Settings.chatSimilarity <= 1 && Strings.similarDegree(lastMessage, message) >= Settings.chatSimilarity) {
285-
player.sendLang("General-Too-Similar")
286-
return false
286+
val period = Settings.chatSimilarityPeriod.get()?.takeIf { it > 0 } ?: 60000L
287+
val now = System.currentTimeMillis()
288+
if (session.similarPeriodStart == 0L || now - session.similarPeriodStart >= period) {
289+
session.similarPeriodStart = now
290+
session.similarCountInPeriod = 0
291+
}
292+
if (session.similarCountInPeriod >= Settings.chatSimilarityMaxPerPeriod) {
293+
player.sendLang("General-Too-Similar")
294+
return false
295+
}
296+
session.similarCountInPeriod++
297+
}
298+
}
299+
if (!player.hasPermission("trchat.bypass.duplicate")) {
300+
val maxRepeat = Settings.chatDuplicatePhraseMaxRepeat
301+
if (maxRepeat > 0 && message.length >= 2) {
302+
val whitelist = Settings.chatDuplicatePhraseWhitelist.toSet()
303+
if (maxConsecutiveRepeat(message, whitelist) > maxRepeat) {
304+
player.sendLang("General-Too-Duplicate")
305+
return false
306+
}
287307
}
288308
}
289309
if (!player.hasPermission("trchat.bypass.chatcd")) {
@@ -296,6 +316,24 @@ open class Channel(
296316
if (Function.functions.any { !it.checkCooldown(player, message) }) {
297317
return false
298318
}
319+
// 消息发送频率检查
320+
if (!player.hasPermission("trchat.bypass.highfrequency")) {
321+
val max = Settings.chatHighFrequencyMaxPerPeriod
322+
if (max > 0) {
323+
val session = player.session
324+
val period = Settings.chatHighFrequencyPeriod.get()?.takeIf { it > 0 } ?: 60000L
325+
val now = System.currentTimeMillis()
326+
if (session.totalPeriodStart == 0L || now - session.totalPeriodStart >= period) {
327+
session.totalPeriodStart = now
328+
session.totalCountInPeriod = 0
329+
}
330+
if (session.totalCountInPeriod >= max) {
331+
player.sendLang("General-Too-Frequent")
332+
return false
333+
}
334+
session.totalCountInPeriod++
335+
}
336+
}
299337
player.updateCooldown(CooldownType.CHAT, Settings.chatCooldown.get())
300338
return true
301339
}
@@ -347,4 +385,53 @@ open class Channel(
347385
}
348386
}
349387
}
388+
}
389+
390+
// 检查重复的子字符串并统计
391+
private fun maxConsecutiveRepeat(message: String, whitelist: Set<String>): Int {
392+
val n = message.length
393+
if (n < 2) return 1
394+
var max = 1
395+
val checkWhitelist = whitelist.isNotEmpty()
396+
var i = 0
397+
while (i < n) {
398+
val maxLen = (n - i) / 2
399+
var len = 1
400+
while (len <= maxLen) {
401+
if (checkWhitelist && isWhitelistedUnit(message, i, len, whitelist)) {
402+
len++
403+
continue
404+
}
405+
var count = 1
406+
var j = i + len
407+
while (j + len <= n && message.regionMatches(j, message, j - len, len)) {
408+
count++
409+
j += len
410+
}
411+
if (count > max) max = count
412+
len++
413+
}
414+
i++
415+
}
416+
return max
417+
}
418+
419+
// 判断重复单元是否被白名单豁免
420+
private fun isWhitelistedUnit(message: String, start: Int, len: Int, whitelist: Set<String>): Boolean {
421+
for (w in whitelist) {
422+
val wl = w.length
423+
if (wl == 0 || len < wl || len % wl != 0) continue
424+
val repeat = len / wl
425+
var ok = true
426+
var k = 0
427+
while (k < repeat) {
428+
if (!message.regionMatches(start + k * wl, w, 0, wl)) {
429+
ok = false
430+
break
431+
}
432+
k++
433+
}
434+
if (ok) return true
435+
}
436+
return false
350437
}

‎project/runtime-bukkit/src/main/resources/lang/en_US.yml‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ Plugin-Failed: '&8[&3Tr&bChat&8] &cOperation failure.'
4242

4343
General-Too-Long: '&8[&3Tr&bChat&8] &7Message is too long. &8[&6{0}&8/&2{1}&8]'
4444
General-Too-Similar: '&8[&3Tr&bChat&8] &7Please do not repeat your message.'
45+
General-Too-Frequent: '&8[&3Tr&bChat&8] &7You are sending messages too frequently, please slow down.'
46+
General-Too-Duplicate: '&8[&3Tr&bChat&8] &7Your message contains consecutively repeated phrases, please do not spam.'
4547
General-No-Permission: '&8[&3Tr&bChat&8] &7You do not have enough permission to do this.'
4648
General-Muted: '&8[&3Tr&bChat&8] &cYou have been muted until {0}!Reason: {1}'
4749
General-Cancel-Muted: '&8[&3Tr&bChat&8] &aYou can speak now.'

‎project/runtime-bukkit/src/main/resources/lang/es_ES.yml‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ Plugin-Failed: '&8[&3Tr&bChat&8] &cOperación fallida.'
4242

4343
General-Too-Long: '&8[&3Tr&bChat&8] &7El mensaje enviado es muy largo. &8[&6{0}&8/&2{1}&8]'
4444
General-Too-Similar: '&8[&3Tr&bChat&8] &7Porfavor no repitas el mismo mensaje.'
45+
General-Too-Frequent: '&8[&3Tr&bChat&8] &7Estás enviando mensajes con demasiada frecuencia, por favor espera.'
46+
General-Too-Duplicate: '&8[&3Tr&bChat&8] &7Tu mensaje contiene frases repetidas consecutivamente, por favor no hagas spam.'
4547
General-No-Permission: '&8[&3Tr&bChat&8] &7No tienes permisos para realizar esto.'
4648
General-Muted: '&8[&3Tr&bChat&8] &cFuiste silenciado por {0}! Razón: {1}'
4749
General-Cancel-Muted: '&8[&3Tr&bChat&8] &aYa puedes escribir.'

‎project/runtime-bukkit/src/main/resources/lang/zh_CN.yml‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ Plugin-Failed: '&8[&3Tr&bChat&8] &c操作失败.'
4545

4646
General-Too-Long: '&8[&3Tr&bChat&8] &7你的聊天内容过长. &8[&6{0}&8/&2{1}&8]'
4747
General-Too-Similar: '&8[&3Tr&bChat&8] &7你的两次聊天内容过于相似, 请勿复读.'
48+
General-Too-Frequent: '&8[&3Tr&bChat&8] &7你发送消息的频率过高, 请稍后再试.'
49+
General-Too-Duplicate: '&8[&3Tr&bChat&8] &7你发送的内容中存在连续重复的词组, 请勿刷屏.'
4850
General-No-Permission: '&8[&3Tr&bChat&8] &7你没有足够的权限执行此操作.'
4951
General-Muted: '&8[&3Tr&bChat&8] &c你被禁言了!解除时间: {0} 原因: {1}'
5052
General-Cancel-Muted: '&8[&3Tr&bChat&8] &a你的禁言已被解除.'

‎project/runtime-bukkit/src/main/resources/settings.yml‎

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,31 @@ Redis:
3636
timeout: 1000
3737

3838
Chat:
39-
Anti-Repeat: 0.85
39+
Interception:
40+
Anti-Repeat:
41+
#内容相似度防刷屏
42+
Similarity: 0.85
43+
#每个周期内(下面的Period配置项)允许发送的相似消息最大条数,0表示任何时间完全禁止连续发送相似消息
44+
Max-Per-Period: 0
45+
#计数周期
46+
Period: '1m'
47+
Anti-High-frequency:
48+
#每个周期内(下面的Period配置项)允许发送的消息最大条数,0表示无消息发送频率限制
49+
Max-Per-Period: 0
50+
#计数周期
51+
Period: '1m'
52+
Anti-Duplicate-Phrase:
53+
#句内连续重复词组允许的最大次数,超过则拦截,0表示禁用此检测
54+
#例如填2时,发送"加我微信加我微信"时放行, "加我微信加我微信加我微信"时拦截
55+
Max-Repeat: 0
56+
#词组白名单,其中的词组无论连续重复多少次都不拦截
57+
Whitelist:
58+
- '哈'
59+
- '6'
60+
- '?'
61+
- '?'
62+
- '!'
63+
- '!'
4064
Cooldown: '2.0s'
4165
Length-Limit: 100
4266
Permission-Check:

‎project/runtime-bukkit/src/main/resources/special-chars.yml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
# 在频道配置文件启用special-char功能,玩家发出以下字符都会变成特定颜色
2+
# 注意更新频道配置文件的配置,如果不使用此功能可忽略
23

34
SpecialChars: []
45

0 commit comments

Comments
 (0)