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+ }
0 commit comments