Skip to content

Commit 0e301eb

Browse files
committed
[ChatAntiSpamModule] Add consecutiveUnfilteredThreshold config
1 parent 1cb0a19 commit 0e301eb

File tree

4 files changed

+46
-2
lines changed

4 files changed

+46
-2
lines changed

bukkit/src/main/kotlin/io/github/rothes/esu/bukkit/module/ChatAntiSpamModule.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,11 +180,17 @@ object ChatAntiSpamModule: BukkitModule<ChatAntiSpamModule.ModuleConfig, ChatAnt
180180
get() = parsed("prefix", localed(locale) { this.prefix })
181181

182182
data class ModuleConfig(
183+
@Comment("Enable to notify console anti-spam messages")
183184
val notifyConsole: Boolean = true,
184185
val userDataExpiresAfter: Duration = 20.minutes.toJavaDuration(),
185186
val baseMuteDuration: Duration = 10.minutes.toJavaDuration(),
186187
val expireSize: ExpireSize = ExpireSize(),
187188
val expireTime: ExpireTime = ExpireTime(),
189+
@Comment("""
190+
Remove one oldest filtered record if the player sends multiple unfiltered chat message in a row.
191+
Set to non-positive value to disable this, and record only expires after configured time above.
192+
""")
193+
val consecutiveUnfilteredThreshold: Int = 3,
188194
val muteHandler: MuteHandler = MuteHandler(),
189195
val spamCheck: DefaultedEnumMap<MessageType, SpamCheck> =
190196
DefaultedEnumMap(MessageType::class.java, SpamCheck()).apply {

bukkit/src/main/kotlin/io/github/rothes/esu/bukkit/module/chatantispam/CasListeners.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import org.bukkit.event.player.AsyncPlayerChatEvent
3333
import org.bukkit.event.player.PlayerCommandPreprocessEvent
3434
import org.bukkit.event.player.PlayerJoinEvent
3535
import org.bukkit.event.player.PlayerQuitEvent
36+
import kotlin.concurrent.atomics.ExperimentalAtomicApi
3637
import kotlin.math.min
3738
import kotlin.time.Duration.Companion.milliseconds
3839

@@ -99,6 +100,7 @@ object CasListeners: Listener {
99100
}
100101
}
101102

103+
@OptIn(ExperimentalAtomicApi::class)
102104
private fun checkBlocked(player: Player, message: String, messageMeta: MessageMeta): Boolean {
103105
val user = player.user
104106
if (user.hasPerm("bypass")) {
@@ -189,7 +191,15 @@ object CasListeners: Listener {
189191
}
190192
}
191193
if (!blockValue) {
194+
if (config.consecutiveUnfilteredThreshold > 0 && spamData.filtered.isNotEmpty()) {
195+
if (spamData.consecutiveUnfiltered.addAndFetch(1) >= config.consecutiveUnfilteredThreshold) {
196+
spamData.filtered.removeFirst()
197+
spamData.consecutiveUnfiltered.store(0)
198+
}
199+
}
192200
spamData.records.sizedAdd(config.expireSize.messageRecord, SpamData.MessageRecord(request.message, now))
201+
} else {
202+
spamData.consecutiveUnfiltered.store(0)
193203
}
194204
return blockValue
195205
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package io.github.rothes.esu.bukkit.module.chatantispam.check
2+
3+
import io.github.rothes.esu.bukkit.module.chatantispam.message.MessageRequest
4+
import kotlin.concurrent.atomics.ExperimentalAtomicApi
5+
6+
object ConsecutiveUnfilteredFilter: Check("consecutive-unfiltered-filter") {
7+
8+
@OptIn(ExperimentalAtomicApi::class)
9+
override fun check(request: MessageRequest): CheckResult {
10+
if (config.consecutiveUnfilteredThreshold > 0) {
11+
if (request.spamData.consecutiveUnfiltered.addAndFetch(1) >= config.consecutiveUnfilteredThreshold) {
12+
request.spamData.consecutiveUnfiltered.store(0)
13+
}
14+
}
15+
if (request.spamCheck.letterCase.uniformOnCheck) {
16+
request.message = request.message.lowercase()
17+
}
18+
return CheckResult()
19+
}
20+
}

bukkit/src/main/kotlin/io/github/rothes/esu/bukkit/module/chatantispam/user/SpamData.kt

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,13 @@ package io.github.rothes.esu.bukkit.module.chatantispam.user
22

33
import com.google.gson.annotations.SerializedName
44
import io.github.rothes.esu.bukkit.module.ChatAntiSpamModule.config
5+
import io.github.rothes.esu.bukkit.module.chatantispam.user.CasDataManager.ChatSpamTable.lastAccess
56
import io.github.rothes.esu.core.util.extension.DurationExt.compareTo
67
import io.github.rothes.esu.core.util.extension.DurationExt.valuePositive
8+
import jdk.internal.net.http.common.Log.requests
9+
import net.minecraft.data.worldgen.placement.PlacementUtils.filtered
10+
import kotlin.concurrent.atomics.AtomicInt
11+
import kotlin.concurrent.atomics.ExperimentalAtomicApi
712

813
data class SpamData(
914
@SerializedName("t", alternate = ["muteUntil"])
@@ -32,9 +37,12 @@ data class SpamData(
3237
*/
3338
@SerializedName("ft", alternate = ["filtered"])
3439
val filtered: ArrayDeque<Long> = ArrayDeque(),
35-
@Transient
36-
var lastAccess: Long = -1,
3740
) {
41+
42+
@OptIn(ExperimentalAtomicApi::class)
43+
@Transient var consecutiveUnfiltered: AtomicInt = AtomicInt(0)
44+
@Transient var lastAccess: Long = -1
45+
3846
fun mute(): Long {
3947
val now = System.currentTimeMillis()
4048
if (now - muteUntil <= config.muteHandler.muteDurationMultiplier.maxMuteInterval) {

0 commit comments

Comments
 (0)