Skip to content

Commit 5ded046

Browse files
committed
Add SocialFilterModule
1 parent fb97030 commit 5ded046

File tree

4 files changed

+176
-0
lines changed

4 files changed

+176
-0
lines changed

bukkit/build.gradle.kts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,8 @@ dependencies {
8888
}
8989
compileOnly("net.momirealms:craft-engine-core:0.0.49")
9090
compileOnly("net.momirealms:craft-engine-bukkit:0.0.49")
91+
92+
compileOnly("com.hankcs:aho-corasick-double-array-trie:1.2.2")
9193
}
9294

9395
kotlin {

bukkit/src/main/kotlin/io/github/rothes/esu/bukkit/EsuPluginBukkit.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ class EsuPluginBukkit(
124124
"org.mariadb.jdbc:mariadb-java-client:3.5.3",
125125

126126
"info.debatty:java-string-similarity:2.0.0",
127+
"com.hankcs:aho-corasick-double-array-trie:1.2.2",
127128
)
128129
)
129130
dependenciesResolved = true
@@ -200,6 +201,7 @@ class EsuPluginBukkit(
200201
ModuleManager.addModule(ExploitFixesModule)
201202
ModuleManager.addModule(ItemEditModule)
202203
ModuleManager.addModule(NetworkThrottleModule)
204+
ModuleManager.addModule(SocialFilterModule)
203205
ModuleManager.addModule(SpawnProtectModule)
204206
ModuleManager.addModule(NewsModule)
205207
ModuleManager.addModule(OptimizationsModule)
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
package io.github.rothes.esu.bukkit.module
2+
3+
import com.hankcs.algorithm.AhoCorasickDoubleArrayTrie
4+
import io.github.rothes.esu.bukkit.event.UserChatEvent
5+
import io.github.rothes.esu.bukkit.event.UserEmoteCommandEvent
6+
import io.github.rothes.esu.bukkit.event.UserWhisperCommandEvent
7+
import io.github.rothes.esu.bukkit.user
8+
import io.github.rothes.esu.bukkit.util.extension.ListenerExt.register
9+
import io.github.rothes.esu.bukkit.util.extension.ListenerExt.unregister
10+
import io.github.rothes.esu.bukkit.util.version.adapter.ItemStackAdapter.Companion.displayName_
11+
import io.github.rothes.esu.bukkit.util.version.adapter.ItemStackAdapter.Companion.meta
12+
import io.github.rothes.esu.core.configuration.ConfigLoader
13+
import io.github.rothes.esu.core.configuration.ConfigurationPart
14+
import io.github.rothes.esu.core.configuration.MultiConfiguration
15+
import io.github.rothes.esu.core.configuration.data.MessageData
16+
import io.github.rothes.esu.core.configuration.data.MessageData.Companion.message
17+
import io.github.rothes.esu.core.configuration.meta.Comment
18+
import io.github.rothes.esu.core.module.configuration.BaseModuleConfiguration
19+
import io.github.rothes.esu.core.user.User
20+
import io.github.rothes.esu.core.util.ComponentUtils.legacy
21+
import io.github.rothes.esu.core.util.ComponentUtils.plainText
22+
import io.github.rothes.esu.core.util.extension.ifLet
23+
import org.bukkit.entity.Player
24+
import org.bukkit.event.EventHandler
25+
import org.bukkit.event.EventPriority
26+
import org.bukkit.event.Listener
27+
import org.bukkit.event.block.SignChangeEvent
28+
import org.bukkit.event.inventory.InventoryClickEvent
29+
import org.bukkit.inventory.AnvilInventory
30+
31+
object SocialFilterModule: BukkitModule<BaseModuleConfiguration, SocialFilterModule.ModuleLang>() {
32+
33+
lateinit var filters: MultiConfiguration<Filter>
34+
private set
35+
36+
override fun enable() {
37+
Listeners.register()
38+
}
39+
40+
override fun disable() {
41+
Listeners.unregister()
42+
}
43+
44+
override fun reloadConfig() {
45+
super.reloadConfig()
46+
filters = ConfigLoader.loadMulti(
47+
moduleFolder.resolve("filters"),
48+
ConfigLoader.LoaderSettingsMulti<Filter>(
49+
createKeys = listOf("example.yml")
50+
)
51+
)
52+
}
53+
54+
private object Listeners : Listener {
55+
56+
@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
57+
fun onAnvilRename(e: InventoryClickEvent) {
58+
val inventory = e.inventory
59+
if (inventory !is AnvilInventory) return
60+
val item = inventory.result ?: return
61+
62+
item.meta { meta ->
63+
val name = meta.displayName_?.plainText ?: return
64+
val find = filters.configs.values.find {
65+
it.enabled && it.blockAnvilRename && it.contains(name)
66+
} ?: return
67+
68+
e.isCancelled = true
69+
find.messageBlocked((e.whoClicked as Player).user)
70+
}
71+
}
72+
73+
@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
74+
fun onChat(e: UserChatEvent) {
75+
val message = e.message.legacy
76+
val find = filters.configs.values.find {
77+
it.enabled && it.blockChat && it.contains(message)
78+
} ?: return
79+
80+
e.isCancelled = true
81+
find.messageBlocked(e.user)
82+
}
83+
84+
@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
85+
fun onEmote(e: UserEmoteCommandEvent) {
86+
val message = e.message
87+
val find = filters.configs.values.find {
88+
it.enabled && it.blockChat && it.contains(message)
89+
} ?: return
90+
91+
e.isCancelled = true
92+
find.messageBlocked(e.user)
93+
}
94+
95+
@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
96+
fun onWhisper(e: UserWhisperCommandEvent) {
97+
val message = e.message
98+
val find = filters.configs.values.find {
99+
it.enabled && it.blockChat && it.contains(message)
100+
} ?: return
101+
102+
e.isCancelled = true
103+
find.messageBlocked(e.user)
104+
}
105+
106+
@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
107+
fun onSign(e: SignChangeEvent) {
108+
@Suppress("DEPRECATION")
109+
val message = e.lines.joinToString("")
110+
val find = filters.configs.values.find {
111+
it.enabled && it.blockSign && it.contains(message)
112+
} ?: return
113+
114+
e.isCancelled = true
115+
find.messageBlocked(e.player.user)
116+
}
117+
118+
}
119+
120+
data class Filter(
121+
@Comment("Enable this filter file.")
122+
val enabled: Boolean = false,
123+
@Comment("Block renaming items in anvils.")
124+
val blockAnvilRename: Boolean = true,
125+
@Comment("Block chat texts, including chat/emote/whisper .")
126+
val blockChat: Boolean = true,
127+
@Comment("Block writing texts on sign blocks.")
128+
val blockSign: Boolean = true,
129+
@Comment("Ignore case on matching texts.")
130+
val ignoreCase: Boolean = true,
131+
@Comment("""
132+
The message to send when blocked by this file.
133+
This is the key in the 'blocked-message' map in lang files.
134+
""")
135+
val blockedMessageKey: String = "bad-keywords",
136+
val keywords: List<String> = listOf("A keyword to block", "Another keyword to block"),
137+
) : ConfigurationPart {
138+
139+
val searcher by lazy {
140+
AhoCorasickDoubleArrayTrie<Filter>().also { trie ->
141+
trie.build(keywords.ifLet(ignoreCase) { map { it.lowercase() } }.associateWith { this })
142+
}
143+
}
144+
145+
fun contains(text: String): Boolean {
146+
return searcher.findFirst(text.ifLet(ignoreCase) { lowercase() }) != null
147+
}
148+
149+
fun messageBlocked(user: User) {
150+
if (blockedMessageKey.isEmpty()) return
151+
val message = user.localedOrNull(locale) { blockedMessage[blockedMessageKey] } ?: blockedMessageKey.message
152+
user.message(message)
153+
}
154+
155+
}
156+
157+
data class ModuleLang(
158+
val blockedMessage: Map<String, MessageData> = linkedMapOf(
159+
Pair("bad-keywords", "<ec>You have bad words in the text.".message)
160+
),
161+
): ConfigurationPart
162+
163+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package io.github.rothes.esu.core.util.extension
2+
3+
inline fun <T> T.ifLet(condition: Boolean, block: T.() -> T): T {
4+
return if (condition) block(this) else this
5+
}
6+
7+
inline fun <T> T.ifLet(condition: () -> Boolean, block: T.() -> T): T {
8+
return ifLet(condition(), block)
9+
}

0 commit comments

Comments
 (0)