1+ /*
2+ * Copyright 2025 Lambda
3+ *
4+ * This program is free software: you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation, either version 3 of the License, or
7+ * (at your option) any later version.
8+ *
9+ * This program is distributed in the hope that it will be useful,
10+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
11+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+ * GNU General Public License for more details.
13+ *
14+ * You should have received a copy of the GNU General Public License
15+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
16+ */
17+
18+ package com.lambda.module.modules.chat
19+
20+ import com.lambda.config.Configurable
21+ import com.lambda.config.SettingGroup
22+ import com.lambda.config.groups.ReplaceConfig
23+ import com.lambda.event.events.ChatEvent
24+ import com.lambda.event.listener.SafeListener.Companion.listen
25+ import com.lambda.module.Module
26+ import com.lambda.module.tag.ModuleTag
27+ import com.lambda.util.NamedEnum
28+ import net.minecraft.text.Text
29+
30+ object AntiSpam : Module(
31+ name = " AntiSpam" ,
32+ description = " Keeps your chat clean" ,
33+ tag = ModuleTag .CHAT ,
34+ ) {
35+ private val detectSlurs = ReplaceSettings (" Slurs" , this , Group .Slurs )
36+ private val detectSwears = ReplaceSettings (" Swears" , this , Group .Swears )
37+ private val detectSexual = ReplaceSettings (" Sexual" , this , Group .Sexual )
38+ private val detectAddresses = ReplaceSettings (" Addresses" , this , Group .Addresses )
39+
40+ val slurs = sequenceOf(" \\ bch[i1l]nks?\\ b" , " \\ bc[o0]{2}ns?\\ b" , " f[a@4](g{1,2}|qq)([e3il1o0]t{1,2}(ry|r[i1l]e)?)?\\ b" , " \\ bk[il1y]k[e3](ry|r[i1l]e)?s?\\ b" , " \\ b(s[a4]nd)?n[ila4o10][gq]{1,2}(l[e3]t|[e3]r|[a4]|n[o0]g)?s?\\ b" , " \\ btr[a4]n{1,2}([il1][e3]|y|[e3]r)s?\\ b" ).map { Regex (it) }
41+ val swears = sequenceOf(" fuck(er)?" , " shit" , " cunt" , " puss(ie|y)" , " bitch" , " twat" ).map { Regex (it) }
42+ val sexual = sequenceOf(" ^cum[s\\ $]?$" , " cumm?[i1]ng" , " h[o0]rny" , " mast(e|ur)b(8|ait|ate)" ).map { Regex (it) }
43+ val addresses = sequenceOf(" ^((25[0-5]|2[0-4]\\ d|1\\ d{2}|[1-9]?\\ d)(\\ .)){3}(25[0-5]|2[0-4]\\ d|1\\ d{2}|[1-9]?\\ d)$" , " ^(\\ :\\ :)?[0-9a-fA-F]{1,4}(\\ :\\ :?[0-9a-fA-F]{1,4}){0,7}(\\ :\\ :)?$" , " [A-Za-z0-9-]{1,63}\\ .[A-Za-z]{2,6}$" ).map { Regex (it) }
44+
45+ enum class Group (override val displayName : String ) : NamedEnum {
46+ Slurs (" Slurs" ),
47+ Swears (" Swears" ),
48+ Sexual (" Sexual" ),
49+ Addresses (" IPs and Addresses" ),
50+ }
51+
52+ init {
53+ listen<ChatEvent .Message > { event ->
54+ val author = event.message.string.substringBefore(' ' )
55+ var content = event.message.string.substringAfter(' ' )
56+
57+ val slurMatches = slurs.takeIf { detectSlurs.enabled }.orEmpty()
58+ .flatMap { it.findAll(content).toList().reversed() }
59+
60+ val swearMatches = swears.takeIf { detectSwears.enabled }.orEmpty()
61+ .flatMap { it.findAll(content).toList().reversed() }
62+
63+ val sexualMatches = sexual.takeIf { detectSexual.enabled }.orEmpty()
64+ .flatMap { it.findAll(content).toList().reversed() }
65+
66+ val addressMatches = addresses.takeIf { detectAddresses.enabled }.orEmpty()
67+ .flatMap { it.findAll(content).toList().reversed() }
68+
69+ var cancelled = false
70+ var hasMatches = false
71+
72+ fun doMatch (replace : ReplaceConfig , matches : Sequence <MatchResult >) {
73+ if (cancelled) return
74+
75+ when (replace.action) {
76+ ReplaceConfig .ActionStrategy .Hide -> matches.firstOrNull()?.let { event.cancel(); cancelled = true } // If there's one detection, nuke the whole damn thang
77+ ReplaceConfig .ActionStrategy .Delete -> matches
78+ .forEach { content = content.replaceRange(it.range, " " ); hasMatches = true }
79+ ReplaceConfig .ActionStrategy .Replace -> matches
80+ .forEach { content = content.replaceRange(it.range, replace.replace.block(it.value)); hasMatches = true }
81+ ReplaceConfig .ActionStrategy .None -> {}
82+ }
83+ }
84+
85+ doMatch(detectSlurs, slurMatches)
86+ doMatch(detectSwears, swearMatches)
87+ doMatch(detectSexual, sexualMatches)
88+ doMatch(detectAddresses, addressMatches)
89+
90+ if (! hasMatches) return @listen
91+
92+ event.message = Text .of(" $author $content " )
93+ }
94+ }
95+
96+ class ReplaceSettings (
97+ name : String ,
98+ c : Configurable ,
99+ baseGroup : NamedEnum ,
100+ ) : ReplaceConfig, SettingGroup(c) {
101+ override val action by setting(" $name Action Strategy" , ReplaceConfig .ActionStrategy .Replace ).group(baseGroup)
102+ override val replace by setting(" $name Replace Strategy" , ReplaceConfig .ReplaceStrategy .CensorAll ) { action == ReplaceConfig .ActionStrategy .Replace }.group(baseGroup)
103+ }
104+ }
0 commit comments