1
+ package dev.racci.elixir.extensions
2
+
3
+ import com.github.jezza.Toml
4
+ import com.github.jezza.TomlArray
5
+ import com.github.jezza.TomlTable
6
+ import com.kotlindiscord.kord.extensions.components.buttons.PublicInteractionButtonContext
7
+ import com.kotlindiscord.kord.extensions.components.components
8
+ import com.kotlindiscord.kord.extensions.components.publicButton
9
+ import com.kotlindiscord.kord.extensions.extensions.Extension
10
+ import com.kotlindiscord.kord.extensions.utils.hasRole
11
+ import com.kotlindiscord.kord.extensions.utils.parseBoolean
12
+ import dev.kord.common.annotation.KordExperimental
13
+ import dev.kord.common.annotation.KordPreview
14
+ import dev.kord.common.annotation.KordUnsafe
15
+ import dev.kord.common.entity.ButtonStyle
16
+ import dev.kord.common.entity.DiscordPartialEmoji
17
+ import dev.kord.common.entity.Snowflake
18
+ import dev.kord.common.entity.optional.OptionalBoolean
19
+ import dev.kord.core.behavior.MemberBehavior
20
+ import dev.kord.core.behavior.RoleBehavior
21
+ import dev.kord.core.behavior.channel.GuildMessageChannelBehavior
22
+ import dev.kord.core.behavior.channel.createMessage
23
+ import dev.kord.core.behavior.edit
24
+ import dev.kord.core.behavior.interaction.followUpEphemeral
25
+ import dev.kord.core.entity.Member
26
+ import dev.kord.core.entity.Message
27
+ import dev.racci.elixir.configPath
28
+ import dev.racci.elixir.database.DatabaseManager
29
+ import dev.racci.elixir.utils.GUILD_ID
30
+ import java.nio.file.Files
31
+ import java.nio.file.Paths
32
+ import java.util.*
33
+ import kotlin.properties.Delegates
34
+ import kotlinx.coroutines.flow.count
35
+ import org.jetbrains.exposed.sql.deleteWhere
36
+ import org.jetbrains.exposed.sql.select
37
+ import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction
38
+
39
+ class RoleSelector : Extension () {
40
+
41
+ override var name = " roles"
42
+
43
+ private val roles: TomlTable = Toml .from(Files .newInputStream(Paths .get(" $configPath /roles.toml" )))
44
+ private var channel by Delegates .notNull<GuildMessageChannelBehavior >()
45
+
46
+ @OptIn(KordUnsafe ::class , KordExperimental ::class )
47
+ override suspend fun setup () {
48
+ val roleSelectors: TomlArray = roles.get(" roleselector" ) as TomlArray
49
+ channel = kord.unsafe.guildMessageChannel(GUILD_ID , (Snowflake (roles[" roleChannel" ] as ULong )))
50
+ newSuspendedTransaction {
51
+ DatabaseManager .RoleSelector .messageId.ddl.forEach {
52
+ val message = channel.getMessageOrNull(Snowflake (it))
53
+ if (message == null ) {
54
+ DatabaseManager .RoleSelector .deleteWhere { DatabaseManager .RoleSelector .messageId eq it }
55
+ }
56
+ }
57
+ }
58
+ for (rsls in roleSelectors) {
59
+ val rsl = rsls as TomlTable
60
+ addRoleSelector(
61
+ rsl.get(" name" ) as String ,
62
+ rsl.getOrDefault(" attachment" , " " ) as String ,
63
+ rsl.getOrDefault(" limit" , - 1 ) as Int ,
64
+ rsl.getOrDefault(" removable" , true ) as Boolean ,
65
+ rsl.get(" role" ) as TomlArray ?
66
+ )
67
+ }
68
+ }
69
+
70
+ private suspend fun ensureExistingMessage (
71
+ name : String ,
72
+ attachment : String
73
+ ): Message {
74
+ return if (! DatabaseManager .RoleSelector .name.ddl.any { it == name }) {
75
+ channel.createMessage {
76
+ addFile(Paths .get(attachment))
77
+ }
78
+ } else {
79
+ channel.getMessage(
80
+ Snowflake (
81
+ DatabaseManager .RoleSelector .select {
82
+ DatabaseManager .RoleSelector .name eq name
83
+ }.first()[DatabaseManager .RoleSelector .messageId]
84
+ )
85
+ )
86
+ }
87
+ }
88
+
89
+ private suspend fun removeCurrentRoleCheck (
90
+ removable : Boolean ,
91
+ member : MemberBehavior ,
92
+ roleBehavior : RoleBehavior ,
93
+ context : PublicInteractionButtonContext
94
+ ): String? { // Return a nullable string for easily returning if we need to
95
+ return if (member.asMember().hasRole(roleBehavior)) {
96
+ if (removable) {
97
+ member.removeRole(roleBehavior.id)
98
+ } else {
99
+ context.interactionResponse.followUpEphemeral {
100
+ ephemeral = true
101
+ content = " Sorry, You cannot the remove the ${roleBehavior.fetchRole().name} role."
102
+ }
103
+ }
104
+ null
105
+ } else " "
106
+ }
107
+
108
+ private suspend fun roleLimitCheck (
109
+ limit : Int ,
110
+ member : MemberBehavior ,
111
+ selectorRoles : List <RoleBehavior >,
112
+ context : PublicInteractionButtonContext ,
113
+ ): String? {
114
+ return if (limit != - 1 && member.asMember().roles.count(selectorRoles::contains) > limit) {
115
+ context.interactionResponse.followUpEphemeral {
116
+ ephemeral = true
117
+ content = " Sorry, You already have the maximum amount of roles from this selector."
118
+ }
119
+ null
120
+ } else " "
121
+ }
122
+
123
+ private suspend fun roleIncompatibleWithCheck (
124
+ incompatibleWith : Collection <RoleBehavior >,
125
+ roleBehavior : RoleBehavior ,
126
+ member : MemberBehavior ,
127
+ context : PublicInteractionButtonContext ,
128
+ ): String? {
129
+ val incompatible = member.asMember().getAnyRole(incompatibleWith)
130
+ return if (incompatible != null ) {
131
+ context.interactionResponse.followUpEphemeral {
132
+ ephemeral = true
133
+ content = " Sorry, You ${roleBehavior.fetchRole().name} is incompatible with ${incompatible.fetchRole().name} "
134
+ }
135
+ null
136
+ } else " "
137
+ }
138
+
139
+ @OptIn(KordPreview ::class )
140
+ @Suppress(" UNCHECKED_CAST" )
141
+ private suspend fun addRoleSelector (
142
+ name : String ,
143
+ attachment : String ,
144
+ limit : Int ,
145
+ removable : Boolean ,
146
+ roles : TomlArray ? ,
147
+ ) {
148
+ val message = ensureExistingMessage(name, attachment)
149
+ message.edit {
150
+ components {
151
+ // Remove all so we have the order and everything fully up to date
152
+ removeAll()
153
+
154
+ if (roles == null ) return @components
155
+ val selectorRoles = roles.map { RoleBehavior (GUILD_ID , Snowflake ((it as TomlTable )[" roleId" ] as ULong ), kord) }
156
+
157
+ for (role in roles.asList() as List <TomlTable >) {
158
+ val roleBehavior = selectorRoles.first { it.id.value == role[" roleId" ] as ULong }
159
+
160
+ publicButton {
161
+ label = role[" name" ] as String
162
+ val emoji = (role[" emoji" ] as String ).split(' :' , limit = 3 )
163
+ partialEmoji = DiscordPartialEmoji .dsl {
164
+ id = Snowflake (emoji[0 ])
165
+ this .name = emoji[1 ]
166
+ animated = OptionalBoolean .Value (emoji[2 ].parseBoolean(Locale .ENGLISH ) ? : false )
167
+ }
168
+ style = ButtonStyle .Primary
169
+
170
+ val incompatibleWith = (role[" incompatibleWith" ] as Array <ULong >).map {
171
+ RoleBehavior (GUILD_ID , Snowflake (it), kord)
172
+ }
173
+
174
+ action {
175
+ val member = member ? : return @action
176
+
177
+ removeCurrentRoleCheck(
178
+ removable,
179
+ member,
180
+ roleBehavior,
181
+ this
182
+ ) ? : return @action
183
+
184
+ roleLimitCheck(
185
+ limit,
186
+ member,
187
+ selectorRoles,
188
+ this ,
189
+ ) ? : return @action
190
+
191
+ roleIncompatibleWithCheck(
192
+ incompatibleWith,
193
+ roleBehavior,
194
+ member,
195
+ this ,
196
+ ) ? : return @action
197
+
198
+ member.addRole(roleBehavior.id, " Role Selections" )
199
+ }
200
+ }
201
+ }
202
+ }
203
+ }
204
+ }
205
+ }
206
+
207
+ fun Member.hasAnyRole (roles : Collection <RoleBehavior >): Boolean {
208
+ return if (roles.isEmpty()) {
209
+ true
210
+ } else {
211
+ val ids = roles.map { it.id }
212
+ roleIds.any { ids.contains(id) }
213
+ }
214
+ }
215
+
216
+ fun Member.getAnyRole (roles : Collection <RoleBehavior >): RoleBehavior ? {
217
+ return if (roles.isEmpty()) {
218
+ null
219
+ } else {
220
+ val ids = roles.map { it.id }
221
+ roleBehaviors.firstOrNull { ids.contains(it.id) }
222
+ }
223
+ }
224
+ class DiscordPartialEmojiDSL {
225
+
226
+ var id: Snowflake ? = null
227
+ var name: String? = null
228
+ var animated: OptionalBoolean = OptionalBoolean .Missing
229
+
230
+ fun build (): DiscordPartialEmoji = DiscordPartialEmoji (id, name, animated)
231
+ }
232
+
233
+ inline fun DiscordPartialEmoji.Companion.dsl (block : DiscordPartialEmojiDSL .() -> Unit ): DiscordPartialEmoji {
234
+ val dsl = DiscordPartialEmojiDSL ()
235
+ dsl.block()
236
+ return dsl.build()
237
+ }
0 commit comments