From c535437477abb8304c8b3b31a702b0d5802c6698 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Victor?= <60321993+hechfx@users.noreply.github.com> Date: Sat, 14 Sep 2024 21:41:07 -0300 Subject: [PATCH] migrate role button system to JDA --- .gitignore | 4 +- .../loritta/helper/LorittaHelper.kt | 1 + .../vanilla/ButtonRoleSenderCommand.kt | 235 ++++++++++++++++++ .../listeners/ComponentInteractionListener.kt | 206 ++++++++++++++- .../loritta/helper/utils/LorittaLandGuild.kt | 8 +- .../helper/utils/extensions/JDAExtensions.kt | 9 + .../interactions/InteractionContext.kt | 10 + 7 files changed, 470 insertions(+), 3 deletions(-) create mode 100644 src/main/kotlin/net/perfectdreams/loritta/helper/interactions/commands/vanilla/ButtonRoleSenderCommand.kt diff --git a/.gitignore b/.gitignore index 4f864cdb..4f5daae8 100644 --- a/.gitignore +++ b/.gitignore @@ -131,4 +131,6 @@ fabric.properties # Don't ignore the configuration file in resources !/src/main/resources/helper.conf !/src/main/resources/fan_arts.conf -!/src/main/resources/loritta.conf \ No newline at end of file +!/src/main/resources/loritta.conf + +top_sonhos.json \ No newline at end of file diff --git a/src/main/kotlin/net/perfectdreams/loritta/helper/LorittaHelper.kt b/src/main/kotlin/net/perfectdreams/loritta/helper/LorittaHelper.kt index 4b20bd9d..715ea2c1 100644 --- a/src/main/kotlin/net/perfectdreams/loritta/helper/LorittaHelper.kt +++ b/src/main/kotlin/net/perfectdreams/loritta/helper/LorittaHelper.kt @@ -152,6 +152,7 @@ class LorittaHelper(val config: LorittaHelperConfig, val fanArtsConfig: FanArtsC commandManager.register(DailyCheckCommand(this)) commandManager.register(TicketSenderCommand(this)) commandManager.register(ReportMessageSenderCommand(this)) + commandManager.register(ButtonRoleSenderCommand(this)) if (config.loritta.database != null) { val dailyCatcher = DailyCatcherManager(this, jda) diff --git a/src/main/kotlin/net/perfectdreams/loritta/helper/interactions/commands/vanilla/ButtonRoleSenderCommand.kt b/src/main/kotlin/net/perfectdreams/loritta/helper/interactions/commands/vanilla/ButtonRoleSenderCommand.kt new file mode 100644 index 00000000..a1769026 --- /dev/null +++ b/src/main/kotlin/net/perfectdreams/loritta/helper/interactions/commands/vanilla/ButtonRoleSenderCommand.kt @@ -0,0 +1,235 @@ +package net.perfectdreams.loritta.helper.interactions.commands.vanilla + +import net.dv8tion.jda.api.interactions.components.buttons.Button +import net.dv8tion.jda.api.interactions.components.buttons.ButtonStyle +import net.perfectdreams.loritta.helper.LorittaHelper +import net.perfectdreams.loritta.helper.utils.LorittaLandGuild +import net.perfectdreams.loritta.helper.utils.buttonroles.LorittaCommunityRoleButtons +import net.perfectdreams.loritta.helper.utils.buttonroles.LorittaCommunityRoleButtons.partialEmojiAsMention +import net.perfectdreams.loritta.helper.utils.buttonroles.SparklyPowerRoleButtons +import net.perfectdreams.loritta.helper.utils.extensions.toJDA +import net.perfectdreams.loritta.morenitta.interactions.commands.* +import net.perfectdreams.loritta.morenitta.interactions.commands.options.ApplicationCommandOptions +import net.perfectdreams.loritta.morenitta.interactions.styled +import java.awt.Color + +class ButtonRoleSenderCommand(val loritta: LorittaHelper) : SlashCommandDeclarationWrapper { + override fun command() = slashCommand("buttonrolesender2", "Envia a mensagem de cargos no canal selecionado") { + executor = ButtonRoleSenderCommandExecutor() + } + + inner class ButtonRoleSenderCommandExecutor : LorittaSlashCommandExecutor() { + inner class Options : ApplicationCommandOptions() { + val channel = channel("channel", "Canal aonde a mensagem será enviada") + } + + override val options = Options() + + override suspend fun execute(context: ApplicationCommandContext, args: SlashCommandArguments) { + if (context.guildId == null) + return + + val channel = args[options.channel] + val guildId = context.guildId!! + + context.reply(true) { + styled( + "Enviando mensagem. Segure firme!!" + ) + } + + when (guildId) { + loritta.config.guilds.community.id -> { + // ===[ CUSTOM COLORS ]=== + context.sendMessage(channel.idLong) { + embed { + title = "Cores Personalizadas" + color = Color(26, 160, 254).rgb + + description = """Escolha uma cor personalizada para o seu nome e, de brinde, receba um ícone do lado do seu nome relacionado com a cor que você escolheu aqui no servidor da Loritta! + | + |**Apenas disponível para usuários Premium da Loritta (<@&364201981016801281>)!** Ficou interessado? Então [clique aqui](https://loritta.website/br/donate)! + """.trimMargin() + + thumbnail = "https://cdn.discordapp.com/attachments/358774895850815488/889932408177168394/gabriela_sortros_crop.png" + } + + val chunkedRoleButtons = LorittaCommunityRoleButtons.colors.chunked(5) + + for (actionRow in chunkedRoleButtons) { + actionRow( + actionRow.map { + Button.of( + ButtonStyle.SECONDARY, + "color-${LorittaLandGuild.LORITTA_COMMUNITY}:${it.roleId}", + it.emoji.toJDA() + ) + } + ) + } + } + + // ===[ CUSTOM ROLE ICONS ]=== + context.sendMessage(channel.idLong) { + embed { + title = "Ícones Personalizados" + color = Color(26, 160, 254).rgb + + description = """Escolha um ícone personalizado que irá aparecer ao lado do seu nome aqui no servidor da Loritta! O ícone personalizado irá substituir qualquer outro ícone que você possui! + | + |**Apenas disponível para usuários Premium da Loritta (<@&364201981016801281>) ou <@&655132411566358548>!** Ficou interessado? Então [clique aqui](https://loritta.website/br/donate)! Ou, se preferir, seja mais ativo no servidor para chegar no nível 10! + """.trimMargin() + + thumbnail = "https://cdn.discordapp.com/emojis/853048446974033960.png?v=1" + } + + val chunkedRoleButtons = LorittaCommunityRoleButtons.coolBadges.chunked(5) + + for (actionRow in chunkedRoleButtons) { + actionRow( + actionRow.map { + Button.of( + ButtonStyle.SECONDARY, + "badge-${LorittaLandGuild.LORITTA_COMMUNITY}:${it.roleId}", + it.emoji.toJDA() + ) + } + ) + } + } + + // ===[ NOTIFICATIONS ]=== + context.sendMessage(channel.idLong) { + embed { + title = "Cargos de Notificações" + color = Color(26, 160, 254).rgb + + description = buildString { + for (roleInfo in LorittaCommunityRoleButtons.notifications) { + append("**") + append(partialEmojiAsMention(roleInfo.emoji)) + append(' ') + append(roleInfo.label) + append(':') + append("**") + append(' ') + append(roleInfo.description) + append('\n') + append('\n') + } + } + + thumbnail = "https://cdn.discordapp.com/emojis/640141673531441153.png?v=1" + } + + actionRow( + LorittaCommunityRoleButtons.notifications.map { + Button.of( + ButtonStyle.SECONDARY, + "notif-${LorittaLandGuild.LORITTA_COMMUNITY}:${it.roleId}", + it.label, + it.emoji.toJDA() + ) + } + ) + } + } + loritta.config.guilds.sparklyPower.id -> { + // ===[ CUSTOM COLORS ]=== + context.sendMessage(channel.idLong) { + embed { + title = "Cores Personalizadas" + color = Color(26, 160, 254).rgb + + description = """Escolha uma cor personalizada para o seu nome e, de brinde, receba um ícone do lado do seu nome relacionado com a cor que você escolheu aqui no servidor da Loritta! + | + |**Apenas disponível para usuários VIPs (<@&332652664544428044>)!** Ficou interessado? Então [clique aqui](https://sparklypower.net/loja)! + """.trimMargin() + + thumbnail = "https://cdn.discordapp.com/attachments/358774895850815488/889932408177168394/gabriela_sortros_crop.png" + } + + val chunkedRoleButtons = SparklyPowerRoleButtons.colors.chunked(5) + + for (actionRow in chunkedRoleButtons) { + actionRow( + actionRow.map { + Button.of( + ButtonStyle.SECONDARY, + "color-${LorittaLandGuild.SPARKLYPOWER}:${it.roleId}", + it.emoji.toJDA() + ) + } + ) + } + } + + // ===[ CUSTOM ROLE ICONS ]=== + context.sendMessage(channel.idLong) { + embed { + title = "Ícones Personalizados" + color = Color(26, 160, 254).rgb + + description = """Escolha um ícone personalizado que irá aparecer ao lado do seu nome aqui no servidor do SparklyPower! O ícone personalizado irá substituir qualquer outro ícone que você possui! + | + |**Apenas disponível para usuários VIPs (<@&332652664544428044>) ou <@&834625069321551892>!** Ficou interessado? Então [clique aqui](https://sparklypower.net/loja)! Ou, se preferir, seja mais ativo no servidor para chegar no nível 10! + """.trimMargin() + + thumbnail = "https://cdn.discordapp.com/emojis/853048446974033960.png?v=1" + } + + val chunkedRoleButtons = SparklyPowerRoleButtons.coolBadges.chunked(5) + + for (actionRow in chunkedRoleButtons) { + actionRow( + actionRow.map { + Button.of( + ButtonStyle.SECONDARY, + "badge-${LorittaLandGuild.SPARKLYPOWER}:${it.roleId}", + it.emoji.toJDA() + ) + } + ) + } + } + + // ===[ NOTIFICATIONS ]=== + context.sendMessage(channel.idLong) { + embed { + title = "Cargos de Notificações" + color = Color(26, 160, 254).rgb + + description = buildString { + for (roleInfo in SparklyPowerRoleButtons.notifications) { + append("**") + append(partialEmojiAsMention(roleInfo.emoji)) + append(' ') + append(roleInfo.label) + append(':') + append("**") + append(' ') + append(roleInfo.description) + append('\n') + append('\n') + } + } + + thumbnail = "https://cdn.discordapp.com/emojis/640141673531441153.png?v=1" + } + + actionRow( + SparklyPowerRoleButtons.notifications.map { + Button.of( + ButtonStyle.SECONDARY, + "notif-${LorittaLandGuild.SPARKLYPOWER}:${it.roleId}", + it.label, + it.emoji.toJDA() + ) + } + ) + } + } + } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/net/perfectdreams/loritta/helper/listeners/ComponentInteractionListener.kt b/src/main/kotlin/net/perfectdreams/loritta/helper/listeners/ComponentInteractionListener.kt index ca9a91d2..9fffa5dc 100644 --- a/src/main/kotlin/net/perfectdreams/loritta/helper/listeners/ComponentInteractionListener.kt +++ b/src/main/kotlin/net/perfectdreams/loritta/helper/listeners/ComponentInteractionListener.kt @@ -1,6 +1,7 @@ package net.perfectdreams.loritta.helper.listeners import com.github.benmanes.caffeine.cache.Caffeine +import dev.kord.common.entity.Snowflake import dev.minn.jda.ktx.messages.MessageCreate import io.ktor.http.* import kotlinx.coroutines.sync.withLock @@ -13,6 +14,8 @@ import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent import net.dv8tion.jda.api.events.interaction.component.StringSelectInteractionEvent import net.dv8tion.jda.api.hooks.ListenerAdapter import net.dv8tion.jda.api.interactions.components.buttons.Button +import net.perfectdreams.discordinteraktions.common.builder.message.MessageBuilder +import net.perfectdreams.discordinteraktions.common.builder.message.create.InteractionOrFollowupMessageCreateBuilder import net.perfectdreams.loritta.api.messages.LorittaReply import net.perfectdreams.loritta.cinnamon.pudding.tables.BannedUsers import net.perfectdreams.loritta.helper.LorittaHelper @@ -20,6 +23,8 @@ import net.perfectdreams.loritta.helper.i18n.I18nKeysData import net.perfectdreams.loritta.helper.tables.SelectedResponsesLog import net.perfectdreams.loritta.helper.tables.StartedSupportSolicitations import net.perfectdreams.loritta.helper.utils.ComponentDataUtils +import net.perfectdreams.loritta.helper.utils.LorittaLandGuild +import net.perfectdreams.loritta.helper.utils.buttonroles.GuildRolesData import net.perfectdreams.loritta.helper.utils.extensions.await import net.perfectdreams.loritta.helper.utils.generateserverreport.EncryptionUtils import net.perfectdreams.loritta.helper.utils.tickets.FakePrivateThreadChannel @@ -46,24 +51,223 @@ class ComponentInteractionListener(val m: LorittaHelper) : ListenerAdapter() { override fun onButtonInteraction(event: ButtonInteractionEvent) { logger.info { "Button Interaction ${event.user.idLong} - ${event.channel.idLong}: ${event.componentId}" } - val (id, data) = event.componentId.split(":") + if (id.contains("-")) { + val split = id.split("-") + val newId = split[0] + val guild = LorittaLandGuild.fromId(split[1]) + when (newId) { + "color" -> { + giveColorRole(event, data, guild) + } + + "badge" -> { + giveBadgeRole(event, data, guild) + } + + "notif" -> { + giveNotifRole(event, data, guild) + } + } + } + when (id) { "create_ticket" -> { m.launch { createTicket(event, data) } } + "close_ticket" -> { closeTicket(event, data) } + "open_report_form" -> { openReportForm(event) } } } + private fun giveColorRole(event: ButtonInteractionEvent, data: String, guild: LorittaLandGuild) { + val guildRolesData = mapOf( + LorittaLandGuild.LORITTA_COMMUNITY to GuildRolesData( + Snowflake(297732013006389252L), + listOf(Snowflake(364201981016801281L)) + ), + LorittaLandGuild.SPARKLYPOWER to GuildRolesData( + Snowflake(320248230917046282L), + listOf(Snowflake(332652664544428044L)) + ) + ) + + if (!event.isFromGuild) + return + + val guildData = guildRolesData[guild]!! + + if (!event.member!!.roles.any { it.id in guildData.allowedRoles.map { it.value.toString() } }) { + event.interaction.reply("Para você pegar uma cor personalizada, você precisa ser ${guildData.allowedRoles.joinToString(" ou ") { "<@&${it.value}>" }}!").setEphemeral(true).queue() + return + } + + val roleInformation = guild.colors.first { it.roleId == Snowflake(data.toLong()) } + + if (data in event.member!!.roles.map { it.id }) { + // remove role + val role = event.guild!!.getRoleById(data) + + event.guild!!.removeRoleFromMember(event.member!!, role!!).queue() + + val builtMessage = MessageCreate { + val kordMessage = InteractionOrFollowupMessageCreateBuilder(true) + .apply { + roleInformation.messageRemove.invoke(this, roleInformation) + } + + content = kordMessage.content + } + + event.interaction.reply(builtMessage).setEphemeral(true).queue() + } else { + // add role + val role = event.guild!!.getRoleById(data) + + val availableRoles = guild.colors + + val rolesToRemove = availableRoles.map { it.roleId } + + // remove other roles that the user may have + val rolesToBeOverwritten = event.member!!.roles.filter { it.idLong in rolesToRemove.map { it.value.toLong() } }.toMutableSet() + + rolesToBeOverwritten.add(role) + + event.guild!!.modifyMemberRoles(event.member!!, rolesToBeOverwritten).queue() + + val builtMessage = MessageCreate { + val kordMessage = InteractionOrFollowupMessageCreateBuilder(true) + .apply { + roleInformation.messageReceive.invoke(this, roleInformation) + } + + content = kordMessage.content + } + + event.interaction.reply(builtMessage).setEphemeral(true).queue() + } + } + + private fun giveBadgeRole(event: ButtonInteractionEvent, data: String, guild: LorittaLandGuild) { + if (!event.isFromGuild) + return + + val guildRolesData = mapOf( + LorittaLandGuild.LORITTA_COMMUNITY to GuildRolesData( + Snowflake(297732013006389252L), + listOf(Snowflake(364201981016801281L), Snowflake(655132411566358548L)) + ), + LorittaLandGuild.SPARKLYPOWER to GuildRolesData( + Snowflake(320248230917046282L), + listOf(Snowflake(332652664544428044L), Snowflake(834625069321551892L)) + ) + ) + + val guildData = guildRolesData[guild]!! + + if (!event.member!!.roles.any { Snowflake(it.idLong) in guildData.allowedRoles}) { + event.interaction.reply("Para você pegar um ícone personalizado, você precisa ser ${guildData.allowedRoles.joinToString(" ou ") { "<@&${it.value}>" }}!").setEphemeral(true).queue() + return + } + + val roleInformation = guild.coolBadges.first { it.roleId == Snowflake(data.toLong()) } + + if (Snowflake(data.toLong()) in event.member!!.roles.map { Snowflake(it.idLong) }) { + // remove role + val role = event.guild!!.getRoleById(data) + + event.member!!.roles.remove(role) + + val builtMessage = MessageCreate { + val kordMessage = InteractionOrFollowupMessageCreateBuilder(true) + .apply { + roleInformation.messageRemove.invoke(this, roleInformation) + } + + content = kordMessage.content + } + + event.interaction.reply(builtMessage).setEphemeral(true).queue() + } else { + // add role + val role = event.guild!!.getRoleById(data) + + val availableRoles = guild.coolBadges + + val rolesToRemove = availableRoles.map { it.roleId } + + // remove other roles that the user may have + val rolesToBeOverwritten = event.member!!.roles.filter { it.idLong in rolesToRemove.map { it.value.toLong() } }.toMutableSet() + + rolesToBeOverwritten.add(role) + + event.guild!!.modifyMemberRoles(event.member!!, rolesToBeOverwritten).queue() + + val builtMessage = MessageCreate { + val kordMessage = InteractionOrFollowupMessageCreateBuilder(true) + .apply { + roleInformation.messageReceive.invoke(this, roleInformation) + } + + content = kordMessage.content + } + + event.interaction.reply(builtMessage).setEphemeral(true).queue() + } + } + + private fun giveNotifRole(event: ButtonInteractionEvent, data: String, guild: LorittaLandGuild) { + if (!event.isFromGuild) + return + + val roleInformation = guild.notifications.first { it.roleId == Snowflake(data.toLong()) } + + if (Snowflake(data.toLong()) in event.member!!.roles.map { Snowflake(it.idLong) }) { + // remove role + val role = event.guild!!.getRoleById(data) + + event.guild!!.removeRoleFromMember(event.member!!, role!!).queue() + + event.member!!.roles.remove(role) + + val builtMessage = MessageCreate { + val kordMessage = InteractionOrFollowupMessageCreateBuilder(true) + .apply { + roleInformation.messageRemove.invoke(this, roleInformation) + } + + content = kordMessage.content + } + + event.interaction.reply(builtMessage).setEphemeral(true).queue() + } else { + // add role + val role = event.guild!!.getRoleById(data) + + event.guild!!.addRoleToMember(event.member!!, role!!).queue() + + val builtMessage = MessageCreate { + val kordMessage = InteractionOrFollowupMessageCreateBuilder(true) + .apply { + roleInformation.messageReceive.invoke(this, roleInformation) + } + + content = kordMessage.content + } + + event.interaction.reply(builtMessage).setEphemeral(true).queue() + } + } + private fun openReportForm(event: ButtonInteractionEvent) { val json = buildJsonObject { put("user", event.user.idLong) diff --git a/src/main/kotlin/net/perfectdreams/loritta/helper/utils/LorittaLandGuild.kt b/src/main/kotlin/net/perfectdreams/loritta/helper/utils/LorittaLandGuild.kt index 193e2721..9140f52a 100644 --- a/src/main/kotlin/net/perfectdreams/loritta/helper/utils/LorittaLandGuild.kt +++ b/src/main/kotlin/net/perfectdreams/loritta/helper/utils/LorittaLandGuild.kt @@ -18,5 +18,11 @@ enum class LorittaLandGuild( SparklyPowerRoleButtons.colors, SparklyPowerRoleButtons.coolBadges, SparklyPowerRoleButtons.notifications - ) + ); + + companion object { + fun fromId(id: String): LorittaLandGuild { + return entries.first { it.name == id } + } + } } \ No newline at end of file diff --git a/src/main/kotlin/net/perfectdreams/loritta/helper/utils/extensions/JDAExtensions.kt b/src/main/kotlin/net/perfectdreams/loritta/helper/utils/extensions/JDAExtensions.kt index 040e8b1a..3f83b535 100644 --- a/src/main/kotlin/net/perfectdreams/loritta/helper/utils/extensions/JDAExtensions.kt +++ b/src/main/kotlin/net/perfectdreams/loritta/helper/utils/extensions/JDAExtensions.kt @@ -1,7 +1,12 @@ package net.perfectdreams.loritta.helper.utils.extensions +import dev.kord.common.entity.DiscordPartialEmoji +import dev.kord.common.entity.optional.value +import dev.kord.core.entity.channel.TextChannel import net.dv8tion.jda.api.entities.Message import net.dv8tion.jda.api.entities.MessageHistory +import net.dv8tion.jda.api.entities.emoji.CustomEmoji +import net.dv8tion.jda.api.entities.emoji.Emoji import net.dv8tion.jda.api.requests.RestAction import kotlin.coroutines.resume import kotlin.coroutines.resumeWithException @@ -25,4 +30,8 @@ suspend fun MessageHistory.retrieveAllMessages(): List { } return messages +} + +fun DiscordPartialEmoji.toJDA(): CustomEmoji { + return Emoji.fromCustom(this.name!!, this.id!!.value.toLong(), this.animated.value ?: false) } \ No newline at end of file diff --git a/src/main/kotlin/net/perfectdreams/loritta/morenitta/interactions/InteractionContext.kt b/src/main/kotlin/net/perfectdreams/loritta/morenitta/interactions/InteractionContext.kt index 7f72182b..f48335e5 100644 --- a/src/main/kotlin/net/perfectdreams/loritta/morenitta/interactions/InteractionContext.kt +++ b/src/main/kotlin/net/perfectdreams/loritta/morenitta/interactions/InteractionContext.kt @@ -2,8 +2,10 @@ package net.perfectdreams.loritta.morenitta.interactions import dev.minn.jda.ktx.coroutines.await import dev.minn.jda.ktx.messages.InlineMessage +import dev.minn.jda.ktx.messages.MessageCreate import net.dv8tion.jda.api.entities.Guild import net.dv8tion.jda.api.entities.Member +import net.dv8tion.jda.api.entities.Message import net.dv8tion.jda.api.interactions.InteractionHook import net.dv8tion.jda.api.interactions.callbacks.IReplyCallback import net.dv8tion.jda.api.utils.messages.MessageCreateBuilder @@ -63,6 +65,14 @@ abstract class InteractionContext( } } + suspend fun sendMessage(channelId: Long, block: suspend InlineMessage<*>.() -> Unit): Message? { + val builtMessage = MessageCreate { + block() + } + + return this.loritta.jda.getTextChannelById(channelId)?.sendMessage(builtMessage)?.await() + } + suspend inline fun chunkedReply(ephemeral: Boolean, builder: ChunkedMessageBuilder.() -> Unit = {}) { // Chunked replies are replies that are chunked into multiple messages, depending on the length of the content val createdMessage = ChunkedMessageBuilder().apply(builder)