From 5dae1f9af3c2113e9f582c63a02dc2a9d82d87af Mon Sep 17 00:00:00 2001 From: NoComment1105 Date: Wed, 7 Sep 2022 23:15:32 +0100 Subject: [PATCH 01/25] Make `getUtilityLogOrFirst` better and more usable in other situations --- .../lilybot/extensions/util/GalleryChannel.kt | 7 ++++--- .../lilybot/extensions/util/ModUtilities.kt | 4 ++-- .../lilybot/extensions/util/RoleMenu.kt | 11 ++++++----- .../lilybot/extensions/util/Tags.kt | 8 ++++---- .../lilybot/extensions/util/ThreadControl.kt | 9 +++++---- .../org/hyacinthbots/lilybot/utils/_Utils.kt | 18 +++++++++++++----- 6 files changed, 34 insertions(+), 23 deletions(-) diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/GalleryChannel.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/GalleryChannel.kt index 9ab96f32..47172e3b 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/GalleryChannel.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/GalleryChannel.kt @@ -22,10 +22,11 @@ import dev.kord.core.exception.EntityNotFoundException import dev.kord.rest.builder.message.create.embed import kotlinx.coroutines.delay import org.hyacinthbots.lilybot.database.collections.GalleryChannelCollection +import org.hyacinthbots.lilybot.extensions.config.ConfigOptions import org.hyacinthbots.lilybot.extensions.config.ConfigType import org.hyacinthbots.lilybot.utils.botHasChannelPerms +import org.hyacinthbots.lilybot.utils.getChannelOrFirstUsable import org.hyacinthbots.lilybot.utils.getLoggingChannelWithPerms -import org.hyacinthbots.lilybot.utils.getUtilityLogOrFirst /** * The class the holds the systems that allow a guild to set a channel as a gallery channel. @@ -63,7 +64,7 @@ class GalleryChannel : Extension() { val utilityLog = getLoggingChannelWithPerms( guild!!.asGuild(), - getUtilityLogOrFirst(guild)?.id, + getChannelOrFirstUsable(ConfigOptions.UTILITY_LOG, guild)?.id, ConfigType.UTILITY, interactionResponse ) @@ -114,7 +115,7 @@ class GalleryChannel : Extension() { val utilityLog = getLoggingChannelWithPerms( guild!!.asGuild(), - getUtilityLogOrFirst(guild)?.id, + getChannelOrFirstUsable(ConfigOptions.UTILITY_LOG, guild)?.id, ConfigType.UTILITY, interactionResponse ) diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/ModUtilities.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/ModUtilities.kt index fe377d9b..59ed07e7 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/ModUtilities.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/ModUtilities.kt @@ -48,8 +48,8 @@ import org.hyacinthbots.lilybot.extensions.config.ConfigType import org.hyacinthbots.lilybot.utils.TEST_GUILD_ID import org.hyacinthbots.lilybot.utils.botHasChannelPerms import org.hyacinthbots.lilybot.utils.configPresent +import org.hyacinthbots.lilybot.utils.getChannelOrFirstUsable import org.hyacinthbots.lilybot.utils.getLoggingChannelWithPerms -import org.hyacinthbots.lilybot.utils.getUtilityLogOrFirst import org.hyacinthbots.lilybot.utils.updateDefaultPresence /** @@ -190,7 +190,7 @@ class ModUtilities : Extension() { val utilityLog = getLoggingChannelWithPerms( guild!!.asGuild(), - getUtilityLogOrFirst(guild)?.id, + getChannelOrFirstUsable(ConfigOptions.UTILITY_LOG, guild)?.id, ConfigType.UTILITY, interactionResponse ) diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/RoleMenu.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/RoleMenu.kt index ece8ab03..fc04aec6 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/RoleMenu.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/RoleMenu.kt @@ -38,10 +38,11 @@ import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.toList import org.hyacinthbots.lilybot.database.collections.RoleMenuCollection +import org.hyacinthbots.lilybot.extensions.config.ConfigOptions import org.hyacinthbots.lilybot.extensions.config.ConfigType import org.hyacinthbots.lilybot.utils.botHasChannelPerms +import org.hyacinthbots.lilybot.utils.getChannelOrFirstUsable import org.hyacinthbots.lilybot.utils.getLoggingChannelWithPerms -import org.hyacinthbots.lilybot.utils.getUtilityLogOrFirst import org.hyacinthbots.lilybot.utils.utilsLogger /** @@ -121,7 +122,7 @@ class RoleMenu : Extension() { val utilityLog = getLoggingChannelWithPerms( guild!!.asGuild(), - getUtilityLogOrFirst(guild)?.id, + getChannelOrFirstUsable(ConfigOptions.UTILITY_LOG, guild)?.id, ConfigType.UTILITY, interactionResponse ) @@ -218,7 +219,7 @@ class RoleMenu : Extension() { val utilityLog = getLoggingChannelWithPerms( guild!!.asGuild(), - getUtilityLogOrFirst(guild)?.id, + getChannelOrFirstUsable(ConfigOptions.UTILITY_LOG, guild)?.id, ConfigType.UTILITY, interactionResponse ) @@ -287,7 +288,7 @@ class RoleMenu : Extension() { val utilityLog = getLoggingChannelWithPerms( guild!!.asGuild(), - getUtilityLogOrFirst(guild)?.id, + getChannelOrFirstUsable(ConfigOptions.UTILITY_LOG, guild)?.id, ConfigType.UTILITY, interactionResponse ) @@ -394,7 +395,7 @@ class RoleMenu : Extension() { val utilityLog = getLoggingChannelWithPerms( guild!!.asGuild(), - getUtilityLogOrFirst(guild)?.id, + getChannelOrFirstUsable(ConfigOptions.UTILITY_LOG, guild)?.id, ConfigType.UTILITY, interactionResponse ) diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/Tags.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/Tags.kt index ad9ad24e..a70162b7 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/Tags.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/Tags.kt @@ -32,8 +32,8 @@ import org.hyacinthbots.lilybot.extensions.config.ConfigOptions import org.hyacinthbots.lilybot.extensions.config.ConfigType import org.hyacinthbots.lilybot.utils.botHasChannelPerms import org.hyacinthbots.lilybot.utils.configPresent +import org.hyacinthbots.lilybot.utils.getChannelOrFirstUsable import org.hyacinthbots.lilybot.utils.getLoggingChannelWithPerms -import org.hyacinthbots.lilybot.utils.getUtilityLogOrFirst /** * The class that holds the commands to create tags commands. @@ -219,7 +219,7 @@ class Tags : Extension() { val utilityLog = getLoggingChannelWithPerms( guild!!.asGuild(), - getUtilityLogOrFirst(guild)?.id, + getChannelOrFirstUsable(ConfigOptions.UTILITY_LOG, guild)?.id, ConfigType.UTILITY, interactionResponse ) @@ -306,7 +306,7 @@ class Tags : Extension() { val utilityLog = getLoggingChannelWithPerms( guild!!.asGuild(), - getUtilityLogOrFirst(guild)?.id, + getChannelOrFirstUsable(ConfigOptions.UTILITY_LOG, guild)?.id, ConfigType.UTILITY, interactionResponse ) @@ -345,7 +345,7 @@ class Tags : Extension() { val utilityLog = getLoggingChannelWithPerms( guild!!.asGuild(), - getUtilityLogOrFirst(guild)?.id, + getChannelOrFirstUsable(ConfigOptions.UTILITY_LOG, guild)?.id, ConfigType.UTILITY, interactionResponse ) diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/ThreadControl.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/ThreadControl.kt index 3f1da501..1be78a06 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/ThreadControl.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/ThreadControl.kt @@ -41,10 +41,11 @@ import dev.kord.rest.builder.message.create.embed import kotlinx.datetime.Clock import org.hyacinthbots.lilybot.database.collections.ModerationConfigCollection import org.hyacinthbots.lilybot.database.collections.ThreadsCollection +import org.hyacinthbots.lilybot.extensions.config.ConfigOptions import org.hyacinthbots.lilybot.extensions.config.ConfigType import org.hyacinthbots.lilybot.utils.botHasChannelPerms +import org.hyacinthbots.lilybot.utils.getChannelOrFirstUsable import org.hyacinthbots.lilybot.utils.getLoggingChannelWithPerms -import org.hyacinthbots.lilybot.utils.getUtilityLogOrFirst class ThreadControl : Extension() { @@ -161,7 +162,7 @@ class ThreadControl : Extension() { val utilityLog = getLoggingChannelWithPerms( guild!!.asGuild(), - getUtilityLogOrFirst(guild)?.id, + getChannelOrFirstUsable(ConfigOptions.UTILITY_LOG, guild)?.id, ConfigType.UTILITY, interactionResponse ) @@ -195,7 +196,7 @@ class ThreadControl : Extension() { utilityLog.createMessage { embed { - title = "Thread ownership transfered" + title = "Thread ownership transferred" field { name = "Previous owner" value = "${oldOwner.mention} ${oldOwner.tag}" @@ -231,7 +232,7 @@ class ThreadControl : Extension() { val utilityLog = getLoggingChannelWithPerms( guild!!.asGuild(), - getUtilityLogOrFirst(guild)?.id, + getChannelOrFirstUsable(ConfigOptions.UTILITY_LOG, guild)?.id, ConfigType.UTILITY, interactionResponse ) diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/utils/_Utils.kt b/src/main/kotlin/org/hyacinthbots/lilybot/utils/_Utils.kt index cc82e665..a9409e5e 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/utils/_Utils.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/utils/_Utils.kt @@ -530,17 +530,25 @@ suspend inline fun getLoggingChannelWithPerms( getLoggingChannelWithPerms(inputGuild, targetChannel, configType, null) /** - * A small function to get the utility log of a guild or the first available channel. + * A small function to get a log of a guild or the first available channel. * + * @param configOption The option to get the channel of * @param guild The guild for the channel * @return The utility log or the first usable channel + * @throws IllegalArgumentException when the [configOption] is invalid * @author NoComment1105 * @since 4.0.1 */ -suspend inline fun getUtilityLogOrFirst(guild: GuildBehavior?): GuildMessageChannel? { - val config = UtilityConfigCollection().getConfig(guild!!.id) - return if (config?.utilityLogChannel != null) { - guild.getChannelOf(config.utilityLogChannel) +suspend inline fun getChannelOrFirstUsable(configOption: ConfigOptions, guild: GuildBehavior?): GuildMessageChannel? { + val channel = when (configOption) { + ConfigOptions.ACTION_LOG -> ModerationConfigCollection().getConfig(guild!!.id)?.channel + ConfigOptions.MESSAGE_LOG -> LoggingConfigCollection().getConfig(guild!!.id)?.messageChannel + ConfigOptions.MEMBER_LOG -> LoggingConfigCollection().getConfig(guild!!.id)?.memberLog + ConfigOptions.UTILITY_LOG -> UtilityConfigCollection().getConfig(guild!!.id)?.utilityLogChannel + else -> throw IllegalArgumentException("Config Option $configOption does not point to a channel.") + } + return if (channel != null) { + guild.getChannelOf(channel) } else { guild.asGuild().getSystemChannel() ?: getFirstUsableChannel(guild.asGuild()) } From 984330057c18f1e30dd9ad424c1e8a71f7df6ddc Mon Sep 17 00:00:00 2001 From: NoComment1105 Date: Wed, 7 Sep 2022 23:28:31 +0100 Subject: [PATCH 02/25] Make some small changes to log uploading to try fix "required content missing" errors --- .../lilybot/extensions/events/LogUploading.kt | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/LogUploading.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/LogUploading.kt index aba96ba5..6ddb5f12 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/LogUploading.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/LogUploading.kt @@ -131,23 +131,21 @@ class LogUploading : Extension() { if (attachmentFileExtension in logFileExtensions) { val logBytes = attachment.download() - val builder = StringBuilder() - - if (attachmentFileExtension != "gz") { + val logContent: String = if (attachmentFileExtension != "gz") { // If the file is not a gz log, we just decode it - builder.append(logBytes.decodeToString()) + logBytes.decodeToString() } else { // If the file is a gz log, we convert it to a byte array, // and unzip it val bis = ByteArrayInputStream(logBytes) val gis = GZIPInputStream(bis) - builder.append(String(gis.readAllBytes())) + gis.readAllBytes().decodeToString() } // Ask the user to remove NEC to ease the debugging on the support team val necText = "at Not Enough Crashes" - val indexOfNECText = builder.indexOf(necText) + val indexOfNECText = logContent.indexOf(necText) if (indexOfNECText != -1) { uploadChannel.createEmbed { title = "Not Enough Crashes detected in logs" @@ -203,7 +201,7 @@ class LogUploading : Extension() { } try { - val response = postToMCLogs(builder.toString()) + val response = postToMCLogs(logContent) uploadMessage.edit { embed { From a8167ed0cbb178a61a07421d51ebfc05b063a4d9 Mon Sep 17 00:00:00 2001 From: NoComment1105 Date: Thu, 8 Sep 2022 08:03:51 +0100 Subject: [PATCH 03/25] Fix reminder interval field being broken --- .../org/hyacinthbots/lilybot/extensions/util/Reminders.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/Reminders.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/Reminders.kt index b97391a8..653edf98 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/Reminders.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/Reminders.kt @@ -831,7 +831,7 @@ class Reminders : Extension() { /** The interval at which you want the reminder to repeat. */ val repeatingInterval by coalescingOptionalDuration { - name = "repeatingInterval" + name = "repeating-interval" description = "How often should the reminder repeat?" } } From 033b8a2d50b83e5bb2832554f383449e081d085c Mon Sep 17 00:00:00 2001 From: NoComment1105 Date: Thu, 8 Sep 2022 17:45:00 +0100 Subject: [PATCH 04/25] Add config view command --- .../lilybot/extensions/config/Config.kt | 171 +++++++++++++++++- 1 file changed, 170 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/config/Config.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/config/Config.kt index c64c1b9d..b69c9dc6 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/config/Config.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/config/Config.kt @@ -33,6 +33,7 @@ import dev.kord.core.event.interaction.ModalSubmitInteractionCreateEvent import dev.kord.rest.builder.message.EmbedBuilder import dev.kord.rest.builder.message.create.embed import dev.kord.rest.builder.message.modify.embed +import kotlinx.datetime.Clock import org.hyacinthbots.lilybot.database.collections.LoggingConfigCollection import org.hyacinthbots.lilybot.database.collections.ModerationConfigCollection import org.hyacinthbots.lilybot.database.collections.SupportConfigCollection @@ -578,6 +579,161 @@ suspend fun Config.configCommand() = unsafeSlashCommand { } } } + + ephemeralSubCommand(::ViewArgs) { + name = "view" + description = "View the current config that you have set" + + check { + anyGuild() + hasPermission(Permission.ManageGuild) + } + + action { + when (arguments.config) { + ConfigType.MODERATION.name -> { + val config = ModerationConfigCollection().getConfig(guild!!.id) + if (config == null) { + respond { + content = "There is no moderation config for this guild" + } + return@action + } + + respond { + embed { + title = "Current moderation config" + description = "This is the current moderation config for this guild" + field { + name = "Enabled/Disabled" + value = if (config.enabled) "Enabled" else "Disabled" + } + field { + name = "Moderators" + value = config.role?.let { guild!!.getRoleOrNull(it)?.mention } ?: "Disabled" + } + field { + name = "Action log" + value = config.channel?.let { guild!!.getChannelOrNull(it)?.mention } ?: "Disabled" + } + field { + name = "Log publicly" + value = when (config.publicLogging) { + true -> "True" + false -> "Disabled" + null -> "Disabled" + } + } + timestamp = Clock.System.now() + } + } + } + + ConfigType.LOGGING.name -> { + val config = LoggingConfigCollection().getConfig(guild!!.id) + if (config == null) { + respond { + content = "There is no logging config for this guild" + } + return@action + } + + respond { + embed { + title = "Current logging config" + description = "This is the current logging config for this guild" + field { + name = "Message logs" + value = if (config.enableMessageLogs) { + "Enabled\n" + + "${config.messageChannel?.let { guild!!.getChannelOrNull(it)?.mention }} " + + "${config.messageChannel?.let { guild!!.getChannelOrNull(it)?.name }}" + } else { + "Disabled" + } + } + field { + name = "Member logs" + value = if (config.enableMemberLogs) { + "Enabled\n" + + "${config.memberLog?.let { guild!!.getChannelOrNull(it)?.mention }} " + + "${config.memberLog?.let { guild!!.getChannelOrNull(it)?.name }} " + } else { + "Disabled" + } + } + timestamp = Clock.System.now() + } + } + } + + ConfigType.SUPPORT.name -> { + val config = SupportConfigCollection().getConfig(guild!!.id) + if (config == null) { + respond { + content = "There is no support config for this guild" + } + return@action + } + + respond { + embed { + title = "Current support config" + description = "This is the current support config for this guild" + field { + name = "Enabled/Disabled" + value = if (config.enabled) "Enabled" else "Disabled" + } + field { + name = "Channel" + value = "${config.channel?.let { guild!!.getChannelOrNull(it)?.mention }} " + + "${config.channel?.let { guild!!.getChannelOrNull(it)?.name }}" + } + field { + name = "Role" + value = "${config.role?.let { guild!!.getRoleOrNull(it)?.mention }} " + + "${config.role?.let { guild!!.getRoleOrNull(it)?.name }}" + } + field { + name = "Custom message" + value = + if (config.message != null) "${config.message.substring(0, 500)} ..." else "Default" + } + timestamp = Clock.System.now() + } + } + } + + ConfigType.UTILITY.name -> { + val config = UtilityConfigCollection().getConfig(guild!!.id) + if (config == null) { + respond { + content = "There is no utility config for this guild" + } + return@action + } + + respond { + embed { + title = "Current utility config" + description = "This is the current utility config for this guild" + field { + name = "Log uploading" + value = if (config.disableLogUploading) "Disabled" else "Enabled" + } + field { + name = "Channel" + value = + "${config.utilityLogChannel?.let {guild!!.getChannelOrNull(it)?.mention } ?: "None" + } ${config.utilityLogChannel?.let {guild!!.getChannelOrNull(it)?.name } ?: "" }" + } + timestamp = Clock.System.now() + } + } + } + } + } + } } class SupportArgs : Arguments() { @@ -671,6 +827,19 @@ class ClearArgs : Arguments() { } } +class ViewArgs : Arguments() { + val config by stringChoice { + name = "config-type" + description = "The type of config to clear" + choices = mutableMapOf( + "support" to ConfigType.SUPPORT.name, + "moderation" to ConfigType.MODERATION.name, + "logging" to ConfigType.LOGGING.name, + "utility" to ConfigType.UTILITY.name, + ) + } +} + /** * Checks the moderation config and returns where the message needs to be sent. * @@ -689,7 +858,7 @@ suspend inline fun checkChannel( val toReturn: GuildMessageChannel? if (ModerationConfigCollection().getConfig(guild!!.id) == null || !ModerationConfigCollection().getConfig(guild.id)!!.enabled || - channelIdToCheck == null + channelIdToCheck == null ) { toReturn = getLoggingChannelWithPerms( guild.asGuild(), From be9dc1d7d4f4373d44392a346e31736d4b5974da Mon Sep 17 00:00:00 2001 From: trainb0y <66213737+trainb0y@users.noreply.github.com> Date: Sat, 10 Sep 2022 14:35:01 -0600 Subject: [PATCH 05/25] Log Message Edits (#204) * Message edit logging, take two * Update delete logging to match edit logging * Cleanup and make detekt happy * Make message edit logging a separate option from delete logging * Add database migration and fix a silly --- .../org/hyacinthbots/lilybot/LilyBot.kt | 2 + .../lilybot/database/entities/Config.kt | 6 +- .../lilybot/database/migrations/Migrator.kt | 3 + .../database/migrations/config/configV1.kt | 16 +- .../lilybot/extensions/config/Config.kt | 28 ++- .../extensions/config/ConfigOptions.kt | 7 +- .../extensions/events/MessageDelete.kt | 210 +++++++----------- .../lilybot/extensions/events/MessageEdit.kt | 141 ++++++++++++ .../org/hyacinthbots/lilybot/utils/_Utils.kt | 49 +++- 9 files changed, 312 insertions(+), 150 deletions(-) create mode 100644 src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/MessageEdit.kt diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/LilyBot.kt b/src/main/kotlin/org/hyacinthbots/lilybot/LilyBot.kt index 8cfd87b9..cdcb3119 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/LilyBot.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/LilyBot.kt @@ -18,6 +18,7 @@ import org.hyacinthbots.lilybot.extensions.config.GuildLogging import org.hyacinthbots.lilybot.extensions.events.LogUploading import org.hyacinthbots.lilybot.extensions.events.MemberLogging import org.hyacinthbots.lilybot.extensions.events.MessageDelete +import org.hyacinthbots.lilybot.extensions.events.MessageEdit import org.hyacinthbots.lilybot.extensions.events.ThreadInviter import org.hyacinthbots.lilybot.extensions.moderation.Report import org.hyacinthbots.lilybot.extensions.moderation.TemporaryModeration @@ -80,6 +81,7 @@ suspend fun main() { add(::LogUploading) add(::MemberLogging) add(::MessageDelete) + add(::MessageEdit) add(::ModUtilities) add(::PublicUtilities) add(::Reminders) diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/database/entities/Config.kt b/src/main/kotlin/org/hyacinthbots/lilybot/database/entities/Config.kt index 96471b2e..188dbf36 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/database/entities/Config.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/database/entities/Config.kt @@ -8,7 +8,8 @@ import kotlinx.serialization.Serializable * disable certain configurations. * * @param guildId The ID of the guild the config is for - * @param enableMessageLogs If edited and deleted messages should be logged + * @param enableMessageDeleteLogs If deleted messages should be logged + * @param enableMessageEditLogs If edited messages should be logged * @param messageChannel The channel to send message logs to * @param enableMemberLogs If users joining or leaving the guild should be logged * @param memberLog The channel to send member logs to @@ -17,7 +18,8 @@ import kotlinx.serialization.Serializable @Serializable data class LoggingConfigData( val guildId: Snowflake, - val enableMessageLogs: Boolean, + val enableMessageDeleteLogs: Boolean, + val enableMessageEditLogs: Boolean, val messageChannel: Snowflake?, val enableMemberLogs: Boolean, val memberLog: Snowflake?, diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/Migrator.kt b/src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/Migrator.kt index c07e440e..103589ba 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/Migrator.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/Migrator.kt @@ -75,6 +75,9 @@ object Migrator : KordExKoinComponent { suspend fun migrateConfig() { logger.info { "Starting config database migration" } + // TODO Remove this line once the migration is done because nc is an absolute clown and got versions out of sync + db.configDatabase.dropCollection("configMetaData") + // ^ var meta = configMetaCollection.get() if (meta == null) { diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/config/configV1.kt b/src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/config/configV1.kt index 8ac635f1..d63e0460 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/config/configV1.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/config/configV1.kt @@ -1,8 +1,20 @@ package org.hyacinthbots.lilybot.database.migrations.config +import org.hyacinthbots.lilybot.database.entities.LoggingConfigData import org.litote.kmongo.coroutine.CoroutineDatabase +import org.litote.kmongo.exists +import org.litote.kmongo.setValue -@Suppress("UnusedPrivateMember") suspend fun configV1(configDb: CoroutineDatabase) { - // Empty until required + with(configDb.getCollection("loggingConfigData")) { + updateMany( + LoggingConfigData::enableMessageEditLogs exists false, + setValue(LoggingConfigData::enableMessageEditLogs, false) + ) + } + + configDb.getCollection("loggingConfigData").updateMany( + "{}", + "{\$rename: {enableMessageLogs: \"enableMessageDeleteLogs\"}}" + ) } diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/config/Config.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/config/Config.kt index b69c9dc6..832a4189 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/config/Config.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/config/Config.kt @@ -306,16 +306,24 @@ suspend fun Config.configCommand() = unsafeSlashCommand { if (arguments.enableMemberLogging && arguments.memberLog == null) { respond { content = "You must specify a channel to log members joining and leaving to!" } return@action - } else if (arguments.enableMessageLogs && arguments.messageLogs == null) { - respond { content = "You must specify a channel to log deleted messages to!" } + } else if ((arguments.enableMessageDeleteLogs || arguments.enableMessageEditLogs) && arguments.messageLogs == null) { + respond { content = "You must specify a channel to log deleted/edited messages to!" } return@action } suspend fun EmbedBuilder.loggingEmbed() { title = "Configuration: Logging" field { - name = "Message Logs" - value = if (arguments.enableMessageLogs && arguments.messageLogs?.mention != null) { + name = "Message Delete Logs" + value = if (arguments.enableMessageDeleteLogs && arguments.messageLogs?.mention != null) { + arguments.messageLogs!!.mention + } else { + "Disabled" + } + } + field { + name = "Message Edit Logs" + value = if (arguments.enableMessageEditLogs && arguments.messageLogs?.mention != null) { arguments.messageLogs!!.mention } else { "Disabled" @@ -344,7 +352,8 @@ suspend fun Config.configCommand() = unsafeSlashCommand { LoggingConfigCollection().setConfig( LoggingConfigData( guild!!.id, - arguments.enableMessageLogs, + arguments.enableMessageDeleteLogs, + arguments.enableMessageEditLogs, arguments.messageLogs?.id, arguments.enableMemberLogging, arguments.memberLog?.id @@ -781,11 +790,16 @@ class ModerationArgs : Arguments() { } class LoggingArgs : Arguments() { - val enableMessageLogs by boolean { - name = "enable-message-logs" + val enableMessageDeleteLogs by boolean { + name = "enable-delete-logs" description = "Enable logging of message deletions" } + val enableMessageEditLogs by boolean { + name = "enable-edit-logs" + description = "Enable logging of message edits" + } + val enableMemberLogging by boolean { name = "enable-member-logging" description = "Enable logging of members joining and leaving the guild" diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/config/ConfigOptions.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/config/ConfigOptions.kt index b4584f2d..c2ca7044 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/config/ConfigOptions.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/config/ConfigOptions.kt @@ -27,8 +27,11 @@ enum class ConfigOptions { /** The option that stores whether to log a moderation action publicly. */ LOG_PUBLICLY, - /** The option that stores whether the logging config is enabled or not. */ - MESSAGE_LOGGING_ENABLED, + /** The option that stores whether message delete logging is enabled. */ + MESSAGE_DELETE_LOGGING_ENABLED, + + /** The option that stores whether message edit logging is enabled. */ + MESSAGE_EDIT_LOGGING_ENABLED, /** The options that stores the message logging channel. */ MESSAGE_LOG, diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/MessageDelete.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/MessageDelete.kt index 82f3f1d5..5f195a74 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/MessageDelete.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/MessageDelete.kt @@ -4,14 +4,13 @@ import com.kotlindiscord.kord.extensions.DISCORD_PINK import com.kotlindiscord.kord.extensions.checks.anyGuild import com.kotlindiscord.kord.extensions.extensions.Extension import com.kotlindiscord.kord.extensions.extensions.event +import com.kotlindiscord.kord.extensions.modules.extra.pluralkit.api.PKMessage import com.kotlindiscord.kord.extensions.modules.extra.pluralkit.events.ProxiedMessageDeleteEvent import com.kotlindiscord.kord.extensions.modules.extra.pluralkit.events.UnProxiedMessageDeleteEvent import dev.kord.core.behavior.channel.asChannelOf import dev.kord.core.behavior.channel.createMessage -import dev.kord.core.behavior.getChannelOf -import dev.kord.core.entity.Attachment +import dev.kord.core.entity.Message import dev.kord.core.entity.channel.GuildMessageChannel -import dev.kord.rest.builder.message.EmbedBuilder import dev.kord.rest.builder.message.create.embed import kotlinx.datetime.Clock import org.hyacinthbots.lilybot.database.collections.LoggingConfigCollection @@ -19,6 +18,8 @@ import org.hyacinthbots.lilybot.extensions.config.ConfigOptions import org.hyacinthbots.lilybot.extensions.config.ConfigType import org.hyacinthbots.lilybot.utils.configPresent import org.hyacinthbots.lilybot.utils.getLoggingChannelWithPerms +import org.hyacinthbots.lilybot.utils.ifNullOrEmpty +import org.hyacinthbots.lilybot.utils.trimmedContents /** * The class for logging deletion of messages to the guild message log. @@ -30,76 +31,33 @@ class MessageDelete : Extension() { override suspend fun setup() { /** - * Logs deleted messages in a guild to the message log channel designated in the config for that guild + * Logs proxied deleted messages in a guild to the message log channel designated in the config for that guild * @author NoComment1105 - * @since 2.0 + * @see onMessageDelete */ event { check { anyGuild() - configPresent(ConfigOptions.MESSAGE_LOGGING_ENABLED, ConfigOptions.MESSAGE_LOG) + configPresent(ConfigOptions.MESSAGE_DELETE_LOGGING_ENABLED, ConfigOptions.MESSAGE_LOG) failIf { event.message?.author?.id == kord.selfId } } action { - val config = LoggingConfigCollection().getConfig(event.getGuild().id) ?: return@action - val messageLog = - getLoggingChannelWithPerms(event.getGuild(), config.messageChannel!!, ConfigType.LOGGING) - ?: return@action - - val originalMessage = event.message - val proxiedMessage = event.pkMessage - val messageContent = if (originalMessage?.asMessageOrNull() != null) { - if (originalMessage.asMessageOrNull().content.length > 1024) { - originalMessage.asMessageOrNull().content.substring(0, 1024) + "..." - } else { - originalMessage.asMessageOrNull().content - } - } else { - null - } - val messageLocation = event.pkMessage.channel - val attachments = event.message?.attachments - val images: MutableSet = mutableSetOf() - attachments?.forEach { if (it.isImage) images += it } - - messageLog.createMessage { - embed { - color = DISCORD_PINK - author { - name = "Message deleted" - icon = proxiedMessage.member.avatarUrl - } - description = - "Location: ${event.getGuild().getChannelOf(messageLocation).mention}" + - "(${event.getGuild().getChannelOf(messageLocation).name})" - timestamp = Clock.System.now() - - fields(messageContent, attachments) - - field { - name = "Message Author:" - value = "System Member: ${proxiedMessage.member.name}\n" + - "Account: ${event.getGuild().getMember(proxiedMessage.sender).tag} " + - event.getGuild().getMember(proxiedMessage.sender).mention - inline = true - } - - field { - name = "Author ID:" - value = proxiedMessage.sender.toString() - } - } - } + onMessageDelete(event.getMessage(), event.pkMessage) } } + /** + * Logs unproxied deleted messages in a guild to the message log channel designated in the config for that guild. + * @author NoComment1105 + * @see onMessageDelete + */ event { check { anyGuild() - configPresent(ConfigOptions.MESSAGE_LOGGING_ENABLED, ConfigOptions.MESSAGE_LOG) + configPresent(ConfigOptions.MESSAGE_DELETE_LOGGING_ENABLED, ConfigOptions.MESSAGE_LOG) failIf { event.message?.author?.id == kord.selfId || event.message?.author?.isBot == true @@ -107,94 +65,78 @@ class MessageDelete : Extension() { } action { - val config = LoggingConfigCollection().getConfig(event.getGuild().id) ?: return@action - - val message = event.message + onMessageDelete(event.getMessage(), null) + } + } + } - val messageLog = - getLoggingChannelWithPerms(event.getGuild(), config.messageChannel!!, ConfigType.LOGGING) - ?: return@action + /** + * If message logging is enabled, sends an embed describing the message deletion to the guild's message log channel. + * + * @param message The deleted message + * @param proxiedMessage Extra data for PluralKit proxied messages + * @author trainb0y + */ + private suspend fun onMessageDelete(message: Message, proxiedMessage: PKMessage?) { + val guild = message.getGuild() + val config = LoggingConfigCollection().getConfig(guild.id) ?: return + val messageLog = + getLoggingChannelWithPerms(message.getGuild(), config.messageChannel!!, ConfigType.LOGGING) + ?: return + + messageLog.createMessage { + embed { + color = DISCORD_PINK + author { + name = "Message deleted" + icon = proxiedMessage?.member?.avatarUrl ?: message.author?.avatar?.url + } + description = + "Location: ${message.channel.mention}" + + "(${message.channel.asChannelOf().name})" + timestamp = Clock.System.now() + + field { + name = "Message contents" + value = message.trimmedContents().ifNullOrEmpty { "Failed to retrieve previous message contents" } + inline = false + } - val messageContent = if (message?.asMessageOrNull() != null) { - if (message.asMessageOrNull().content.length > 1024) { - message.asMessageOrNull().content.substring(0, 1024) + "..." - } else { - message.asMessageOrNull().content + if (message.attachments.isNotEmpty()) { + field { + name = "Attachments" + value = message.attachments.map { it.url }.joinToString { "\n" } + inline = false } - } else { - null } - message ?: return@action - - val messageLocation = event.channel.asChannelOf() - val attachments = event.message?.attachments - val images: MutableSet = mutableSetOf() - attachments?.forEach { if (it.isImage) images += it } - - messageLog.createMessage { - embed { - color = DISCORD_PINK - author { - name = "Message deleted" - icon = message.author?.avatar?.url - } - description = - "Location: ${messageLocation.mention}" + - "(${messageLocation.name})" - timestamp = Clock.System.now() - - fields(messageContent, attachments) + if (proxiedMessage != null) { + field { + name = "Message Author:" + value = "System Member: ${proxiedMessage.member.name}\n" + + "Account: ${guild.getMember(proxiedMessage.sender).tag} " + + guild.getMember(proxiedMessage.sender).mention + inline = true + } - field { - name = "Message Author:" - value = - "${message.author?.tag ?: "Failed to get author of message"} ${message.author?.mention ?: ""}" - inline = true - } + field { + name = "Author ID:" + value = proxiedMessage.sender.toString() + } + } else { + field { + name = "Message Author:" + value = + "${message.author?.tag ?: "Failed to get author of message"} ${message.author?.mention ?: ""}" + inline = true + } - field { - name = "Author ID:" - value = message.author?.id.toString() - } + field { + name = "Author ID:" + value = message.author?.id.toString() } } } } } } - -/** - * Adds the common fields to a deleted message embed. - * - * @param messageContent The content of the message. - * @param attachments The attachments of the message. - * - * @author NoComment1105 - * @since 3.6.0 - */ -private fun EmbedBuilder.fields(messageContent: String?, attachments: Set?) { - field { - name = "Message contents" - value = - if (messageContent.isNullOrEmpty()) { - "Failed to retrieve message contents" - } else { - messageContent - } - inline = false - } - if (!attachments.isNullOrEmpty()) { - val attachmentUrls = StringBuilder() - attachments.forEach { - attachmentUrls.append( - it.url + "\n" - ) - } - field { - name = "Attachments" - value = attachmentUrls.trim().toString() - inline = false - } - } -} diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/MessageEdit.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/MessageEdit.kt new file mode 100644 index 00000000..5037c536 --- /dev/null +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/MessageEdit.kt @@ -0,0 +1,141 @@ +package org.hyacinthbots.lilybot.extensions.events + +import com.kotlindiscord.kord.extensions.DISCORD_YELLOW +import com.kotlindiscord.kord.extensions.checks.anyGuild +import com.kotlindiscord.kord.extensions.extensions.Extension +import com.kotlindiscord.kord.extensions.extensions.event +import com.kotlindiscord.kord.extensions.modules.extra.pluralkit.api.PKMessage +import com.kotlindiscord.kord.extensions.modules.extra.pluralkit.events.ProxiedMessageUpdateEvent +import com.kotlindiscord.kord.extensions.modules.extra.pluralkit.events.UnProxiedMessageUpdateEvent +import dev.kord.core.behavior.channel.asChannelOf +import dev.kord.core.behavior.channel.createMessage +import dev.kord.core.entity.Message +import dev.kord.core.entity.channel.GuildMessageChannel +import dev.kord.rest.builder.message.create.embed +import kotlinx.datetime.Clock +import org.hyacinthbots.lilybot.database.collections.LoggingConfigCollection +import org.hyacinthbots.lilybot.extensions.config.ConfigOptions +import org.hyacinthbots.lilybot.extensions.config.ConfigType +import org.hyacinthbots.lilybot.utils.configPresent +import org.hyacinthbots.lilybot.utils.getLoggingChannelWithPerms +import org.hyacinthbots.lilybot.utils.ifNullOrEmpty +import org.hyacinthbots.lilybot.utils.trimmedContents + +/** + * The class for logging editing of messages to the guild message log. + * @since 4.1.0 + */ +class MessageEdit : Extension() { + override val name = "message-edit" + + override suspend fun setup() { + /** + * Logs edited messages to the message log channel. + * @see onMessageEdit + * @author trainb0y + */ + event { + check { + anyGuild() + configPresent(ConfigOptions.MESSAGE_EDIT_LOGGING_ENABLED, ConfigOptions.MESSAGE_LOG) + failIf { + event.message.asMessage().author?.id == kord.selfId + } + } + action { + onMessageEdit(event.getMessage(), event.old, null) + } + } + + /** + * Logs proxied edited messages to the message log channel. + * @see onMessageEdit + * @author trainb0y + */ + event { + check { + anyGuild() + configPresent(ConfigOptions.MESSAGE_EDIT_LOGGING_ENABLED, ConfigOptions.MESSAGE_LOG) + failIf { + event.message.asMessage().author?.id == kord.selfId + } + } + action { + onMessageEdit(event.getMessage(), event.old, event.pkMessage) + } + } + } + + /** + * If message logging is enabled, sends an embed describing the message edit to the guild's message log channel. + * + * @param message The current message + * @param old The original message + * @param proxiedMessage Extra data for PluralKit proxied messages + * @author trainb0y + */ + private suspend fun onMessageEdit(message: Message, old: Message?, proxiedMessage: PKMessage?) { + val guild = message.getGuild() + val config = LoggingConfigCollection().getConfig(guild.id) ?: return + val messageLog = + getLoggingChannelWithPerms(guild, config.messageChannel!!, ConfigType.LOGGING) + ?: return + + messageLog.createMessage { + embed { + color = DISCORD_YELLOW + author { + name = "Message Edited" + icon = proxiedMessage?.member?.avatarUrl ?: message.author?.avatar?.url + } + description = + "Location: ${message.channel.mention}" + + "(${message.channel.asChannelOf().name})" + timestamp = Clock.System.now() + + field { + name = "Previous contents" + value = old?.trimmedContents().ifNullOrEmpty { "Failed to retrieve previous message contents" } + inline = false + } + field { + name = "New contents" + value = message.trimmedContents().ifNullOrEmpty { "Failed to retrieve new message contents" } + inline = false + } + + if (message.attachments.isNotEmpty()) { + field { + name = "Attachments" + value = message.attachments.map { it.url }.joinToString { "\n" } + inline = false + } + } + if (proxiedMessage != null) { + field { + name = "Message Author:" + value = "System Member: ${proxiedMessage.member.name}\n" + + "Account: ${guild.getMember(proxiedMessage.sender).tag} " + + guild.getMember(proxiedMessage.sender).mention + inline = true + } + field { + name = "Author ID:" + value = proxiedMessage.sender.toString() + } + } else { + field { + name = "Message Author:" + value = + "${message.author?.tag ?: "Failed to get author of message"} ${message.author?.mention ?: ""}" + inline = true + } + field { + name = "Author ID:" + value = message.author?.id.toString() + } + } + } + } + } +} diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/utils/_Utils.kt b/src/main/kotlin/org/hyacinthbots/lilybot/utils/_Utils.kt index a9409e5e..4b719cac 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/utils/_Utils.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/utils/_Utils.kt @@ -21,6 +21,7 @@ import dev.kord.core.behavior.getChannelOfOrNull import dev.kord.core.behavior.interaction.response.FollowupPermittingInteractionResponseBehavior import dev.kord.core.behavior.interaction.response.createEphemeralFollowup import dev.kord.core.entity.Guild +import dev.kord.core.entity.Message import dev.kord.core.entity.User import dev.kord.core.entity.channel.GuildMessageChannel import dev.kord.core.entity.channel.NewsChannel @@ -170,13 +171,26 @@ suspend inline fun CheckContext<*>.configPresent(vararg configOptions: ConfigOpt } } - ConfigOptions.MESSAGE_LOGGING_ENABLED -> { + ConfigOptions.MESSAGE_DELETE_LOGGING_ENABLED -> { val loggingConfig = LoggingConfigCollection().getConfig(guildFor(event)!!.id) if (loggingConfig == null) { fail("Unable to access logging config for this guild! Please inform a member of staff.") break - } else if (!loggingConfig.enableMessageLogs) { - fail("Message logging is disabled for this guild!") + } else if (!loggingConfig.enableMessageDeleteLogs) { + fail("Message delete logging is disabled for this guild!") + break + } else { + pass() + } + } + + ConfigOptions.MESSAGE_EDIT_LOGGING_ENABLED -> { + val loggingConfig = LoggingConfigCollection().getConfig(guildFor(event)!!.id) + if (loggingConfig == null) { + fail("Unable to access logging config for this guild! Please inform a member of staff.") + break + } else if (!loggingConfig.enableMessageEditLogs) { + fail("Message edit logging is disabled for this guild!") break } else { pass() @@ -553,3 +567,32 @@ suspend inline fun getChannelOrFirstUsable(configOption: ConfigOptions, guild: G guild.asGuild().getSystemChannel() ?: getFirstUsableChannel(guild.asGuild()) } } + +/** + * Utility to get a string or a default value. + * Basically String.ifEmpty but works with nullable strings + * + * @return This, or defaultValue if this is null or empty + * @author trainb0y + * @since 4.1.0 + * @see String.ifEmpty + */ +fun String?.ifNullOrEmpty(defaultValue: () -> String): String = + if (this.isNullOrEmpty()) { + defaultValue() + } else { + this + } + +/** + * Get this message's contents, trimmed to 1024 characters. + * If the message exceeds that length, it will be truncated and an ellipsis appended. + * @author trainb0y + * @since 4.1.0 + */ +fun Message?.trimmedContents(): String? { + this ?: return null + return if (this.content.length > 1024) { + this.content.substring(0, 1020) + " ..." + } else this.content +} From 76fa5e913ca051303e02f557b78f829cc67cff01 Mon Sep 17 00:00:00 2001 From: NoComment1105 Date: Mon, 12 Sep 2022 08:11:42 +0100 Subject: [PATCH 06/25] Fix build and config logging words. --- .../lilybot/extensions/config/Config.kt | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/config/Config.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/config/Config.kt index 832a4189..0e1adf24 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/config/Config.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/config/Config.kt @@ -76,7 +76,7 @@ suspend fun Config.configCommand() = unsafeSlashCommand { if (supportConfig != null) { ackEphemeral() respondEphemeral { - content = "You already have a moderation configuration set. " + + content = "You already have a support configuration set. " + "Please clear it before attempting to set a new one." } return@action @@ -468,7 +468,9 @@ suspend fun Config.configCommand() = unsafeSlashCommand { interactionResponse )?.createMessage { embed { - title = "Configuration Cleared: ${arguments.config}" + title = "Configuration Cleared: ${arguments.config[0]}${ + arguments.config.substring(1, arguments.config.length).lowercase() + }" ModerationConfigCollection().getConfig(guild!!.id) ?: run { description = "Consider setting the moderation configuration to receive configuration " + "updates where you want them!" @@ -652,8 +654,18 @@ suspend fun Config.configCommand() = unsafeSlashCommand { title = "Current logging config" description = "This is the current logging config for this guild" field { - name = "Message logs" - value = if (config.enableMessageLogs) { + name = "Message delete logs" + value = if (config.enableMessageDeleteLogs) { + "Enabled\n" + + "${config.messageChannel?.let { guild!!.getChannelOrNull(it)?.mention }} " + + "${config.messageChannel?.let { guild!!.getChannelOrNull(it)?.name }}" + } else { + "Disabled" + } + } + field { + name = "Message edit logs" + value = if (config.enableMessageEditLogs) { "Enabled\n" + "${config.messageChannel?.let { guild!!.getChannelOrNull(it)?.mention }} " + "${config.messageChannel?.let { guild!!.getChannelOrNull(it)?.name }}" @@ -733,8 +745,9 @@ suspend fun Config.configCommand() = unsafeSlashCommand { field { name = "Channel" value = - "${config.utilityLogChannel?.let {guild!!.getChannelOrNull(it)?.mention } ?: "None" - } ${config.utilityLogChannel?.let {guild!!.getChannelOrNull(it)?.name } ?: "" }" + "${ + config.utilityLogChannel?.let { guild!!.getChannelOrNull(it)?.mention } ?: "None" + } ${config.utilityLogChannel?.let { guild!!.getChannelOrNull(it)?.name } ?: ""}" } timestamp = Clock.System.now() } From 10af2653b53a5eb087088ee2e516b266fe03eda1 Mon Sep 17 00:00:00 2001 From: NoComment1105 Date: Mon, 12 Sep 2022 09:51:54 +0100 Subject: [PATCH 07/25] Change polling seconds for reminder task to 1 to hopefully fix reminders sometimes not being sent --- .../org/hyacinthbots/lilybot/extensions/util/Reminders.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/Reminders.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/Reminders.kt index 653edf98..3d6aeb22 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/Reminders.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/Reminders.kt @@ -73,7 +73,7 @@ class Reminders : Extension() { override suspend fun setup() { /** Set the task to run every 30 seconds. */ - task = scheduler.schedule(30, pollingSeconds = 30, repeat = true, callback = ::postReminders) + task = scheduler.schedule(30, pollingSeconds = 1, repeat = true, callback = ::postReminders) /** * The command for reminders From e28921ee32b387320465bcbba731098dfcdea564 Mon Sep 17 00:00:00 2001 From: NoComment1105 Date: Mon, 12 Sep 2022 19:38:24 +0100 Subject: [PATCH 08/25] Replace deprecated `deleteMessageDays` --- .../extensions/moderation/TerminalModeration.kt | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/TerminalModeration.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/TerminalModeration.kt index 165b3e36..28a1dcca 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/TerminalModeration.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/TerminalModeration.kt @@ -16,6 +16,7 @@ import com.kotlindiscord.kord.extensions.extensions.ephemeralSlashCommand import com.kotlindiscord.kord.extensions.types.respond import com.kotlindiscord.kord.extensions.utils.dm import com.kotlindiscord.kord.extensions.utils.timeoutUntil +import com.kotlindiscord.kord.extensions.utils.toDuration import dev.kord.common.entity.Permission import dev.kord.core.behavior.ban import dev.kord.core.behavior.channel.createEmbed @@ -28,6 +29,8 @@ import dev.kord.rest.builder.message.create.embed import dev.kord.rest.request.KtorRequestException import kotlinx.coroutines.flow.toList import kotlinx.datetime.Clock +import kotlinx.datetime.DateTimePeriod +import kotlinx.datetime.TimeZone import mu.KotlinLogging import org.hyacinthbots.lilybot.database.collections.ModerationConfigCollection import org.hyacinthbots.lilybot.extensions.config.ConfigOptions @@ -106,8 +109,8 @@ class TerminalModeration : Extension() { // Run the ban task guild?.ban(userArg.id, builder = { - this.reason = arguments.reason - this.deleteMessagesDays = arguments.messages + reason = arguments.reason + deleteMessageDuration = DateTimePeriod(days = arguments.messages).toDuration(TimeZone.UTC) }) respond { @@ -262,8 +265,8 @@ class TerminalModeration : Extension() { // Ban the user, mark it as a soft-ban clearly guild?.ban(userArg.id, builder = { - this.reason = "${arguments.reason} + **SOFT-BAN**" - this.deleteMessagesDays = arguments.messages + reason = "${arguments.reason} + **SOFT-BAN**" + deleteMessageDuration = DateTimePeriod(days = arguments.messages).toDuration(TimeZone.UTC) }) respond { From 325d9a970f42ab1f18588944b0b2c13fe8c72e04 Mon Sep 17 00:00:00 2001 From: NoComment1105 Date: Mon, 12 Sep 2022 21:29:24 +0100 Subject: [PATCH 09/25] Log channel name for utility log, rather than enabled or disabled --- .../org/hyacinthbots/lilybot/extensions/config/Config.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/config/Config.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/config/Config.kt index 0e1adf24..546e3478 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/config/Config.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/config/Config.kt @@ -409,7 +409,7 @@ suspend fun Config.configCommand() = unsafeSlashCommand { field { name = "Utility Log" value = if (arguments.utilityLogChannel != null) { - "Enabled" + "${arguments.utilityLogChannel!!.mention} ${arguments.utilityLogChannel!!.data.name.value}" } else { "Disabled" } @@ -469,8 +469,8 @@ suspend fun Config.configCommand() = unsafeSlashCommand { )?.createMessage { embed { title = "Configuration Cleared: ${arguments.config[0]}${ - arguments.config.substring(1, arguments.config.length).lowercase() - }" + arguments.config.substring(1, arguments.config.length).lowercase() + }" ModerationConfigCollection().getConfig(guild!!.id) ?: run { description = "Consider setting the moderation configuration to receive configuration " + "updates where you want them!" From 89842141ea5fcb0da740560f41673547085703e3 Mon Sep 17 00:00:00 2001 From: NoComment <67918617+NoComment1105@users.noreply.github.com> Date: Tue, 13 Sep 2022 14:58:04 +0100 Subject: [PATCH 10/25] Run cleanups on a scheduler (#260) * Run cleanups on a scheduler * Apply changes from reviews, fix kdocs on data classes * Actually Serialize the class * Condense cleanup into one function * Run the scheduler one a day, rather than on a periodic schedule --- .../lilybot/database/entities/Config.kt | 36 ++++++------ .../database/entities/GalleryChannelData.kt | 4 +- .../database/entities/GuildLeaveTimeData.kt | 4 +- .../lilybot/database/entities/RemindMeData.kt | 18 +++--- .../lilybot/database/entities/RoleMenuData.kt | 8 +-- .../lilybot/database/entities/StatusData.kt | 2 +- .../lilybot/database/entities/TagsData.kt | 10 ++-- .../lilybot/database/entities/ThreadData.kt | 6 +- .../lilybot/database/entities/WarnData.kt | 6 +- .../lilybot/extensions/util/StartupHooks.kt | 55 ++++++++++--------- 10 files changed, 76 insertions(+), 73 deletions(-) diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/database/entities/Config.kt b/src/main/kotlin/org/hyacinthbots/lilybot/database/entities/Config.kt index 188dbf36..9df1e8da 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/database/entities/Config.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/database/entities/Config.kt @@ -7,12 +7,12 @@ import kotlinx.serialization.Serializable * The data for moderation configuration. The logging config stores where logs are sent to, and whether to enable or * disable certain configurations. * - * @param guildId The ID of the guild the config is for - * @param enableMessageDeleteLogs If deleted messages should be logged - * @param enableMessageEditLogs If edited messages should be logged - * @param messageChannel The channel to send message logs to - * @param enableMemberLogs If users joining or leaving the guild should be logged - * @param memberLog The channel to send member logs to + * @property guildId The ID of the guild the config is for + * @property enableMessageDeleteLogs If deleted messages should be logged + * @property enableMessageEditLogs If edited messages should be logged + * @property messageChannel The channel to send message logs to + * @property enableMemberLogs If users joining or leaving the guild should be logged + * @property memberLog The channel to send member logs to * @since 4.0.0 */ @Serializable @@ -29,10 +29,10 @@ data class LoggingConfigData( * The data for moderation configuration. The moderation config is what stores the data for moderation actions. The * channel for logging and the team for pinging. * - * @param guildId The ID of the guild the config is for - * @param enabled If the support module is enabled or not - * @param channel The ID of the action log for the guild - * @param role The ID of the moderation role for the guild + * @property guildId The ID of the guild the config is for + * @property enabled If the support module is enabled or not + * @property channel The ID of the action log for the guild + * @property role The ID of the moderation role for the guild * @since 4.0.0 */ @Serializable @@ -48,11 +48,11 @@ data class ModerationConfigData( * The data for support configuration. The support config stores the data for support functionality. Channel for the * place to create threads to and team for pinging into support threads. * - * @param guildId The ID of the guild the config is for - * @param enabled If the support module is enabled or not - * @param channel The ID of the support channel for the guild - * @param role The ID of the support team for the guild - * @param message The support message as a string, nullable + * @property guildId The ID of the guild the config is for + * @property enabled If the support module is enabled or not + * @property channel The ID of the support channel for the guild + * @property role The ID of the support team for the guild + * @property message The support message as a string, nullable * @since 4.0.0 */ @Serializable @@ -68,9 +68,9 @@ data class SupportConfigData( * The data for miscellaneous configuration. The miscellaneous config stores the data for enabling or disabling log * uploading. * - * @param guildId The ID of the guild the config is for - * @param disableLogUploading If log uploading is enabled or not - * @param utilityLogChannel The channel to log various utility actions too + * @property guildId The ID of the guild the config is for + * @property disableLogUploading If log uploading is enabled or not + * @property utilityLogChannel The channel to log various utility actions too * @since 4.0.0 */ @Serializable diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/database/entities/GalleryChannelData.kt b/src/main/kotlin/org/hyacinthbots/lilybot/database/entities/GalleryChannelData.kt index 6a79d23d..5c7944c8 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/database/entities/GalleryChannelData.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/database/entities/GalleryChannelData.kt @@ -6,8 +6,8 @@ import kotlinx.serialization.Serializable /** * The data for image channels in a guild. * - * @param guildId The ID of the guild the image channel is for - * @param channelId The ID of the image channel being set + * @property guildId The ID of the guild the image channel is for + * @property channelId The ID of the image channel being set * @since 3.3.0 */ @Serializable diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/database/entities/GuildLeaveTimeData.kt b/src/main/kotlin/org/hyacinthbots/lilybot/database/entities/GuildLeaveTimeData.kt index 14819679..53cf35c2 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/database/entities/GuildLeaveTimeData.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/database/entities/GuildLeaveTimeData.kt @@ -7,8 +7,8 @@ import kotlinx.serialization.Serializable /** * The data for when Lily leaves a guild. * - * @param guildId The ID of the guild Lily left - * @param guildLeaveTime The [Instant] that Lily left the guild + * @property guildId The ID of the guild Lily left + * @property guildLeaveTime The [Instant] that Lily left the guild * @since 3.2.0 */ @Serializable diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/database/entities/RemindMeData.kt b/src/main/kotlin/org/hyacinthbots/lilybot/database/entities/RemindMeData.kt index e2b7e7c6..65bf89f5 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/database/entities/RemindMeData.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/database/entities/RemindMeData.kt @@ -8,15 +8,15 @@ import kotlinx.serialization.Serializable /** * The data for reminders set by users. * - * @param initialSetTime The time the reminder was set - * @param guildId The ID of the guild the reminder was set in - * @param userId The ID of the user that would like to be reminded - * @param channelId The ID of the channel the reminder was set in - * @param remindTime The time the user would like to be reminded at - * @param originalMessageUrl The URL to the original message that set the reminder - * @param customMessage A custom message to attach to the reminder - * @param repeating Whether the reminder should repeat - * @param id The numerical ID of the reminder + * @property initialSetTime The time the reminder was set + * @property guildId The ID of the guild the reminder was set in + * @property userId The ID of the user that would like to be reminded + * @property channelId The ID of the channel the reminder was set in + * @property remindTime The time the user would like to be reminded at + * @property originalMessageUrl The URL to the original message that set the reminder + * @property customMessage A custom message to attach to the reminder + * @property repeating Whether the reminder should repeat + * @property id The numerical ID of the reminder * * @since 3.3.2 */ diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/database/entities/RoleMenuData.kt b/src/main/kotlin/org/hyacinthbots/lilybot/database/entities/RoleMenuData.kt index bd423f9d..0673bd2e 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/database/entities/RoleMenuData.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/database/entities/RoleMenuData.kt @@ -6,10 +6,10 @@ import kotlinx.serialization.Serializable /** * The data for role menus. * - * @param messageId The ID of the message of the role menu - * @param channelId The ID of the channel the role menu is in - * @param guildId The ID of the guild the role menu is in - * @param roles A [MutableList] of the role IDs associated with this role menu. + * @property messageId The ID of the message of the role menu + * @property channelId The ID of the channel the role menu is in + * @property guildId The ID of the guild the role menu is in + * @property roles A [MutableList] of the role IDs associated with this role menu. * @since 3.4.0 */ @Serializable diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/database/entities/StatusData.kt b/src/main/kotlin/org/hyacinthbots/lilybot/database/entities/StatusData.kt index 0d0aac95..090244d0 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/database/entities/StatusData.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/database/entities/StatusData.kt @@ -5,7 +5,7 @@ import kotlinx.serialization.Serializable /** * The data for the bot status. * - * @param status The string value that will be seen in the bots presence + * @property status The string value that will be seen in the bots presence * @since 3.0.0 */ @Serializable diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/database/entities/TagsData.kt b/src/main/kotlin/org/hyacinthbots/lilybot/database/entities/TagsData.kt index 9a113c05..78b00ffb 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/database/entities/TagsData.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/database/entities/TagsData.kt @@ -6,11 +6,11 @@ import kotlinx.serialization.Serializable /** * The data of guild tags, which are stored in the database. * - * @param guildId The ID of the guild the tag will be saved for - * @param name The named identifier of the tag - * @param tagTitle The title of the created tag - * @param tagValue The value of the created tag - * @param tagAppearance The appearance of the created tag + * @property guildId The ID of the guild the tag will be saved for + * @property name The named identifier of the tag + * @property tagTitle The title of the created tag + * @property tagValue The value of the created tag + * @property tagAppearance The appearance of the created tag * @since 3.1.0 */ @Serializable diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/database/entities/ThreadData.kt b/src/main/kotlin/org/hyacinthbots/lilybot/database/entities/ThreadData.kt index 16860ebb..399bbb38 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/database/entities/ThreadData.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/database/entities/ThreadData.kt @@ -6,9 +6,9 @@ import kotlinx.serialization.Serializable /** * The data for threads. * - * @param threadId The ID of the thread - * @param ownerId The ID of the thread's owner - * @param preventArchiving Whether to stop the thread from being archived or not + * @property threadId The ID of the thread + * @property ownerId The ID of the thread's owner + * @property preventArchiving Whether to stop the thread from being archived or not * @since 3.2.0 */ @Suppress("DataClassShouldBeImmutable") diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/database/entities/WarnData.kt b/src/main/kotlin/org/hyacinthbots/lilybot/database/entities/WarnData.kt index 5491161e..db5a79be 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/database/entities/WarnData.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/database/entities/WarnData.kt @@ -6,9 +6,9 @@ import kotlinx.serialization.Serializable /** * The data for warnings in guilds. *. - * @param userId The ID of the user with warnings - * @param guildId The ID of the guild they received the warning in - * @param strikes The amount of strikes they have received + * @property userId The ID of the user with warnings + * @property guildId The ID of the guild they received the warning in + * @property strikes The amount of strikes they have received * @since 3.0.0 */ @Serializable diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/StartupHooks.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/StartupHooks.kt index 150a9672..bb6250d9 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/StartupHooks.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/StartupHooks.kt @@ -5,6 +5,8 @@ import com.kotlindiscord.kord.extensions.extensions.Extension import com.kotlindiscord.kord.extensions.extensions.event import com.kotlindiscord.kord.extensions.time.TimestampType import com.kotlindiscord.kord.extensions.time.toDiscord +import com.kotlindiscord.kord.extensions.utils.scheduling.Scheduler +import com.kotlindiscord.kord.extensions.utils.scheduling.Task import dev.kord.common.entity.PresenceStatus import dev.kord.core.behavior.channel.createEmbed import dev.kord.core.behavior.getChannelOf @@ -16,6 +18,7 @@ import org.hyacinthbots.lilybot.database.collections.StatusCollection import org.hyacinthbots.lilybot.utils.ONLINE_STATUS_CHANNEL import org.hyacinthbots.lilybot.utils.TEST_GUILD_ID import org.hyacinthbots.lilybot.utils.updateDefaultPresence +import kotlin.time.Duration.Companion.days /** * This class serves as a place for all functions that get run on bot start and bot start alone. This *hypothetically* @@ -26,7 +29,11 @@ import org.hyacinthbots.lilybot.utils.updateDefaultPresence * @since 3.2.2 */ class StartupHooks : Extension() { - override val name = "startuphooks" + override val name = "startup-hooks" + + private val cleanupScheduler = Scheduler() + + private lateinit var cleanupTask: Task override suspend fun setup() { event { @@ -47,36 +54,32 @@ class StartupHooks : Extension() { color = DISCORD_GREEN }?.publish() - /** - * This function is called to remove any threads in the database that haven't had a message sent in the last - * week. It only runs on startup. - * @author tempest15 - * @since 3.2.0 - */ - Cleanups.cleanupThreadData(kord) - - /** - * This function is called to remove any guilds in the database that haven't had Lily in them for more than - * a month. It only runs on startup - * - * @author NoComment1105 - * @since 3.2.0 - */ - Cleanups.cleanupGuildData() - /** * Check the status value in the database. If it is "default", set the status to watching over X guilds, * else the database value. */ - if (StatusCollection().getStatus() == null) { - updateDefaultPresence() - } else { - this@event.kord.editPresence { - status = PresenceStatus.Online - playing(StatusCollection().getStatus()!!) - } - } + if (StatusCollection().getStatus() == null) { + updateDefaultPresence() + } else { + this@event.kord.editPresence { + status = PresenceStatus.Online + playing(StatusCollection().getStatus()!!) + } + } } } + + cleanupTask = cleanupScheduler.schedule(1.days, callback = ::cleanup) + } + + /** + * This function is called to remove any threads in the database that haven't had a message sent in the last + * week. + * @author NoComment1105 + * @since 4.1.0 + */ + private suspend fun cleanup() { + Cleanups.cleanupThreadData(kord) + Cleanups.cleanupGuildData() } } From 844f5398596014343ca63a39e76ef4371de17e00 Mon Sep 17 00:00:00 2001 From: NoComment <67918617+NoComment1105@users.noreply.github.com> Date: Wed, 14 Sep 2022 15:37:36 +0100 Subject: [PATCH 11/25] Implement a command to reset the database entirely for a guild (#262) * Implement a command to reset the database entirely for a guild * Add reminders to cleanup and improve messages * Add guild ID to thread data object --- .../hyacinthbots/lilybot/database/Cleanups.kt | 10 +- .../collections/GalleryChannelCollection.kt | 10 ++ .../LogUploadingBlacklistCollection.kt | 10 ++ .../collections/RemindMeCollection.kt | 11 ++ .../database/collections/ThreadsCollection.kt | 14 +- .../lilybot/database/entities/ThreadData.kt | 2 + .../lilybot/database/migrations/Migrator.kt | 2 + .../database/migrations/main/mainV2.kt | 12 ++ .../extensions/events/ThreadInviter.kt | 6 +- .../lilybot/extensions/util/ModUtilities.kt | 126 ++++++++++++++++++ .../lilybot/extensions/util/StartupHooks.kt | 17 +++ .../lilybot/extensions/util/ThreadControl.kt | 10 +- 12 files changed, 218 insertions(+), 12 deletions(-) create mode 100644 src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/main/mainV2.kt diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/database/Cleanups.kt b/src/main/kotlin/org/hyacinthbots/lilybot/database/Cleanups.kt index 12540107..5ed9a97f 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/database/Cleanups.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/database/Cleanups.kt @@ -2,15 +2,19 @@ package org.hyacinthbots.lilybot.database import com.kotlindiscord.kord.extensions.koin.KordExKoinComponent import dev.kord.core.Kord +import dev.kord.core.behavior.getChannelOf import dev.kord.core.entity.channel.thread.ThreadChannel import dev.kord.rest.request.KtorRequestException import kotlinx.datetime.Clock import mu.KotlinLogging +import org.hyacinthbots.lilybot.database.Cleanups.cleanupGuildData +import org.hyacinthbots.lilybot.database.Cleanups.cleanupThreadData import org.hyacinthbots.lilybot.database.collections.LoggingConfigCollection import org.hyacinthbots.lilybot.database.collections.ModerationConfigCollection import org.hyacinthbots.lilybot.database.collections.RoleMenuCollection import org.hyacinthbots.lilybot.database.collections.SupportConfigCollection import org.hyacinthbots.lilybot.database.collections.TagsCollection +import org.hyacinthbots.lilybot.database.collections.ThreadsCollection import org.hyacinthbots.lilybot.database.collections.UtilityConfigCollection import org.hyacinthbots.lilybot.database.collections.WarnCollection import org.hyacinthbots.lilybot.database.entities.GuildLeaveTimeData @@ -82,15 +86,15 @@ object Cleanups : KordExKoinComponent { var deletedThreads = 0 for (it in threads) { try { - val thread = kordInstance.getChannelOf(it.threadId) ?: continue + val thread = kordInstance.getGuild(it.guildId!!)?.getChannelOf(it.threadId) ?: continue val latestMessage = thread.getLastMessage() ?: continue val timeSinceLatestMessage = Clock.System.now() - latestMessage.id.timestamp if (timeSinceLatestMessage.inWholeDays > 7) { - threadDataCollection.deleteOne(ThreadData::threadId eq thread.id) + ThreadsCollection().removeThread(thread.id) deletedThreads++ } } catch (e: KtorRequestException) { - threadDataCollection.deleteOne(ThreadData::threadId eq it.threadId) + ThreadsCollection().removeThread(it.threadId) deletedThreads++ continue } diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/GalleryChannelCollection.kt b/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/GalleryChannelCollection.kt index 41f369f1..63b5475e 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/GalleryChannelCollection.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/GalleryChannelCollection.kt @@ -58,4 +58,14 @@ class GalleryChannelCollection : KordExKoinComponent { GalleryChannelData::channelId eq inputChannelId, GalleryChannelData::guildId eq inputGuildId ) + + /** + * Removes all gallery channels from this guild. + * + * @param inputGuildId The guild to clear the gallery channels from + * @author NoComment1105 + * @since 4.1.0 + */ + suspend inline fun removeAll(inputGuildId: Snowflake) = + collection.deleteMany(GalleryChannelData::guildId eq inputGuildId) } diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/LogUploadingBlacklistCollection.kt b/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/LogUploadingBlacklistCollection.kt index 826c98b5..fb92c58c 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/LogUploadingBlacklistCollection.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/LogUploadingBlacklistCollection.kt @@ -69,4 +69,14 @@ class LogUploadingBlacklistCollection : KordExKoinComponent { */ suspend inline fun getLogUploadingBlacklist(inputGuildId: Snowflake): List = collection.find(LogUploadingBlacklistData::guildId eq inputGuildId).toList() + + /** + * Removes all data of the log upload blacklist for a given guild. + * + * @param inputGuildId The guild to clear the data from + * @author NoComment1105 + * @since 4.1.0 + */ + suspend inline fun clearBlacklist(inputGuildId: Snowflake) = + collection.deleteMany(LogUploadingBlacklistData::guildId eq inputGuildId) } diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/RemindMeCollection.kt b/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/RemindMeCollection.kt index 3e3678bb..158aec7c 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/RemindMeCollection.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/RemindMeCollection.kt @@ -106,4 +106,15 @@ class RemindMeCollection : KordExKoinComponent { RemindMeData::userId eq inputUserId, RemindMeData::id eq id ) + + /** + * Removes all reminders associated with a guild. + * + * @param inputGuildId The guild to remove reminders for + * + * @author NoComment1105 + * @since 4.1.0 + */ + suspend inline fun removeGuildReminders(inputGuildId: Snowflake) = + collection.deleteMany(RemindMeData::guildId eq inputGuildId) } diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/ThreadsCollection.kt b/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/ThreadsCollection.kt index 070f7b2b..c4f3adb8 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/ThreadsCollection.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/ThreadsCollection.kt @@ -70,12 +70,13 @@ class ThreadsCollection : KordExKoinComponent { * @since 3.2.0 */ suspend inline fun setThreadOwner( + inputGuildId: Snowflake?, inputThreadId: Snowflake, newOwnerId: Snowflake, preventArchiving: Boolean = false ) { collection.deleteOne(ThreadData::threadId eq inputThreadId) - collection.insertOne(ThreadData(inputThreadId, newOwnerId, preventArchiving)) + collection.insertOne(ThreadData(inputGuildId, inputThreadId, newOwnerId, preventArchiving)) } /** @@ -88,4 +89,15 @@ class ThreadsCollection : KordExKoinComponent { */ suspend inline fun removeThread(inputThreadId: Snowflake) = collection.deleteOne(ThreadData::threadId eq inputThreadId) + + /** + * This function deletes the ownership data stored in database for the given [inputGuildId]. + * + * @param inputGuildId The ID of the guild whose threads to delete + * + * @author NoComment1105 + * @since 4.1.0 + */ + suspend inline fun removeGuildThreads(inputGuildId: Snowflake) = + collection.deleteMany(ThreadData::guildId eq inputGuildId) } diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/database/entities/ThreadData.kt b/src/main/kotlin/org/hyacinthbots/lilybot/database/entities/ThreadData.kt index 399bbb38..6baa6a86 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/database/entities/ThreadData.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/database/entities/ThreadData.kt @@ -6,6 +6,7 @@ import kotlinx.serialization.Serializable /** * The data for threads. * + * @property guildId The ID of the guild this thread is in * @property threadId The ID of the thread * @property ownerId The ID of the thread's owner * @property preventArchiving Whether to stop the thread from being archived or not @@ -14,6 +15,7 @@ import kotlinx.serialization.Serializable @Suppress("DataClassShouldBeImmutable") @Serializable data class ThreadData( + val guildId: Snowflake?, // TODO make not nullable after migration val threadId: Snowflake, val ownerId: Snowflake, var preventArchiving: Boolean = false diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/Migrator.kt b/src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/Migrator.kt index 103589ba..0de1bb9c 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/Migrator.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/Migrator.kt @@ -18,6 +18,7 @@ import org.hyacinthbots.lilybot.database.entities.ConfigMetaData import org.hyacinthbots.lilybot.database.entities.MainMetaData import org.hyacinthbots.lilybot.database.migrations.config.configV1 import org.hyacinthbots.lilybot.database.migrations.main.mainV1 +import org.hyacinthbots.lilybot.database.migrations.main.mainV2 import org.koin.core.component.inject object Migrator : KordExKoinComponent { @@ -50,6 +51,7 @@ object Migrator : KordExKoinComponent { @Suppress("UseIfInsteadOfWhen") when (nextVersion) { 1 -> ::mainV1 + 2 -> ::mainV2 else -> break }(db.mainDatabase) diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/main/mainV2.kt b/src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/main/mainV2.kt new file mode 100644 index 00000000..cb8682fc --- /dev/null +++ b/src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/main/mainV2.kt @@ -0,0 +1,12 @@ +package org.hyacinthbots.lilybot.database.migrations.main + +import org.hyacinthbots.lilybot.database.entities.ThreadData +import org.litote.kmongo.coroutine.CoroutineDatabase +import org.litote.kmongo.exists +import org.litote.kmongo.setValue + +suspend fun mainV2(db: CoroutineDatabase) { + with(db.getCollection()) { + updateMany(ThreadData::guildId exists false, setValue(ThreadData::guildId, null)) + } +} diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/ThreadInviter.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/ThreadInviter.kt index 40f13203..3b27dfaf 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/ThreadInviter.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/ThreadInviter.kt @@ -128,7 +128,7 @@ class ThreadInviter : Extension() { event.message.getChannel().data.defaultAutoArchiveDuration.value ?: ArchiveDuration.Day ) - ThreadsCollection().setThreadOwner(thread.id, userId) + ThreadsCollection().setThreadOwner(guild.id, thread.id, userId) val startMessage = thread.createMessage("Welcome to your support thread! Let me grab the support team...") @@ -234,7 +234,7 @@ class ThreadInviter : Extension() { event.message.getChannel().data.defaultAutoArchiveDuration.value ?: ArchiveDuration.Day ) - ThreadsCollection().setThreadOwner(thread.id, userId) + ThreadsCollection().setThreadOwner(guild.id, thread.id, userId) val startMessage = thread.createMessage("Welcome to your support thread! Let me grab the support team...") @@ -293,7 +293,7 @@ class ThreadInviter : Extension() { val modRole = event.channel.guild.getRole(moderationConfig.role!!) val threadOwner = event.channel.owner.asUser() - ThreadsCollection().setThreadOwner(event.channel.id, threadOwner.id) + ThreadsCollection().setThreadOwner(event.channel.guildId, event.channel.id, threadOwner.id) if (supportConfig.enabled && event.channel.parentId == supportConfig.channel) { val supportRole = event.channel.guild.getRole(supportConfig.role!!) diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/ModUtilities.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/ModUtilities.kt index 59ed07e7..09833ca0 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/ModUtilities.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/ModUtilities.kt @@ -16,33 +16,56 @@ import com.kotlindiscord.kord.extensions.commands.converters.impl.optionalString import com.kotlindiscord.kord.extensions.commands.converters.impl.snowflake import com.kotlindiscord.kord.extensions.commands.converters.impl.string import com.kotlindiscord.kord.extensions.components.components +import com.kotlindiscord.kord.extensions.components.ephemeralButton import com.kotlindiscord.kord.extensions.components.linkButton import com.kotlindiscord.kord.extensions.extensions.Extension import com.kotlindiscord.kord.extensions.extensions.ephemeralSlashCommand import com.kotlindiscord.kord.extensions.extensions.event +import com.kotlindiscord.kord.extensions.modules.unsafe.annotations.UnsafeAPI +import com.kotlindiscord.kord.extensions.modules.unsafe.extensions.unsafeSlashCommand +import com.kotlindiscord.kord.extensions.modules.unsafe.types.InitialSlashCommandResponse import com.kotlindiscord.kord.extensions.types.respond import com.kotlindiscord.kord.extensions.utils.getJumpUrl +import com.kotlindiscord.kord.extensions.utils.waitFor +import dev.kord.common.entity.ButtonStyle import dev.kord.common.entity.Permission import dev.kord.common.entity.Permissions import dev.kord.common.entity.PresenceStatus +import dev.kord.common.entity.TextInputStyle import dev.kord.core.behavior.channel.MessageChannelBehavior import dev.kord.core.behavior.channel.asChannelOf import dev.kord.core.behavior.channel.createEmbed import dev.kord.core.behavior.channel.createMessage import dev.kord.core.behavior.edit import dev.kord.core.behavior.getChannelOf +import dev.kord.core.behavior.interaction.modal +import dev.kord.core.behavior.interaction.response.createEphemeralFollowup +import dev.kord.core.behavior.interaction.response.edit +import dev.kord.core.behavior.interaction.response.respond import dev.kord.core.entity.Message import dev.kord.core.entity.channel.GuildMessageChannel +import dev.kord.core.entity.interaction.response.EphemeralMessageInteractionResponse import dev.kord.core.event.guild.GuildCreateEvent import dev.kord.core.event.guild.GuildDeleteEvent +import dev.kord.core.event.interaction.ModalSubmitInteractionCreateEvent import dev.kord.core.exception.EntityNotFoundException import dev.kord.rest.builder.message.create.embed import dev.kord.rest.builder.message.modify.embed import dev.kord.rest.request.KtorRequestException import kotlinx.coroutines.flow.toList import kotlinx.datetime.Clock +import org.hyacinthbots.lilybot.database.collections.GalleryChannelCollection +import org.hyacinthbots.lilybot.database.collections.LogUploadingBlacklistCollection +import org.hyacinthbots.lilybot.database.collections.LoggingConfigCollection import org.hyacinthbots.lilybot.database.collections.ModerationConfigCollection +import org.hyacinthbots.lilybot.database.collections.RemindMeCollection +import org.hyacinthbots.lilybot.database.collections.RoleMenuCollection import org.hyacinthbots.lilybot.database.collections.StatusCollection +import org.hyacinthbots.lilybot.database.collections.SupportConfigCollection +import org.hyacinthbots.lilybot.database.collections.TagsCollection +import org.hyacinthbots.lilybot.database.collections.ThreadsCollection +import org.hyacinthbots.lilybot.database.collections.UtilityConfigCollection +import org.hyacinthbots.lilybot.database.collections.WarnCollection import org.hyacinthbots.lilybot.extensions.config.ConfigOptions import org.hyacinthbots.lilybot.extensions.config.ConfigType import org.hyacinthbots.lilybot.utils.TEST_GUILD_ID @@ -51,6 +74,7 @@ import org.hyacinthbots.lilybot.utils.configPresent import org.hyacinthbots.lilybot.utils.getChannelOrFirstUsable import org.hyacinthbots.lilybot.utils.getLoggingChannelWithPerms import org.hyacinthbots.lilybot.utils.updateDefaultPresence +import kotlin.time.Duration.Companion.seconds /** * This class contains a few utility commands that can be used by moderators. They all require a guild to be run. @@ -60,6 +84,7 @@ import org.hyacinthbots.lilybot.utils.updateDefaultPresence class ModUtilities : Extension() { override val name = "mod-utilities" + @OptIn(UnsafeAPI::class) override suspend fun setup() { /** * Say Command @@ -418,6 +443,107 @@ class ModUtilities : Extension() { } } + unsafeSlashCommand { + name = "reset" + description = "'Resets' Lily for this guild by deleting all database information relating to this guild" + + initialResponse = InitialSlashCommandResponse.None + + requirePermission(Permission.Administrator) // Hide this command from non-administrators + + check { + anyGuild() + hasPermission(Permission.Administrator) + } + + action { + val modal = event.interaction.modal("Reset data for this guild", "resetModal") { + actionRow { + textInput(TextInputStyle.Short, "confirmation", "Confirm reset") { + placeholder = "Type 'yes' to confirm" + } + } + } + + val interaction = + modal.kord.waitFor(120.seconds.inWholeMilliseconds) { + interaction.modalId == "resetModal" + }?.interaction + + if (interaction == null) { + modal.createEphemeralFollowup { content = "Reset interaction timed out" } + return@action + } + + val confirmation = interaction.textInputs["confirmation"]!!.value!! + val modalResponse = interaction.deferEphemeralResponse() + + if (confirmation.lowercase() != "yes") { + modalResponse.respond { content = "Confirmation failure. Reset cancelled" } + return@action + } + + var response: EphemeralMessageInteractionResponse? = null + + response = modalResponse.respond { + content = "Are you sure you want to reset the database? This will remove all data associated with " + + "this guild from Lily's database. This includes configs, user-set reminders, tags and more." + + "This action is **irreversible** and the data **cannot** be recovered." + + components { + ephemeralButton(0) { + label = "I'm sure" + style = ButtonStyle.Danger + + action { + response?.edit { + content = "Database reset!" + components { removeAll() } + } + + guild?.getChannelOf( + ModerationConfigCollection().getConfig(guild!!.id)?.channel ?: guild!!.asGuild() + .getSystemChannel()!!.id + )?.createMessage { + embed { + title = "Database Reset!" + description = "All data associated with this guild has been removed." + timestamp = Clock.System.now() + color = DISCORD_BLACK + } + } + + // Reset + LoggingConfigCollection().clearConfig(guild!!.id) + ModerationConfigCollection().clearConfig(guild!!.id) + SupportConfigCollection().clearConfig(guild!!.id) + UtilityConfigCollection().clearConfig(guild!!.id) + GalleryChannelCollection().removeAll(guild!!.id) + LogUploadingBlacklistCollection().clearBlacklist(guild!!.id) + RemindMeCollection().removeGuildReminders(guild!!.id) + RoleMenuCollection().removeAllRoleMenus(guild!!.id) + TagsCollection().clearTags(guild!!.id) + ThreadsCollection().removeGuildThreads(guild!!.id) + WarnCollection().clearWarns(guild!!.id) + } + } + + ephemeralButton(0) { + label = "Nevermind" + style = ButtonStyle.Secondary + + action { + response?.edit { + content = "Reset cancelled" + components { removeAll() } + } + } + } + } + } + } + } + /** * Update the presence to reflect the new number of guilds, if the presence is set to "default" * diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/StartupHooks.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/StartupHooks.kt index bb6250d9..b945b303 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/StartupHooks.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/StartupHooks.kt @@ -11,13 +11,19 @@ import dev.kord.common.entity.PresenceStatus import dev.kord.core.behavior.channel.createEmbed import dev.kord.core.behavior.getChannelOf import dev.kord.core.entity.channel.NewsChannel +import dev.kord.core.entity.channel.thread.ThreadChannel import dev.kord.core.event.gateway.ReadyEvent import kotlinx.datetime.Clock import org.hyacinthbots.lilybot.database.Cleanups +import org.hyacinthbots.lilybot.database.Database import org.hyacinthbots.lilybot.database.collections.StatusCollection +import org.hyacinthbots.lilybot.database.entities.ThreadData import org.hyacinthbots.lilybot.utils.ONLINE_STATUS_CHANNEL import org.hyacinthbots.lilybot.utils.TEST_GUILD_ID import org.hyacinthbots.lilybot.utils.updateDefaultPresence +import org.litote.kmongo.coroutine.toList +import org.litote.kmongo.eq +import org.litote.kmongo.setValue import kotlin.time.Duration.Companion.days /** @@ -38,6 +44,17 @@ class StartupHooks : Extension() { override suspend fun setup() { event { action { + // TODO Remove this once the migration is done, because of the fact we cannot access kord in the + // migration we need to do this to apply the guild IDs + with(Database().mainDatabase.getCollection("threadData")) { + collection.find(ThreadData::guildId eq null).toList().forEach { + updateOne( + ThreadData::threadId eq it.threadId, + setValue(ThreadData::guildId, kord.getChannelOf(it.threadId)!!.guildId) + ) + } + } + val now = Clock.System.now() /** diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/ThreadControl.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/ThreadControl.kt index 1be78a06..c2a464da 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/ThreadControl.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/ThreadControl.kt @@ -101,7 +101,7 @@ class ThreadControl : Extension() { if (it.threadId == threadChannel.id) { val preventingArchiving = ThreadsCollection().getThread(it.threadId)?.preventArchiving ThreadsCollection().removeThread(it.threadId) - ThreadsCollection().setThreadOwner(it.threadId, it.ownerId, false) + ThreadsCollection().setThreadOwner(it.guildId, it.threadId, it.ownerId, false) if (preventingArchiving == true) { guild!!.getChannelOf( ModerationConfigCollection().getConfig(guild!!.id)!!.channel!! @@ -183,7 +183,7 @@ class ThreadControl : Extension() { return@action } - ThreadsCollection().setThreadOwner(threadChannel.id, arguments.newOwner.id) + ThreadsCollection().setThreadOwner(guild!!.id, threadChannel.id, arguments.newOwner.id) respond { content = "Ownership transferred." } @@ -253,7 +253,7 @@ class ThreadControl : Extension() { var message: EphemeralMessageInteractionResponse? = null var thread = threads.firstOrNull { it.threadId == threadChannel.id } if (thread == null) { - ThreadsCollection().setThreadOwner(threadChannel.id, threadChannel.ownerId, false) + ThreadsCollection().setThreadOwner(threadChannel.guildId, threadChannel.id, threadChannel.ownerId, false) thread = threads.firstOrNull { it.threadId == threadChannel.id } } if (thread?.preventArchiving == true) { @@ -266,7 +266,7 @@ class ThreadControl : Extension() { style = ButtonStyle.Primary action { - ThreadsCollection().setThreadOwner(thread.threadId, thread.ownerId, false) + ThreadsCollection().setThreadOwner(thread.guildId, thread.threadId, thread.ownerId, false) edit { content = "Thread archiving will no longer be prevented" } utilityLog.createMessage { embed { @@ -299,7 +299,7 @@ class ThreadControl : Extension() { } return@action } else if (thread?.preventArchiving == false) { - ThreadsCollection().setThreadOwner(thread.threadId, thread.ownerId, true) + ThreadsCollection().setThreadOwner(thread.guildId, thread.threadId, thread.ownerId, true) try { utilityLog.createMessage { embed { From 149f289ce6688ea61e37d51c6b6370ef1b98648b Mon Sep 17 00:00:00 2001 From: tempest15 Date: Wed, 14 Sep 2022 10:41:28 -0400 Subject: [PATCH 12/25] add missing space in message edit and delete logging --- .../org/hyacinthbots/lilybot/extensions/events/MessageDelete.kt | 2 +- .../org/hyacinthbots/lilybot/extensions/events/MessageEdit.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/MessageDelete.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/MessageDelete.kt index 5f195a74..1b499859 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/MessageDelete.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/MessageDelete.kt @@ -92,7 +92,7 @@ class MessageDelete : Extension() { icon = proxiedMessage?.member?.avatarUrl ?: message.author?.avatar?.url } description = - "Location: ${message.channel.mention}" + + "Location: ${message.channel.mention} " + "(${message.channel.asChannelOf().name})" timestamp = Clock.System.now() diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/MessageEdit.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/MessageEdit.kt index 5037c536..0661984b 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/MessageEdit.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/MessageEdit.kt @@ -89,7 +89,7 @@ class MessageEdit : Extension() { icon = proxiedMessage?.member?.avatarUrl ?: message.author?.avatar?.url } description = - "Location: ${message.channel.mention}" + + "Location: ${message.channel.mention} " + "(${message.channel.asChannelOf().name})" timestamp = Clock.System.now() From c16a7679fca61efae07b727b115e1dc97d4b57f2 Mon Sep 17 00:00:00 2001 From: NoComment1105 Date: Thu, 15 Sep 2022 17:53:16 +0100 Subject: [PATCH 13/25] Make use of the new KordEx `attachment` and `optionalAttachment` converters for images --- docs/commanddocs.toml | 10 +- docs/commands.md | 10 +- libs.versions.toml | 4 +- .../moderation/TemporaryModeration.kt | 74 +++++------- .../moderation/TerminalModeration.kt | 113 ++++++++---------- 5 files changed, 91 insertions(+), 120 deletions(-) diff --git a/docs/commanddocs.toml b/docs/commanddocs.toml index 91b732ad..17fd845a 100644 --- a/docs/commanddocs.toml +++ b/docs/commanddocs.toml @@ -54,35 +54,35 @@ permissions = "Manage Messages" [[command]] category = "Moderation commands" name = "ban" -args = "* `user` – Person to ban - User\n* `messages` - Number of days of messages to delete - Integer\n* `reason` - Reason for the ban - Optional String\n* `image` - The URL to an image to provide extra context for the action - Optional String\n* `dm` - Whether to DM the user or not. Default: True - Optional Boolean" +args = "* `user` – Person to ban - User\n* `messages` - Number of days of messages to delete - Integer\n* `reason` - Reason for the ban - Optional String\n* `image` - An image to provide extra context for the action - Optional Attachment\n* `dm` - Whether to DM the user or not. Default: True - Optional Boolean" result = "Bans `banUser` from the server with reason `reason` and deletes any messages they sent in the last `messages` day(s)." permissions = "Ban Members" [[command]] category = "Moderation commands" name = "unban" -args = "* `user ` - The Discord ID of the person to unban - User ID" +args = "* `user ` - The Discord ID (Snowflake) of the person to unban - User ID" result = "The user with the ID `unbanUserId` is unbanned." permissions = "Ban Members" [[command]] category = "Moderation commands" name = "soft-ban" -args = "* `user` - Person to soft ban - User\n* `messages` - Number of days of messages to delete - Integer (default 3)\n* `reason` - Reason for the ban - Optional String\n* `image` - The URL to an image to provide extra context for the action - Optional String\n* `dm` - Whether to DM the user or not. Default: True - Optional Boolean" +args = "* `user` - Person to soft ban - User\n* `messages` - Number of days of messages to delete - Integer (default 3)\n* `reason` - Reason for the ban - Optional String\n* `image` - An image to provide extra context for the action - Optional Attachment\n* `dm` - Whether to DM the user or not. Default: True - Optional Boolean" result = "Bans `softBanUser`, deletes the last `messages` days of messages from them, and unbans them." permissions = "Ban Members" [[command]] category = "Moderation commands" name = "warn" -args = "* `user` - Person to warn - User\n* `reason` - Reason for warn - Optional String\n* `image` - The URL to an image to provide extra context for the action - Optional String\n* `dm` - Whether to DM the user or not. Default: True - Optional Boolean" +args = "* `user` - Person to warn - User\n* `reason` - Reason for warn - Optional String\n* `image` - An image to provide extra context for the action - Optional Attachment\n* `dm` - Whether to DM the user or not. Default: True - Optional Boolean" result = "Warns `warnUser` with a DM and adds a strike to their points total. Depending on their new points total, action is taken based on the below table.\n\n| Points | Sanction |\n|:------:|:----------------:|\n| 1 | None. |\n| 2 | 3 hour timeout. |\n| 3 | 12 hour timeout. |\n| 3+ | 3 day timeout. |" permissions = "Moderate Members" [[command]] category = "Moderation commands" name = "timeout" -args = "* `user` - Person to timeout - User\n* `duration` - Duration of timeout - Duration [e.g. 6h or 30s] (default 6h)\n* `reason` - Reason for timeout - Optional String\n* `image` - The URL to an image to provide extra context for the action - Optional String\n* `dm` - Whether to DM the user or not. Default: True - Optional Boolean" +args = "* `user` - Person to timeout - User\n* `duration` - Duration of timeout - Duration [e.g. 6h or 30s] (default 6h)\n* `reason` - Reason for timeout - Optional String\n* `image` - An image to provide extra context for the action - Optional Attachment\n* `dm` - Whether to DM the user or not. Default: True - Optional Boolean" result = "Times `timeoutUser` out for `duration`. A timeout is Discord's built-in mute function." permissions = "Moderate Members" diff --git a/docs/commands.md b/docs/commands.md index c2bfb272..edcc54ec 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -99,7 +99,7 @@ These commands are for use by moderators. They utilize built-in permission check * `user` – Person to ban - User * `messages` - Number of days of messages to delete - Integer * `reason` - Reason for the ban - Optional String -* `image` - The URL to an image to provide extra context for the action - Optional String +* `image` - An image to provide extra context for the action - Optional Attachment * `dm` - Whether to DM the user or not. Default: True - Optional Boolean **Result**: Bans `banUser` from the server with reason `reason` and deletes any messages they sent in the last `messages` day(s). @@ -112,7 +112,7 @@ These commands are for use by moderators. They utilize built-in permission check ### Name: `unban` **Arguments**: -* `user ` - The Discord ID of the person to unban - User ID +* `user ` - The Discord ID (Snowflake) of the person to unban - User ID **Result**: The user with the ID `unbanUserId` is unbanned. @@ -127,7 +127,7 @@ These commands are for use by moderators. They utilize built-in permission check * `user` - Person to soft ban - User * `messages` - Number of days of messages to delete - Integer (default 3) * `reason` - Reason for the ban - Optional String -* `image` - The URL to an image to provide extra context for the action - Optional String +* `image` - An image to provide extra context for the action - Optional Attachment * `dm` - Whether to DM the user or not. Default: True - Optional Boolean **Result**: Bans `softBanUser`, deletes the last `messages` days of messages from them, and unbans them. @@ -142,7 +142,7 @@ These commands are for use by moderators. They utilize built-in permission check **Arguments**: * `user` - Person to warn - User * `reason` - Reason for warn - Optional String -* `image` - The URL to an image to provide extra context for the action - Optional String +* `image` - An image to provide extra context for the action - Optional Attachment * `dm` - Whether to DM the user or not. Default: True - Optional Boolean **Result**: Warns `warnUser` with a DM and adds a strike to their points total. Depending on their new points total, action is taken based on the below table. @@ -165,7 +165,7 @@ These commands are for use by moderators. They utilize built-in permission check * `user` - Person to timeout - User * `duration` - Duration of timeout - Duration [e.g. 6h or 30s] (default 6h) * `reason` - Reason for timeout - Optional String -* `image` - The URL to an image to provide extra context for the action - Optional String +* `image` - An image to provide extra context for the action - Optional Attachment * `dm` - Whether to DM the user or not. Default: True - Optional Boolean **Result**: Times `timeoutUser` out for `duration`. A timeout is Discord's built-in mute function. diff --git a/libs.versions.toml b/libs.versions.toml index 85241182..db566bfd 100644 --- a/libs.versions.toml +++ b/libs.versions.toml @@ -2,11 +2,11 @@ kotlin = "1.7.10" # Note: Plugin versions must be updated in the settings.gradle.kts too groovy = "3.0.12" -kord-extensions = "1.5.5-20220831.110202-26" +kord-extensions = "1.5.5-20220914.170355-31" logging = "2.1.23" logback = "1.2.8" github-api = "1.308" -kmongo = "4.7.0" +kmongo = "4.7.1" detekt = "1.21.0" koma = "1.1.0" diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/TemporaryModeration.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/TemporaryModeration.kt index 7e7f30e9..d34b02b7 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/TemporaryModeration.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/TemporaryModeration.kt @@ -11,8 +11,8 @@ import com.kotlindiscord.kord.extensions.commands.converters.impl.coalescingDefa import com.kotlindiscord.kord.extensions.commands.converters.impl.defaultingBoolean import com.kotlindiscord.kord.extensions.commands.converters.impl.defaultingString import com.kotlindiscord.kord.extensions.commands.converters.impl.int +import com.kotlindiscord.kord.extensions.commands.converters.impl.optionalAttachment import com.kotlindiscord.kord.extensions.commands.converters.impl.optionalChannel -import com.kotlindiscord.kord.extensions.commands.converters.impl.optionalString import com.kotlindiscord.kord.extensions.commands.converters.impl.user import com.kotlindiscord.kord.extensions.extensions.Extension import com.kotlindiscord.kord.extensions.extensions.ephemeralSlashCommand @@ -34,7 +34,6 @@ import dev.kord.core.entity.channel.GuildMessageChannel import dev.kord.core.entity.channel.TextChannel import dev.kord.core.entity.channel.thread.TextChannelThread import dev.kord.core.supplier.EntitySupplyStrategy -import dev.kord.rest.builder.message.EmbedBuilder import dev.kord.rest.builder.message.create.embed import dev.kord.rest.request.KtorRequestException import kotlinx.coroutines.flow.map @@ -263,24 +262,18 @@ class TemporaryModeration : Extension() { } } - val embed = EmbedBuilder() - embed.color = DISCORD_BLACK - embed.title = "Warning" - embed.image = arguments.image - embed.baseModerationEmbed(arguments.reason, userArg, user) - embed.dmNotificationStatusEmbedField(arguments.dm, dm) - embed.timestamp = Clock.System.now() - embed.field { - name = "Total Strikes:" - value = newStrikes.toString() - inline = false - } - - try { - actionLog.createMessage { embeds.add(embed) } - } catch (e: KtorRequestException) { - embed.image = null - actionLog.createMessage { embeds.add(embed) } + actionLog.createMessage { + embed { + title = "Warning" + image = arguments.image?.url + baseModerationEmbed(arguments.reason, userArg, user) + dmNotificationStatusEmbedField(arguments.dm, dm) + timestamp = Clock.System.now() + field { + name = "Total strikes" + value = newStrikes.toString() + } + } } if (config.publicLogging != null && config.publicLogging == true) { @@ -428,25 +421,20 @@ class TemporaryModeration : Extension() { content = "Timed out ${userArg.id}" } - val embed = EmbedBuilder() - embed.color = DISCORD_BLACK - embed.title = "Timeout" - embed.image = arguments.image - embed.baseModerationEmbed(arguments.reason, userArg, user) - embed.dmNotificationStatusEmbedField(arguments.dm, dm) - embed.timestamp = Clock.System.now() - embed.field { - name = "Duration:" - value = duration.toDiscord(TimestampType.Default) + " (" + arguments.duration.toString() - .replace("PT", "") + ")" - inline = false - } - - try { - actionLog.createMessage { embeds.add(embed) } - } catch (e: KtorRequestException) { - embed.image = null - actionLog.createMessage { embeds.add(embed) } + actionLog.createMessage { + embed { + title = "Timeout" + image = arguments.image?.url + baseModerationEmbed(arguments.reason, userArg, user) + dmNotificationStatusEmbedField(arguments.dm, dm) + timestamp = Clock.System.now() + field { + name = "Duration:" + value = duration.toDiscord(TimestampType.Default) + " (" + arguments.duration.toString() + .replace("PT", "") + ")" + inline = false + } + } } if (config.publicLogging != null && config.publicLogging == true) { @@ -825,9 +813,9 @@ class TemporaryModeration : Extension() { } /** An image that the user wishes to provide for context to the kick. */ - val image by optionalString { + val image by optionalAttachment { name = "image" - description = "The URL to an image you'd like to provide as extra context for the action" + description = "An image you'd like to provide as extra context for the action" } val dm by defaultingBoolean { @@ -860,9 +848,9 @@ class TemporaryModeration : Extension() { } /** An image that the user wishes to provide for context to the kick. */ - val image by optionalString { + val image by optionalAttachment { name = "image" - description = "The URL to an image you'd like to provide as extra context for the action" + description = "An image you'd like to provide as extra context for the action" } val dm by defaultingBoolean { diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/TerminalModeration.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/TerminalModeration.kt index 28a1dcca..580351c0 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/TerminalModeration.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/TerminalModeration.kt @@ -9,7 +9,7 @@ import com.kotlindiscord.kord.extensions.commands.converters.impl.defaultingBool import com.kotlindiscord.kord.extensions.commands.converters.impl.defaultingInt import com.kotlindiscord.kord.extensions.commands.converters.impl.defaultingString import com.kotlindiscord.kord.extensions.commands.converters.impl.int -import com.kotlindiscord.kord.extensions.commands.converters.impl.optionalString +import com.kotlindiscord.kord.extensions.commands.converters.impl.optionalAttachment import com.kotlindiscord.kord.extensions.commands.converters.impl.user import com.kotlindiscord.kord.extensions.extensions.Extension import com.kotlindiscord.kord.extensions.extensions.ephemeralSlashCommand @@ -24,9 +24,7 @@ import dev.kord.core.behavior.channel.createMessage import dev.kord.core.behavior.edit import dev.kord.core.entity.Message import dev.kord.core.exception.EntityNotFoundException -import dev.kord.rest.builder.message.EmbedBuilder import dev.kord.rest.builder.message.create.embed -import dev.kord.rest.request.KtorRequestException import kotlinx.coroutines.flow.toList import kotlinx.datetime.Clock import kotlinx.datetime.DateTimePeriod @@ -108,34 +106,29 @@ class TerminalModeration : Extension() { } // Run the ban task - guild?.ban(userArg.id, builder = { + guild?.ban(userArg.id) { reason = arguments.reason deleteMessageDuration = DateTimePeriod(days = arguments.messages).toDuration(TimeZone.UTC) - }) + } respond { content = "Banned a user" } - val embed = EmbedBuilder() - embed.color = DISCORD_BLACK - embed.title = "Banned a user" - embed.description = "${userArg.mention} has been banned!" - embed.image = arguments.image - embed.baseModerationEmbed(arguments.reason, userArg, user) - embed.dmNotificationStatusEmbedField(arguments.dm, dm) - embed.timestamp = Clock.System.now() - embed.field { - name = "Days of messages deleted:" - value = arguments.messages.toString() - inline = false - } - - try { - actionLog.createMessage { embeds.add(embed) } - } catch (e: KtorRequestException) { - embed.image = null - actionLog.createMessage { embeds.add(embed) } + actionLog.createMessage { + embed { + title = "Banned a user" + description = "${userArg.mention} has been banned!" + image = arguments.image?.url + baseModerationEmbed(arguments.reason, userArg, user) + dmNotificationStatusEmbedField(arguments.dm, dm) + timestamp = Clock.System.now() + field { + name = "Days of messages deleted:" + value = arguments.messages.toString() + inline = false + } + } } if (config.publicLogging != null && config.publicLogging == true) { @@ -264,34 +257,29 @@ class TerminalModeration : Extension() { } // Ban the user, mark it as a soft-ban clearly - guild?.ban(userArg.id, builder = { + guild?.ban(userArg.id) { reason = "${arguments.reason} + **SOFT-BAN**" deleteMessageDuration = DateTimePeriod(days = arguments.messages).toDuration(TimeZone.UTC) - }) + } respond { content = "Soft-Banned User" } - val embed = EmbedBuilder() - embed.color = DISCORD_BLACK - embed.title = "Soft-Banned a user" - embed.description = "${userArg.mention} has been soft-banned!" - embed.image = arguments.image - embed.baseModerationEmbed(arguments.reason, userArg, user) - embed.dmNotificationStatusEmbedField(arguments.dm, dm) - embed.timestamp = Clock.System.now() - embed.field { - name = "Days of messages deleted" - value = arguments.messages.toString() - inline = false - } - - try { - actionLog.createMessage { embeds.add(embed) } - } catch (e: KtorRequestException) { - embed.image = null - actionLog.createMessage { embeds.add(embed) } + actionLog.createMessage { + embed { + title = "Soft-Banned a user" + description = "${userArg.mention} has been soft-banned!" + image = arguments.image?.url + baseModerationEmbed(arguments.reason, userArg, user) + dmNotificationStatusEmbedField(arguments.dm, dm) + timestamp = Clock.System.now() + field { + name = "Days of messages deleted" + value = arguments.messages.toString() + inline = false + } + } } if (config.publicLogging != null && config.publicLogging == true) { @@ -361,20 +349,15 @@ class TerminalModeration : Extension() { content = "Kicked User" } - val embed = EmbedBuilder() - embed.color = DISCORD_BLACK - embed.title = "Kicked a user" - embed.description = "${userArg.mention} has been kicked!" - embed.image = arguments.image - embed.baseModerationEmbed(arguments.reason, userArg, user) - embed.dmNotificationStatusEmbedField(arguments.dm, dm) - embed.timestamp = Clock.System.now() - - try { - actionLog.createMessage { embeds.add(embed) } - } catch (e: KtorRequestException) { - embed.image = null - actionLog.createMessage { embeds.add(embed) } + actionLog.createMessage { + embed { + title = "Kicked a user" + description = "${userArg.mention} has been kicked!" + image = arguments.image?.url + baseModerationEmbed(arguments.reason, userArg, user) + dmNotificationStatusEmbedField(arguments.dm, dm) + timestamp = Clock.System.now() + } } if (config.publicLogging != null && config.publicLogging == true) { @@ -408,9 +391,9 @@ class TerminalModeration : Extension() { } /** An image that the user wishes to provide for context to the kick. */ - val image by optionalString { + val image by optionalAttachment { name = "image" - description = "The URL to an image you'd like to provide as extra context for the action" + description = "An image you'd like to provide as extra context for the action" } } @@ -441,9 +424,9 @@ class TerminalModeration : Extension() { } /** An image that the user wishes to provide for context to the ban. */ - val image by optionalString { + val image by optionalAttachment { name = "image" - description = "The URL to an image you'd like to provide as extra context for the action" + description = "An image you'd like to provide as extra context for the action" } } @@ -490,9 +473,9 @@ class TerminalModeration : Extension() { } /** An image that the user wishes to provide for context to the soft-ban. */ - val image by optionalString { + val image by optionalAttachment { name = "image" - description = "The URL to an image you'd like to provide as extra context for the action" + description = "An image you'd like to provide as extra context for the action" } } } From 3661745b3a38e51c07bdb5a53b478e20f30e889e Mon Sep 17 00:00:00 2001 From: NoComment1105 Date: Thu, 15 Sep 2022 18:42:17 +0100 Subject: [PATCH 14/25] Re-add member logging with great hope --- .../lilybot/extensions/events/MemberLogging.kt | 8 +++++++- .../kotlin/org/hyacinthbots/lilybot/utils/_Utils.kt | 12 ++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/MemberLogging.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/MemberLogging.kt index ad6a902c..36fec55d 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/MemberLogging.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/MemberLogging.kt @@ -14,6 +14,7 @@ import org.hyacinthbots.lilybot.extensions.config.ConfigOptions import org.hyacinthbots.lilybot.extensions.config.ConfigType import org.hyacinthbots.lilybot.utils.configPresent import org.hyacinthbots.lilybot.utils.getLoggingChannelWithPerms +import org.hyacinthbots.lilybot.utils.getMemberCount /** * Logs members joining and leaving a guild to the member log channel designated in the config for that guild. @@ -52,6 +53,9 @@ class MemberLogging : Extension() { value = event.member.id.toString() inline = false } + footer { + text = "Member Count: ${getMemberCount(event.guildId)}" + } timestamp = Clock.System.now() color = DISCORD_GREEN } @@ -83,7 +87,9 @@ class MemberLogging : Extension() { field { name = "ID:" value = event.user.id.toString() - inline = false + } + footer { + text = "Member Count: ${getMemberCount(event.guildId)}" } timestamp = Clock.System.now() color = DISCORD_RED diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/utils/_Utils.kt b/src/main/kotlin/org/hyacinthbots/lilybot/utils/_Utils.kt index 4b719cac..5354959a 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/utils/_Utils.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/utils/_Utils.kt @@ -32,6 +32,7 @@ import dev.kord.core.supplier.EntitySupplyStrategy import kotlinx.coroutines.delay import kotlinx.coroutines.flow.count import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.toList import kotlinx.coroutines.runBlocking import mu.KotlinLogging @@ -402,6 +403,17 @@ suspend inline fun Extension.updateDefaultPresence() { */ suspend inline fun Extension.getGuildCount() = kord.with(EntitySupplyStrategy.cacheWithRestFallback).guilds.count() +/** + * Gets the member count for a given guild. + * + * @param guildId The target guild + * @return The number of members in that guild + * @author NoComment1105 + * @since 4.1.0 + */ +suspend inline fun Extension.getMemberCount(guildId: Snowflake) = + kord.with(EntitySupplyStrategy.rest).getGuild(guildId).members.map { }.count() + /** * This function loads the database and checks if it is up-to-date. If it isn't, it will update the database via * migrations. From a96f05aab9984278b3fd25b0c6eb982e1826d1f4 Mon Sep 17 00:00:00 2001 From: NoComment1105 Date: Thu, 15 Sep 2022 18:53:14 +0100 Subject: [PATCH 15/25] Fix timestamps with edit-say --- docs/commanddocs.toml | 2 +- docs/commands.md | 2 +- .../lilybot/extensions/util/ModUtilities.kt | 23 +++++++++++++------ 3 files changed, 18 insertions(+), 9 deletions(-) diff --git a/docs/commanddocs.toml b/docs/commanddocs.toml index 17fd845a..4ea74a23 100644 --- a/docs/commanddocs.toml +++ b/docs/commanddocs.toml @@ -156,7 +156,7 @@ permissions = "Moderate Members" [[command]] category = "Utility commands" name = "edit-say" -args = "(Moderators only)\n* `message-to-edit` - The ID of the message contain the embed you'd like to edit - Snowflake\n* `new-content` - The new content for the message - Optional String\n* `new-color` - The new color for the embed - Optional Color (default: Blurple)\n* `channel-of-message` - The channel the embed was originally sent in - Optional channel (default: Channel command was executed in)\n* `timestamp` - Whether to add the timestamp of when the message was originally sent or not - Optional boolean (default: true)" +args = "(Moderators only)\n* `message-to-edit` - The ID of the message contain the embed you'd like to edit - Snowflake\n* `new-content` - The new content for the message - Optional String\n* `new-color` - The new color for the embed - Optional Color (default: Blurple)\n* `channel-of-message` - The channel the embed was originally sent in - Optional channel (default: Channel command was executed in)\n* `timestamp` - Whether to add the timestamp of when the message was originally sent or not - Optional boolean" result = "Edited message/embed" permissions = "Moderate Members" diff --git a/docs/commands.md b/docs/commands.md index edcc54ec..fd78eac6 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -306,7 +306,7 @@ None * `new-content` - The new content for the message - Optional String * `new-color` - The new color for the embed - Optional Color (default: Blurple) * `channel-of-message` - The channel the embed was originally sent in - Optional channel (default: Channel command was executed in) -* `timestamp` - Whether to add the timestamp of when the message was originally sent or not - Optional boolean (default: true) +* `timestamp` - Whether to add the timestamp of when the message was originally sent or not - Optional boolean **Result**: Edited message/embed diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/ModUtilities.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/ModUtilities.kt index 09833ca0..73e1f1f2 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/ModUtilities.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/ModUtilities.kt @@ -10,6 +10,7 @@ import com.kotlindiscord.kord.extensions.commands.Arguments import com.kotlindiscord.kord.extensions.commands.application.slash.ephemeralSubCommand import com.kotlindiscord.kord.extensions.commands.converters.impl.defaultingBoolean import com.kotlindiscord.kord.extensions.commands.converters.impl.defaultingColor +import com.kotlindiscord.kord.extensions.commands.converters.impl.optionalBoolean import com.kotlindiscord.kord.extensions.commands.converters.impl.optionalChannel import com.kotlindiscord.kord.extensions.commands.converters.impl.optionalColour import com.kotlindiscord.kord.extensions.commands.converters.impl.optionalString @@ -300,7 +301,11 @@ class ModUtilities : Extension() { embed { description = arguments.newContent ?: oldContent color = arguments.newColor ?: oldColor - timestamp = if (arguments.timestamp) message.timestamp else oldTimestamp + timestamp = when (arguments.timestamp) { + true -> message.timestamp + false -> null + null -> oldTimestamp + } } } @@ -330,7 +335,11 @@ class ModUtilities : Extension() { } field { name = "Has Timestamp" - value = arguments.timestamp.toString() + value = when (arguments.timestamp) { + true -> "True" + false -> "False" + else -> "Original" + } } footer { text = "Edited by ${user.asUser().tag}" @@ -486,9 +495,10 @@ class ModUtilities : Extension() { var response: EphemeralMessageInteractionResponse? = null response = modalResponse.respond { - content = "Are you sure you want to reset the database? This will remove all data associated with " + - "this guild from Lily's database. This includes configs, user-set reminders, tags and more." + - "This action is **irreversible** and the data **cannot** be recovered." + content = + "Are you sure you want to reset the database? This will remove all data associated with " + + "this guild from Lily's database. This includes configs, user-set reminders, tags and more." + + "This action is **irreversible** and the data **cannot** be recovered." components { ephemeralButton(0) { @@ -644,10 +654,9 @@ class ModUtilities : Extension() { } /** Whether to add the timestamp of when the message was originally sent or not. */ - val timestamp by defaultingBoolean { + val timestamp by optionalBoolean { name = "timestamp" description = "Whether to timestamp the embed or not. Embeds only" - defaultValue = true } } From 435b24c68be32c0156bf8d8a21b5f92a93729f72 Mon Sep 17 00:00:00 2001 From: NoComment1105 Date: Thu, 15 Sep 2022 19:10:47 +0100 Subject: [PATCH 16/25] Do not flag deleted pk;e messages --- .../hyacinthbots/lilybot/extensions/events/MessageDelete.kt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/MessageDelete.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/MessageDelete.kt index 1b499859..df78897d 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/MessageDelete.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/MessageDelete.kt @@ -84,6 +84,10 @@ class MessageDelete : Extension() { getLoggingChannelWithPerms(message.getGuild(), config.messageChannel!!, ConfigType.LOGGING) ?: return + if (message.content.startsWith("pk;e", 0, true)) { + return + } + messageLog.createMessage { embed { color = DISCORD_PINK From 9ff6444dabc50aafe92fdb7b0a3a658e462fbe5a Mon Sep 17 00:00:00 2001 From: NoComment <67918617+NoComment1105@users.noreply.github.com> Date: Thu, 15 Sep 2022 19:17:52 +0100 Subject: [PATCH 17/25] Log when message tags are sent (#236) * Log when message tags are sent * Use utility config and make it an embed * Change embed structure a little and just ignore the utility config being null --- .../lilybot/extensions/util/Tags.kt | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/Tags.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/Tags.kt index 5cbb4f88..385dc2d5 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/Tags.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/Tags.kt @@ -25,9 +25,12 @@ import dev.kord.common.entity.Permission import dev.kord.common.entity.Permissions import dev.kord.core.behavior.channel.createEmbed import dev.kord.core.behavior.channel.createMessage +import dev.kord.core.behavior.getChannelOfOrNull +import dev.kord.core.entity.channel.GuildMessageChannel import dev.kord.rest.builder.message.create.embed import kotlinx.datetime.Clock import org.hyacinthbots.lilybot.database.collections.TagsCollection +import org.hyacinthbots.lilybot.database.collections.UtilityConfigCollection import org.hyacinthbots.lilybot.extensions.config.ConfigOptions import org.hyacinthbots.lilybot.extensions.config.ConfigType import org.hyacinthbots.lilybot.utils.botHasChannelPerms @@ -149,6 +152,33 @@ class Tags : Extension() { "${arguments.user?.mention ?: ""}\n**${tagFromDatabase.tagTitle}**\n${tagFromDatabase.tagValue}" } } + + // Log when a message tag is sent to allow identification of tag spammers + if (tagFromDatabase.tagAppearance == "message") { + val utilityLog = UtilityConfigCollection().getConfig(guild!!.id)?.utilityLogChannel ?: return@action + guild!!.getChannelOfOrNull(utilityLog)?.createMessage { + embed { + title = "Message Tag used" + field { + name = "User" + value = "${user.asUser().mention} (${user.asUser().tag})" + } + field { + name = "Tag name" + value = "`${arguments.tagName}`" + } + field { + name = "Location" + value = "${channel.mention} ${channel.asChannel().data.name.value}" + } + footer { + text = "User ID: ${user.asUser().id}" + icon = user.asUser().avatar?.url + } + timestamp = Clock.System.now() + } + } + } } } From a27e3581b75f38d762c16aea9d9c5278c657fcdc Mon Sep 17 00:00:00 2001 From: tempest15 Date: Thu, 15 Sep 2022 18:00:56 -0400 Subject: [PATCH 18/25] remove duplicated code in message logging --- .../extensions/events/MessageDelete.kt | 70 ++++------------- .../lilybot/extensions/events/MessageEdit.kt | 77 ++++++------------- .../org/hyacinthbots/lilybot/utils/_Utils.kt | 48 ++++++++++++ 3 files changed, 87 insertions(+), 108 deletions(-) diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/MessageDelete.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/MessageDelete.kt index df78897d..938762cf 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/MessageDelete.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/MessageDelete.kt @@ -8,14 +8,14 @@ import com.kotlindiscord.kord.extensions.modules.extra.pluralkit.api.PKMessage import com.kotlindiscord.kord.extensions.modules.extra.pluralkit.events.ProxiedMessageDeleteEvent import com.kotlindiscord.kord.extensions.modules.extra.pluralkit.events.UnProxiedMessageDeleteEvent import dev.kord.core.behavior.channel.asChannelOf -import dev.kord.core.behavior.channel.createMessage +import dev.kord.core.behavior.channel.createEmbed import dev.kord.core.entity.Message import dev.kord.core.entity.channel.GuildMessageChannel -import dev.kord.rest.builder.message.create.embed import kotlinx.datetime.Clock import org.hyacinthbots.lilybot.database.collections.LoggingConfigCollection import org.hyacinthbots.lilybot.extensions.config.ConfigOptions import org.hyacinthbots.lilybot.extensions.config.ConfigType +import org.hyacinthbots.lilybot.utils.attachmentsAndProxiedMessageInfo import org.hyacinthbots.lilybot.utils.configPresent import org.hyacinthbots.lilybot.utils.getLoggingChannelWithPerms import org.hyacinthbots.lilybot.utils.ifNullOrEmpty @@ -88,59 +88,23 @@ class MessageDelete : Extension() { return } - messageLog.createMessage { - embed { - color = DISCORD_PINK - author { - name = "Message deleted" - icon = proxiedMessage?.member?.avatarUrl ?: message.author?.avatar?.url - } - description = - "Location: ${message.channel.mention} " + - "(${message.channel.asChannelOf().name})" - timestamp = Clock.System.now() - - field { - name = "Message contents" - value = message.trimmedContents().ifNullOrEmpty { "Failed to retrieve previous message contents" } - inline = false - } - - if (message.attachments.isNotEmpty()) { - field { - name = "Attachments" - value = message.attachments.map { it.url }.joinToString { "\n" } - inline = false - } - } - - if (proxiedMessage != null) { - field { - name = "Message Author:" - value = "System Member: ${proxiedMessage.member.name}\n" + - "Account: ${guild.getMember(proxiedMessage.sender).tag} " + - guild.getMember(proxiedMessage.sender).mention - inline = true - } - - field { - name = "Author ID:" - value = proxiedMessage.sender.toString() - } - } else { - field { - name = "Message Author:" - value = - "${message.author?.tag ?: "Failed to get author of message"} ${message.author?.mention ?: ""}" - inline = true - } + messageLog.createEmbed { + author { + name = "Message deleted" + icon = proxiedMessage?.member?.avatarUrl ?: message.author?.avatar?.url + } + description = + "Location: ${message.channel.mention} " + + "(${message.channel.asChannelOf().name})" + color = DISCORD_PINK + timestamp = Clock.System.now() - field { - name = "Author ID:" - value = message.author?.id.toString() - } - } + field { + name = "Message contents" + value = message.trimmedContents().ifNullOrEmpty { "Failed to retrieve previous message contents" } + inline = false } + attachmentsAndProxiedMessageInfo(guild, message, proxiedMessage) } } } diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/MessageEdit.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/MessageEdit.kt index 0661984b..f5df692d 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/MessageEdit.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/MessageEdit.kt @@ -8,14 +8,14 @@ import com.kotlindiscord.kord.extensions.modules.extra.pluralkit.api.PKMessage import com.kotlindiscord.kord.extensions.modules.extra.pluralkit.events.ProxiedMessageUpdateEvent import com.kotlindiscord.kord.extensions.modules.extra.pluralkit.events.UnProxiedMessageUpdateEvent import dev.kord.core.behavior.channel.asChannelOf -import dev.kord.core.behavior.channel.createMessage +import dev.kord.core.behavior.channel.createEmbed import dev.kord.core.entity.Message import dev.kord.core.entity.channel.GuildMessageChannel -import dev.kord.rest.builder.message.create.embed import kotlinx.datetime.Clock import org.hyacinthbots.lilybot.database.collections.LoggingConfigCollection import org.hyacinthbots.lilybot.extensions.config.ConfigOptions import org.hyacinthbots.lilybot.extensions.config.ConfigType +import org.hyacinthbots.lilybot.utils.attachmentsAndProxiedMessageInfo import org.hyacinthbots.lilybot.utils.configPresent import org.hyacinthbots.lilybot.utils.getLoggingChannelWithPerms import org.hyacinthbots.lilybot.utils.ifNullOrEmpty @@ -81,61 +81,28 @@ class MessageEdit : Extension() { getLoggingChannelWithPerms(guild, config.messageChannel!!, ConfigType.LOGGING) ?: return - messageLog.createMessage { - embed { - color = DISCORD_YELLOW - author { - name = "Message Edited" - icon = proxiedMessage?.member?.avatarUrl ?: message.author?.avatar?.url - } - description = - "Location: ${message.channel.mention} " + - "(${message.channel.asChannelOf().name})" - timestamp = Clock.System.now() - - field { - name = "Previous contents" - value = old?.trimmedContents().ifNullOrEmpty { "Failed to retrieve previous message contents" } - inline = false - } - field { - name = "New contents" - value = message.trimmedContents().ifNullOrEmpty { "Failed to retrieve new message contents" } - inline = false - } + messageLog.createEmbed { + color = DISCORD_YELLOW + author { + name = "Message Edited" + icon = proxiedMessage?.member?.avatarUrl ?: message.author?.avatar?.url + } + description = + "Location: ${message.channel.mention} " + + "(${message.channel.asChannelOf().name})" + timestamp = Clock.System.now() - if (message.attachments.isNotEmpty()) { - field { - name = "Attachments" - value = message.attachments.map { it.url }.joinToString { "\n" } - inline = false - } - } - if (proxiedMessage != null) { - field { - name = "Message Author:" - value = "System Member: ${proxiedMessage.member.name}\n" + - "Account: ${guild.getMember(proxiedMessage.sender).tag} " + - guild.getMember(proxiedMessage.sender).mention - inline = true - } - field { - name = "Author ID:" - value = proxiedMessage.sender.toString() - } - } else { - field { - name = "Message Author:" - value = - "${message.author?.tag ?: "Failed to get author of message"} ${message.author?.mention ?: ""}" - inline = true - } - field { - name = "Author ID:" - value = message.author?.id.toString() - } - } + field { + name = "Previous contents" + value = old?.trimmedContents().ifNullOrEmpty { "Failed to retrieve previous message contents" } + inline = false + } + field { + name = "New contents" + value = message.trimmedContents().ifNullOrEmpty { "Failed to retrieve new message contents" } + inline = false } + attachmentsAndProxiedMessageInfo(guild, message, proxiedMessage) } } } diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/utils/_Utils.kt b/src/main/kotlin/org/hyacinthbots/lilybot/utils/_Utils.kt index 5354959a..763bd6b2 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/utils/_Utils.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/utils/_Utils.kt @@ -6,6 +6,7 @@ import com.kotlindiscord.kord.extensions.checks.guildFor import com.kotlindiscord.kord.extensions.checks.types.CheckContext import com.kotlindiscord.kord.extensions.commands.application.slash.EphemeralSlashCommandContext import com.kotlindiscord.kord.extensions.extensions.Extension +import com.kotlindiscord.kord.extensions.modules.extra.pluralkit.api.PKMessage import com.kotlindiscord.kord.extensions.types.respond import com.kotlindiscord.kord.extensions.utils.botHasPermissions import com.kotlindiscord.kord.extensions.utils.loadModule @@ -29,6 +30,7 @@ import dev.kord.core.entity.channel.TextChannel import dev.kord.core.entity.channel.thread.ThreadChannel import dev.kord.core.exception.EntityNotFoundException import dev.kord.core.supplier.EntitySupplyStrategy +import dev.kord.rest.builder.message.EmbedBuilder import kotlinx.coroutines.delay import kotlinx.coroutines.flow.count import kotlinx.coroutines.flow.first @@ -608,3 +610,49 @@ fun Message?.trimmedContents(): String? { this.content.substring(0, 1020) + " ..." } else this.content } + +/** + * This function removed duplicated code from MessageDelete and MessageEdit. + * It holds attachment and PluralKit info fields for the logging embeds. + * @author tempest15 + * @since 4.1.0 + */ +suspend fun EmbedBuilder.attachmentsAndProxiedMessageInfo( + guild: Guild, + message: Message, + proxiedMessage: PKMessage? +) { + if (message.attachments.isNotEmpty()) { + field { + name = "Attachments" + value = message.attachments.map { it.url }.joinToString { "\n" } + inline = false + } + } + if (proxiedMessage != null) { + field { + name = "Message Author:" + value = "System Member: ${proxiedMessage.member.name}\n" + + "Account: ${guild.getMember(proxiedMessage.sender).tag} " + + guild.getMember(proxiedMessage.sender).mention + inline = true + } + + field { + name = "Author ID:" + value = proxiedMessage.sender.toString() + } + } else { + field { + name = "Message Author:" + value = + "${message.author?.tag ?: "Failed to get author of message"} ${message.author?.mention ?: ""}" + inline = true + } + + field { + name = "Author ID:" + value = message.author?.id.toString() + } + } +} From 6af20d3a9e5fe94e1eedb6928b853ef54109e996 Mon Sep 17 00:00:00 2001 From: NoComment <67918617+NoComment1105@users.noreply.github.com> Date: Tue, 20 Sep 2022 22:56:38 +0100 Subject: [PATCH 19/25] Check required roles can be ponged before adding to database (#266) --- .../lilybot/extensions/config/Config.kt | 20 +++++++++++++++++++ .../extensions/events/ThreadInviter.kt | 2 ++ .../org/hyacinthbots/lilybot/utils/_Utils.kt | 16 +++++++++++++-- 3 files changed, 36 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/config/Config.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/config/Config.kt index 546e3478..9c01187a 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/config/Config.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/config/Config.kt @@ -42,6 +42,7 @@ import org.hyacinthbots.lilybot.database.entities.LoggingConfigData import org.hyacinthbots.lilybot.database.entities.ModerationConfigData import org.hyacinthbots.lilybot.database.entities.SupportConfigData import org.hyacinthbots.lilybot.database.entities.UtilityConfigData +import org.hyacinthbots.lilybot.utils.canPingRole import org.hyacinthbots.lilybot.utils.getFirstUsableChannel import org.hyacinthbots.lilybot.utils.getLoggingChannelWithPerms import kotlin.time.Duration.Companion.seconds @@ -82,6 +83,16 @@ suspend fun Config.configCommand() = unsafeSlashCommand { return@action } + if (canPingRole(arguments.role)) { + ackEphemeral() + respondEphemeral { + content = + "I cannot use the role: ${arguments.role!!.mention}, because it is not mentionable by" + + "regular users. Please enable this in the role settings, or use a different role." + } + return@action + } + suspend fun EmbedBuilder.supportEmbed() { title = "Configuration: Support" field { @@ -224,6 +235,15 @@ suspend fun Config.configCommand() = unsafeSlashCommand { return@action } + if (canPingRole(arguments.moderatorRole)) { + respond { + content = + "I cannot use the role: ${arguments.moderatorRole!!.mention}, because it is not mentionable by" + + "regular users. Please enable this in the role settings, or use a different role." + } + return@action + } + suspend fun EmbedBuilder.moderationEmbed() { title = "Configuration: Moderation" field { diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/ThreadInviter.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/ThreadInviter.kt index 3b27dfaf..2ead90c2 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/ThreadInviter.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/ThreadInviter.kt @@ -41,6 +41,8 @@ import org.hyacinthbots.lilybot.utils.configPresent import org.hyacinthbots.lilybot.utils.getLoggingChannelWithPerms import kotlin.time.Duration.Companion.seconds +// todo This is rewritten in another branch, but said branch should take care of making sure that the target roles are +// ping able by Lily. Should/Could be done in a similar fashion to the method in extensions/config/Config.kt class ThreadInviter : Extension() { override val name = "thread-inviter" diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/utils/_Utils.kt b/src/main/kotlin/org/hyacinthbots/lilybot/utils/_Utils.kt index 763bd6b2..16aa63a6 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/utils/_Utils.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/utils/_Utils.kt @@ -15,6 +15,7 @@ import dev.kord.common.entity.Permissions import dev.kord.common.entity.PresenceStatus import dev.kord.common.entity.Snowflake import dev.kord.core.behavior.GuildBehavior +import dev.kord.core.behavior.RoleBehavior import dev.kord.core.behavior.channel.asChannelOf import dev.kord.core.behavior.channel.asChannelOfOrNull import dev.kord.core.behavior.getChannelOf @@ -619,8 +620,8 @@ fun Message?.trimmedContents(): String? { */ suspend fun EmbedBuilder.attachmentsAndProxiedMessageInfo( guild: Guild, - message: Message, - proxiedMessage: PKMessage? + message: Message, + proxiedMessage: PKMessage? ) { if (message.attachments.isNotEmpty()) { field { @@ -656,3 +657,14 @@ suspend fun EmbedBuilder.attachmentsAndProxiedMessageInfo( } } } + +/** + * Check if a role is mentionable by Lily. + * + * @param role The role to check + * @return A Boolean of whether it is pingable or not + * + * @author NoComment1105 + * @since 4.1.0 + */ +suspend fun canPingRole(role: RoleBehavior?) = role != null && role.guild.getRole(role.id).mentionable From fb4f7f7c0b0c41f35a651091b85911a124d31a77 Mon Sep 17 00:00:00 2001 From: tempest Date: Tue, 27 Sep 2022 09:32:01 -0400 Subject: [PATCH 20/25] Granular optional config (#265) * split configPresent to requireConfigs and configIsUsable * first review tweaks * combine all channel getting functions into getLoggingChannelWithPerms * replace usages of getFirstUsableChannel in Config.kt with ephemeral responses * re-add notification for and removal of invalid configs * change getFirstUsableChannel to = function --- .../lilybot/extensions/config/Config.kt | 165 ++++------- .../lilybot/extensions/events/LogUploading.kt | 66 ++--- .../extensions/events/MemberLogging.kt | 16 +- .../extensions/events/MessageDelete.kt | 14 +- .../lilybot/extensions/events/MessageEdit.kt | 14 +- .../extensions/events/ThreadInviter.kt | 19 +- .../lilybot/extensions/moderation/Report.kt | 57 +--- .../moderation/TemporaryModeration.kt | 191 ++++--------- .../moderation/TerminalModeration.kt | 135 ++++----- .../lilybot/extensions/util/GalleryChannel.kt | 24 +- .../lilybot/extensions/util/ModUtilities.kt | 50 +--- .../extensions/util/PublicUtilities.kt | 6 +- .../lilybot/extensions/util/RoleMenu.kt | 43 +-- .../lilybot/extensions/util/Tags.kt | 116 +++----- .../lilybot/extensions/util/ThreadControl.kt | 53 ++-- .../org/hyacinthbots/lilybot/utils/_Utils.kt | 260 +++++++++--------- 16 files changed, 451 insertions(+), 778 deletions(-) diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/config/Config.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/config/Config.kt index 9c01187a..a0418c5e 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/config/Config.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/config/Config.kt @@ -19,16 +19,12 @@ import com.kotlindiscord.kord.extensions.modules.unsafe.types.respondEphemeral import com.kotlindiscord.kord.extensions.types.respond import com.kotlindiscord.kord.extensions.utils.waitFor import dev.kord.common.entity.Permission -import dev.kord.common.entity.Snowflake import dev.kord.common.entity.TextInputStyle -import dev.kord.core.behavior.GuildBehavior import dev.kord.core.behavior.channel.createMessage import dev.kord.core.behavior.interaction.modal import dev.kord.core.behavior.interaction.respondEphemeral -import dev.kord.core.behavior.interaction.response.FollowupPermittingInteractionResponseBehavior import dev.kord.core.behavior.interaction.response.createEphemeralFollowup import dev.kord.core.behavior.interaction.response.respond -import dev.kord.core.entity.channel.GuildMessageChannel import dev.kord.core.event.interaction.ModalSubmitInteractionCreateEvent import dev.kord.rest.builder.message.EmbedBuilder import dev.kord.rest.builder.message.create.embed @@ -43,7 +39,6 @@ import org.hyacinthbots.lilybot.database.entities.ModerationConfigData import org.hyacinthbots.lilybot.database.entities.SupportConfigData import org.hyacinthbots.lilybot.database.entities.UtilityConfigData import org.hyacinthbots.lilybot.utils.canPingRole -import org.hyacinthbots.lilybot.utils.getFirstUsableChannel import org.hyacinthbots.lilybot.utils.getLoggingChannelWithPerms import kotlin.time.Duration.Companion.seconds @@ -175,31 +170,23 @@ suspend fun Config.configCommand() = unsafeSlashCommand { ) } - if (ModerationConfigCollection().getConfig(guild!!.id) == null) { - getLoggingChannelWithPerms( - guild!!.asGuild(), - guild!!.asGuild().getSystemChannel()?.id ?: getFirstUsableChannel(guild!!.asGuild())!!.id, - ConfigType.MODERATION, - interactionResponse - ) - } else { - getLoggingChannelWithPerms( - guild!!.asGuild(), - ModerationConfigCollection().getConfig(guild!!.id)!!.channel!!, - ConfigType.MODERATION, - interactionResponse - ) - }?.createMessage { + val utilityLog = getLoggingChannelWithPerms(ConfigOptions.UTILITY_LOG, this.getGuild()!!) + + if (utilityLog == null) { + ackEphemeral() + respondEphemeral { + content = "Consider setting a utility config to log changes to configurations." + } + return@action + } + + utilityLog.createMessage { embed { supportEmbed() field { name = "Message" value = SupportConfigCollection().getConfig(guild!!.id)?.message ?: "default" } - ModerationConfigCollection().getConfig(guild!!.id) ?: run { - description = "Consider setting the moderation configuration to receive configuration " + - "updates where you want them!" - } } } } @@ -273,15 +260,6 @@ suspend fun Config.configCommand() = unsafeSlashCommand { } } - if (getLoggingChannelWithPerms( - guild!!.asGuild(), - arguments.modActionLog?.id, - ConfigType.MODERATION - )?.id != arguments.modActionLog?.id - ) { - return@action - } - ModerationConfigCollection().setConfig( ModerationConfigData( guild!!.id, @@ -292,11 +270,16 @@ suspend fun Config.configCommand() = unsafeSlashCommand { ) ) - checkChannel( - guild, - arguments.modActionLog?.id, - interactionResponse - )?.createMessage { + val utilityLog = getLoggingChannelWithPerms(ConfigOptions.UTILITY_LOG, this.getGuild()!!) + + if (utilityLog == null) { + respond { + content = "Consider setting a utility config to log changes to configurations." + } + return@action + } + + utilityLog.createMessage { embed { moderationEmbed() } @@ -380,17 +363,18 @@ suspend fun Config.configCommand() = unsafeSlashCommand { ) ) - checkChannel( - guild, - ModerationConfigCollection().getConfig(guild!!.id)?.channel, - interactionResponse - )?.createMessage { + val utilityLog = getLoggingChannelWithPerms(ConfigOptions.UTILITY_LOG, this.getGuild()!!) + + if (utilityLog == null) { + respond { + content = "Consider setting a utility config to log changes to configurations." + } + return@action + } + + utilityLog.createMessage { embed { loggingEmbed() - ModerationConfigCollection().getConfig(guild!!.id) ?: run { - description = "Consider setting the moderation configuration to receive configuration " + - "updates where you want them!" - } } } } @@ -455,17 +439,18 @@ suspend fun Config.configCommand() = unsafeSlashCommand { ) ) - checkChannel( - guild, - ModerationConfigCollection().getConfig(guild!!.id)?.channel, - interactionResponse - )?.createMessage { + val utilityLog = getLoggingChannelWithPerms(ConfigOptions.UTILITY_LOG, this.getGuild()!!) + + if (utilityLog == null) { + respond { + content = "Consider setting a utility config to log changes to configurations." + } + return@action + } + + utilityLog.createMessage { embed { utilityEmbed() - ModerationConfigCollection().getConfig(guild!!.id) ?: run { - description = "Consider setting the moderation configuration to receive configuration " + - "updates where you want them!" - } } } } @@ -482,19 +467,20 @@ suspend fun Config.configCommand() = unsafeSlashCommand { action { suspend fun logClear() { - checkChannel( - guild, - ModerationConfigCollection().getConfig(guild!!.id)?.channel, - interactionResponse - )?.createMessage { + val utilityLog = getLoggingChannelWithPerms(ConfigOptions.UTILITY_LOG, this.getGuild()!!) + + if (utilityLog == null) { + respond { + content = "Consider setting a utility config to log changes to configurations." + } + return + } + + utilityLog.createMessage { embed { title = "Configuration Cleared: ${arguments.config[0]}${ arguments.config.substring(1, arguments.config.length).lowercase() }" - ModerationConfigCollection().getConfig(guild!!.id) ?: run { - description = "Consider setting the moderation configuration to receive configuration " + - "updates where you want them!" - } footer { text = "Config cleared by ${user.asUser().tag}" icon = user.asUser().avatar?.url @@ -677,8 +663,8 @@ suspend fun Config.configCommand() = unsafeSlashCommand { name = "Message delete logs" value = if (config.enableMessageDeleteLogs) { "Enabled\n" + - "${config.messageChannel?.let { guild!!.getChannelOrNull(it)?.mention }} " + - "${config.messageChannel?.let { guild!!.getChannelOrNull(it)?.name }}" + "${guild!!.getChannel(config.messageChannel!!).mention} " + + "${guild!!.getChannel(config.messageChannel).name }}" } else { "Disabled" } @@ -687,8 +673,8 @@ suspend fun Config.configCommand() = unsafeSlashCommand { name = "Message edit logs" value = if (config.enableMessageEditLogs) { "Enabled\n" + - "${config.messageChannel?.let { guild!!.getChannelOrNull(it)?.mention }} " + - "${config.messageChannel?.let { guild!!.getChannelOrNull(it)?.name }}" + "${guild!!.getChannel(config.messageChannel!!).mention }} " + + "${guild!!.getChannel(config.messageChannel).name }}" } else { "Disabled" } @@ -697,8 +683,8 @@ suspend fun Config.configCommand() = unsafeSlashCommand { name = "Member logs" value = if (config.enableMemberLogs) { "Enabled\n" + - "${config.memberLog?.let { guild!!.getChannelOrNull(it)?.mention }} " + - "${config.memberLog?.let { guild!!.getChannelOrNull(it)?.name }} " + "${guild!!.getChannel(config.memberLog!!).mention }} " + + "${guild!!.getChannel(config.memberLog).name }} " } else { "Disabled" } @@ -886,40 +872,3 @@ class ViewArgs : Arguments() { ) } } - -/** - * Checks the moderation config and returns where the message needs to be sent. - * - * @param guild The guild the event is in - * @param channelIdToCheck The id of the channel to check - * @param interactionResponse The response for the interaction - * @return the channel to send the message to - * @since 4.0.0 - * @author NoComment - */ -suspend inline fun checkChannel( - guild: GuildBehavior?, - channelIdToCheck: Snowflake?, - interactionResponse: FollowupPermittingInteractionResponseBehavior -): GuildMessageChannel? { - val toReturn: GuildMessageChannel? - if (ModerationConfigCollection().getConfig(guild!!.id) == null || - !ModerationConfigCollection().getConfig(guild.id)!!.enabled || - channelIdToCheck == null - ) { - toReturn = getLoggingChannelWithPerms( - guild.asGuild(), - guild.asGuild().getSystemChannel()?.id ?: getFirstUsableChannel(guild.asGuild())!!.id, - ConfigType.MODERATION, - interactionResponse - ) - } else { - toReturn = getLoggingChannelWithPerms( - guild.asGuild(), - channelIdToCheck, - ConfigType.MODERATION, - interactionResponse - ) - } - return toReturn -} diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/LogUploading.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/LogUploading.kt index 4132850e..ffcf7bd5 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/LogUploading.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/LogUploading.kt @@ -44,12 +44,14 @@ import kotlinx.serialization.Serializable import kotlinx.serialization.decodeFromString import kotlinx.serialization.json.Json import org.hyacinthbots.lilybot.database.collections.LogUploadingBlacklistCollection -import org.hyacinthbots.lilybot.database.collections.ModerationConfigCollection import org.hyacinthbots.lilybot.database.collections.SupportConfigCollection import org.hyacinthbots.lilybot.database.collections.ThreadsCollection +import org.hyacinthbots.lilybot.database.collections.UtilityConfigCollection +import org.hyacinthbots.lilybot.database.entities.SupportConfigData import org.hyacinthbots.lilybot.extensions.config.ConfigOptions import org.hyacinthbots.lilybot.utils.botHasChannelPerms -import org.hyacinthbots.lilybot.utils.configPresent +import org.hyacinthbots.lilybot.utils.configIsUsable +import org.hyacinthbots.lilybot.utils.requiredConfigs import java.io.ByteArrayInputStream import java.io.IOException import java.util.zip.GZIPInputStream @@ -81,11 +83,7 @@ class LogUploading : Extension() { event.message.author.isNullOrBot() event.message.getChannelOrNull() !is MessageChannel } - configPresent( - ConfigOptions.SUPPORT_ENABLED, - ConfigOptions.SUPPORT_CHANNEL, - ConfigOptions.LOG_UPLOADS_ENABLED - ) + requiredConfigs(ConfigOptions.LOG_UPLOADS_ENABLED) // I hate NullPointerExceptions. This is to prevent a null pointer exception if the message is a Pk one. if (channelFor(event) == null) return@check @@ -100,10 +98,15 @@ class LogUploading : Extension() { return@action } - val supportConfig = SupportConfigCollection().getConfig(guildFor(event)!!.id)!! var deferUploadUntilThread = false - if (supportConfig.enabled && event.message.channel.id == supportConfig.channel) { - deferUploadUntilThread = true + var supportConfig: SupportConfigData? = null + if (configIsUsable(ConfigOptions.SUPPORT_ENABLED, event.guildId!!) && + configIsUsable(ConfigOptions.SUPPORT_CHANNEL, event.guildId!!) + ) { + supportConfig = SupportConfigCollection().getConfig(guildFor(event)!!.id)!! + if (supportConfig.enabled && event.message.channel.id == supportConfig.channel) { + deferUploadUntilThread = true + } } val eventMessage = event.message.asMessageOrNull() // Get the message @@ -114,7 +117,7 @@ class LogUploading : Extension() { delay(4.seconds) // Delay to allow for thread creation ThreadsCollection().getOwnerThreads(eventMember!!.id).forEach { if (event.getGuild().getChannelOf(it.threadId).parentId == - supportConfig.channel + supportConfig?.channel ) { uploadChannel = event.getGuild().getChannelOf(it.threadId) return@forEach @@ -279,7 +282,6 @@ class LogUploading : Extension() { check { anyGuild() - configPresent(ConfigOptions.ACTION_LOG) hasPermission(Permission.ModerateMembers) } @@ -289,7 +291,7 @@ class LogUploading : Extension() { action { val blacklist = LogUploadingBlacklistCollection().isChannelInUploadBlacklist(guild!!.id, channel.id) - val config = ModerationConfigCollection().getConfig(guild!!.id)!! + val utilityConfig = UtilityConfigCollection().getConfig(guild!!.id)!! if (blacklist != null) { respond { @@ -304,15 +306,15 @@ class LogUploading : Extension() { content = "Log uploading is now blocked in this channel!" } - guild!!.getChannelOf(config.channel!!).createMessage { - embed { - title = "Log uploading disabled" - description = "Log uploading was disabled in ${channel.mention}" - color = DISCORD_RED - footer { - text = "Disabled by ${user.asUser().tag}" - icon = user.asUser().avatar?.url - } + if (!configIsUsable(ConfigOptions.UTILITY_LOG, guild!!.id)) return@action + + guild!!.getChannelOf(utilityConfig.utilityLogChannel!!).createEmbed { + title = "Log uploading disabled" + description = "Log uploading was disabled in ${channel.mention}" + color = DISCORD_RED + footer { + text = "Disabled by ${user.asUser().tag}" + icon = user.asUser().avatar?.url } } } @@ -330,7 +332,7 @@ class LogUploading : Extension() { return@action } - val config = ModerationConfigCollection().getConfig(guild!!.id)!! + val utilityConfig = UtilityConfigCollection().getConfig(guild!!.id)!! LogUploadingBlacklistCollection().removeLogUploadingBlacklist(guild!!.id, channel.id) @@ -338,15 +340,15 @@ class LogUploading : Extension() { content = "Log uploading is no longer blocked in this channel!" } - guild!!.getChannelOf(config.channel!!).createMessage { - embed { - title = "Log uploading re-enabled" - description = "Log uploading was re-enabled in ${channel.mention}" - color = DISCORD_GREEN - footer { - text = "Enabled by ${user.asUser().tag}" - icon = user.asUser().avatar?.url - } + if (!configIsUsable(ConfigOptions.UTILITY_LOG, guild!!.id)) return@action + + guild!!.getChannelOf(utilityConfig.utilityLogChannel!!).createEmbed { + title = "Log uploading re-enabled" + description = "Log uploading was re-enabled in ${channel.mention}" + color = DISCORD_GREEN + footer { + text = "Enabled by ${user.asUser().tag}" + icon = user.asUser().avatar?.url } } } diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/MemberLogging.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/MemberLogging.kt index 36fec55d..d4785c0e 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/MemberLogging.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/MemberLogging.kt @@ -9,12 +9,10 @@ import dev.kord.core.behavior.channel.createEmbed import dev.kord.core.event.guild.MemberJoinEvent import dev.kord.core.event.guild.MemberLeaveEvent import kotlinx.datetime.Clock -import org.hyacinthbots.lilybot.database.collections.LoggingConfigCollection import org.hyacinthbots.lilybot.extensions.config.ConfigOptions -import org.hyacinthbots.lilybot.extensions.config.ConfigType -import org.hyacinthbots.lilybot.utils.configPresent import org.hyacinthbots.lilybot.utils.getLoggingChannelWithPerms import org.hyacinthbots.lilybot.utils.getMemberCount +import org.hyacinthbots.lilybot.utils.requiredConfigs /** * Logs members joining and leaving a guild to the member log channel designated in the config for that guild. @@ -30,13 +28,11 @@ class MemberLogging : Extension() { event { check { anyGuild() - configPresent(ConfigOptions.MEMBER_LOGGING_ENABLED, ConfigOptions.MEMBER_LOG) + requiredConfigs(ConfigOptions.MEMBER_LOGGING_ENABLED, ConfigOptions.MEMBER_LOG) failIf { event.member.id == kord.selfId } } action { - val config = LoggingConfigCollection().getConfig(event.guildId)!! - val memberLog = getLoggingChannelWithPerms(event.getGuild(), config.memberLog!!, ConfigType.LOGGING) - ?: return@action + val memberLog = getLoggingChannelWithPerms(ConfigOptions.MEMBER_LOG, event.guild) ?: return@action memberLog.createEmbed { author { @@ -66,13 +62,11 @@ class MemberLogging : Extension() { event { check { anyGuild() - configPresent(ConfigOptions.MEMBER_LOGGING_ENABLED, ConfigOptions.MEMBER_LOG) + requiredConfigs(ConfigOptions.MEMBER_LOGGING_ENABLED, ConfigOptions.MEMBER_LOG) failIf { event.user.id == kord.selfId } } action { - val config = LoggingConfigCollection().getConfig(event.guildId)!! - val memberLog = getLoggingChannelWithPerms(event.getGuild(), config.memberLog!!, ConfigType.LOGGING) - ?: return@action + val memberLog = getLoggingChannelWithPerms(ConfigOptions.MEMBER_LOG, event.guild) ?: return@action memberLog.createEmbed { author { diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/MessageDelete.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/MessageDelete.kt index 938762cf..1167a6e2 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/MessageDelete.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/MessageDelete.kt @@ -12,13 +12,11 @@ import dev.kord.core.behavior.channel.createEmbed import dev.kord.core.entity.Message import dev.kord.core.entity.channel.GuildMessageChannel import kotlinx.datetime.Clock -import org.hyacinthbots.lilybot.database.collections.LoggingConfigCollection import org.hyacinthbots.lilybot.extensions.config.ConfigOptions -import org.hyacinthbots.lilybot.extensions.config.ConfigType import org.hyacinthbots.lilybot.utils.attachmentsAndProxiedMessageInfo -import org.hyacinthbots.lilybot.utils.configPresent import org.hyacinthbots.lilybot.utils.getLoggingChannelWithPerms import org.hyacinthbots.lilybot.utils.ifNullOrEmpty +import org.hyacinthbots.lilybot.utils.requiredConfigs import org.hyacinthbots.lilybot.utils.trimmedContents /** @@ -38,7 +36,7 @@ class MessageDelete : Extension() { event { check { anyGuild() - configPresent(ConfigOptions.MESSAGE_DELETE_LOGGING_ENABLED, ConfigOptions.MESSAGE_LOG) + requiredConfigs(ConfigOptions.MESSAGE_DELETE_LOGGING_ENABLED, ConfigOptions.MESSAGE_LOG) failIf { event.message?.author?.id == kord.selfId } @@ -57,7 +55,7 @@ class MessageDelete : Extension() { event { check { anyGuild() - configPresent(ConfigOptions.MESSAGE_DELETE_LOGGING_ENABLED, ConfigOptions.MESSAGE_LOG) + requiredConfigs(ConfigOptions.MESSAGE_DELETE_LOGGING_ENABLED, ConfigOptions.MESSAGE_LOG) failIf { event.message?.author?.id == kord.selfId || event.message?.author?.isBot == true @@ -79,15 +77,13 @@ class MessageDelete : Extension() { */ private suspend fun onMessageDelete(message: Message, proxiedMessage: PKMessage?) { val guild = message.getGuild() - val config = LoggingConfigCollection().getConfig(guild.id) ?: return - val messageLog = - getLoggingChannelWithPerms(message.getGuild(), config.messageChannel!!, ConfigType.LOGGING) - ?: return if (message.content.startsWith("pk;e", 0, true)) { return } + val messageLog = getLoggingChannelWithPerms(ConfigOptions.MESSAGE_LOG, guild) ?: return + messageLog.createEmbed { author { name = "Message deleted" diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/MessageEdit.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/MessageEdit.kt index f5df692d..24f97278 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/MessageEdit.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/MessageEdit.kt @@ -12,13 +12,11 @@ import dev.kord.core.behavior.channel.createEmbed import dev.kord.core.entity.Message import dev.kord.core.entity.channel.GuildMessageChannel import kotlinx.datetime.Clock -import org.hyacinthbots.lilybot.database.collections.LoggingConfigCollection import org.hyacinthbots.lilybot.extensions.config.ConfigOptions -import org.hyacinthbots.lilybot.extensions.config.ConfigType import org.hyacinthbots.lilybot.utils.attachmentsAndProxiedMessageInfo -import org.hyacinthbots.lilybot.utils.configPresent import org.hyacinthbots.lilybot.utils.getLoggingChannelWithPerms import org.hyacinthbots.lilybot.utils.ifNullOrEmpty +import org.hyacinthbots.lilybot.utils.requiredConfigs import org.hyacinthbots.lilybot.utils.trimmedContents /** @@ -37,7 +35,7 @@ class MessageEdit : Extension() { event { check { anyGuild() - configPresent(ConfigOptions.MESSAGE_EDIT_LOGGING_ENABLED, ConfigOptions.MESSAGE_LOG) + requiredConfigs(ConfigOptions.MESSAGE_EDIT_LOGGING_ENABLED, ConfigOptions.MESSAGE_LOG) failIf { event.message.asMessage().author?.id == kord.selfId } @@ -55,7 +53,7 @@ class MessageEdit : Extension() { event { check { anyGuild() - configPresent(ConfigOptions.MESSAGE_EDIT_LOGGING_ENABLED, ConfigOptions.MESSAGE_LOG) + requiredConfigs(ConfigOptions.MESSAGE_EDIT_LOGGING_ENABLED, ConfigOptions.MESSAGE_LOG) failIf { event.message.asMessage().author?.id == kord.selfId } @@ -76,10 +74,8 @@ class MessageEdit : Extension() { */ private suspend fun onMessageEdit(message: Message, old: Message?, proxiedMessage: PKMessage?) { val guild = message.getGuild() - val config = LoggingConfigCollection().getConfig(guild.id) ?: return - val messageLog = - getLoggingChannelWithPerms(guild, config.messageChannel!!, ConfigType.LOGGING) - ?: return + + val messageLog = getLoggingChannelWithPerms(ConfigOptions.MEMBER_LOG, guild) ?: return messageLog.createEmbed { color = DISCORD_YELLOW diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/ThreadInviter.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/ThreadInviter.kt index 2ead90c2..d1e81bc7 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/ThreadInviter.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/events/ThreadInviter.kt @@ -36,9 +36,8 @@ import org.hyacinthbots.lilybot.database.collections.ModerationConfigCollection import org.hyacinthbots.lilybot.database.collections.SupportConfigCollection import org.hyacinthbots.lilybot.database.collections.ThreadsCollection import org.hyacinthbots.lilybot.extensions.config.ConfigOptions -import org.hyacinthbots.lilybot.extensions.config.ConfigType -import org.hyacinthbots.lilybot.utils.configPresent import org.hyacinthbots.lilybot.utils.getLoggingChannelWithPerms +import org.hyacinthbots.lilybot.utils.requiredConfigs import kotlin.time.Duration.Companion.seconds // todo This is rewritten in another branch, but said branch should take care of making sure that the target roles are @@ -46,6 +45,8 @@ import kotlin.time.Duration.Companion.seconds class ThreadInviter : Extension() { override val name = "thread-inviter" + // note: the requireConfigs checks in this file are not perfect, + // but will be fully replaced with the thread inviting rewrite so it's ok override suspend fun setup() { /** * Thread inviting system for Support Channels @@ -63,7 +64,7 @@ class ThreadInviter : Extension() { */ check { anyGuild() - configPresent(ConfigOptions.SUPPORT_ENABLED, ConfigOptions.SUPPORT_CHANNEL, ConfigOptions.SUPPORT_ROLE) + requiredConfigs(ConfigOptions.SUPPORT_ENABLED, ConfigOptions.SUPPORT_CHANNEL, ConfigOptions.SUPPORT_ROLE) failIf { event.message.type == MessageType.ChatInputCommand || event.message.type == MessageType.ThreadCreated || @@ -93,8 +94,7 @@ class ThreadInviter : Extension() { return@action } - val supportChannel = - getLoggingChannelWithPerms(event.getGuild(), config.channel, ConfigType.SUPPORT) ?: return@action + val supportChannel = getLoggingChannelWithPerms(ConfigOptions.SUPPORT_CHANNEL, guild) ?: return@action if (textChannel != supportChannel) return@action @@ -171,7 +171,7 @@ class ThreadInviter : Extension() { */ check { anyGuild() - configPresent(ConfigOptions.SUPPORT_ENABLED, ConfigOptions.SUPPORT_CHANNEL, ConfigOptions.SUPPORT_ROLE) + requiredConfigs(ConfigOptions.SUPPORT_ENABLED, ConfigOptions.SUPPORT_CHANNEL, ConfigOptions.SUPPORT_ROLE) failIf { event.message.type == MessageType.ChatInputCommand || event.message.type == MessageType.ThreadCreated || @@ -195,9 +195,8 @@ class ThreadInviter : Extension() { var existingUserThread: TextChannelThread? = null val textChannel = event.message.getChannel().asChannelOf() val guild = event.getGuild() - val supportChannel = - getLoggingChannelWithPerms(event.getGuild(), config.channel!!, ConfigType.SUPPORT) - ?: return@action + + val supportChannel = getLoggingChannelWithPerms(ConfigOptions.SUPPORT_CHANNEL, guild) ?: return@action if (textChannel != supportChannel) return@action @@ -281,7 +280,7 @@ class ThreadInviter : Extension() { event.channel.ownerId == kord.selfId || event.channel.member != null } - configPresent( + requiredConfigs( ConfigOptions.SUPPORT_ENABLED, ConfigOptions.SUPPORT_CHANNEL, ConfigOptions.SUPPORT_ROLE, diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/Report.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/Report.kt index 129c1b35..c21e30b7 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/Report.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/Report.kt @@ -27,7 +27,6 @@ import dev.kord.core.behavior.channel.createMessage import dev.kord.core.behavior.getChannelOf import dev.kord.core.behavior.interaction.ModalParentInteractionBehavior import dev.kord.core.behavior.interaction.modal -import dev.kord.core.behavior.interaction.response.DeferredEphemeralMessageInteractionResponseBehavior import dev.kord.core.behavior.interaction.response.createEphemeralFollowup import dev.kord.core.behavior.interaction.response.edit import dev.kord.core.behavior.interaction.response.respond @@ -41,9 +40,9 @@ import dev.kord.rest.builder.message.create.embed import dev.kord.rest.request.KtorRequestException import kotlinx.datetime.Clock import org.hyacinthbots.lilybot.database.collections.ModerationConfigCollection -import org.hyacinthbots.lilybot.database.entities.ModerationConfigData import org.hyacinthbots.lilybot.extensions.config.ConfigOptions -import org.hyacinthbots.lilybot.utils.configPresent +import org.hyacinthbots.lilybot.utils.getLoggingChannelWithPerms +import org.hyacinthbots.lilybot.utils.requiredConfigs import kotlin.time.Duration.Companion.seconds /** @@ -74,12 +73,10 @@ suspend inline fun Report.reportMessageCommand() = unsafeMessageCommand { check { anyGuild() - configPresent(ConfigOptions.MODERATION_ENABLED, ConfigOptions.MODERATOR_ROLE, ConfigOptions.ACTION_LOG) + requiredConfigs(ConfigOptions.MODERATION_ENABLED, ConfigOptions.MODERATOR_ROLE, ConfigOptions.ACTION_LOG) } action { - val moderationConfig = ModerationConfigCollection().getConfig(guild!!.id)!! - val modLog = guild?.getChannelOf(moderationConfig.channel!!) val reportedMessage: Message try { @@ -106,11 +103,13 @@ suspend inline fun Report.reportMessageCommand() = unsafeMessageCommand { return@action } + val actionLog = getLoggingChannelWithPerms(ConfigOptions.ACTION_LOG, this.getGuild()!!) ?: return@action + val config = ModerationConfigCollection().getConfig(guild!!.id) ?: return@action createReportModal( event.interaction as ModalParentInteractionBehavior, user, - moderationConfig, - modLog, + config.role!!, + actionLog, reportedMessage, ) } @@ -132,7 +131,7 @@ suspend inline fun Report.reportSlashCommand() = unsafeSlashCommand(::ManualRepo check { anyGuild() - configPresent(ConfigOptions.MODERATION_ENABLED, ConfigOptions.MODERATOR_ROLE, ConfigOptions.ACTION_LOG) + requiredConfigs(ConfigOptions.MODERATION_ENABLED, ConfigOptions.MODERATOR_ROLE, ConfigOptions.ACTION_LOG) } action { @@ -180,7 +179,7 @@ suspend inline fun Report.reportSlashCommand() = unsafeSlashCommand(::ManualRepo createReportModal( event.interaction as ModalParentInteractionBehavior, user, - moderationConfig, + moderationConfig.role!!, modLog, reportedMessage, ) @@ -192,7 +191,7 @@ suspend inline fun Report.reportSlashCommand() = unsafeSlashCommand(::ManualRepo * * @param inputInteraction The interaction to create a modal in response to * @param user The user who created the [inputInteraction] - * @param config The configuration from the database for the guild + * @param moderatorRoleId The ID of the configured moderator role for the guild * @param modLog The channel for the guild that deleted messages are logged to * @param reportedMessage The message that was reported * @author tempest15 @@ -201,7 +200,7 @@ suspend inline fun Report.reportSlashCommand() = unsafeSlashCommand(::ManualRepo suspend fun createReportModal( inputInteraction: ModalParentInteractionBehavior, user: UserBehavior, - config: ModerationConfigData, + moderatorRoleId: Snowflake, modLog: GuildMessageChannel?, reportedMessage: Message, ) { @@ -229,36 +228,6 @@ suspend fun createReportModal( val reason = interaction.textInputs["reason"]!!.value!! val modalResponse = interaction.deferEphemeralResponse() - createReport( - user, - modLog, - reportedMessage, - config.role!!, - reason, - modalResponse - ) -} - -/** - * Create an embed in the [modLog] for moderators to respond to with appropriate action. - * - * @param user The user that reported the message - * @param modLog The channel to send the report embed to - * @param reportedMessage The message being reported - * @param moderatorRole The role to ping when a report is submitted - * @param reportReason The reason provided from the modal for the report - * @param modalResponse The modal interaction for the message - * @author MissCorruption - * @since 2.0 - */ -private suspend inline fun createReport( - user: UserBehavior, - modLog: GuildMessageChannel?, - reportedMessage: Message, - moderatorRole: Snowflake, - reportReason: String?, - modalResponse: DeferredEphemeralMessageInteractionResponseBehavior -) { var reportResponse: EphemeralMessageInteractionResponse? = null reportResponse = modalResponse.respond { @@ -274,7 +243,7 @@ private suspend inline fun createReport( content = "Message reported to staff" components { removeAll() } - modLog?.createMessage { content = "<@&$moderatorRole>" } + modLog?.createMessage { content = "<@&$moderatorRoleId>" } modLog?.createMessage { embed { @@ -295,7 +264,7 @@ private suspend inline fun createReport( } field { name = "Report reason:" - value = reportReason ?: "No reason provided" + value = reason } footer { text = "Reported by: ${user.asUser().tag}" diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/TemporaryModeration.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/TemporaryModeration.kt index d34b02b7..16a9a833 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/TemporaryModeration.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/TemporaryModeration.kt @@ -45,13 +45,12 @@ import kotlinx.datetime.plus import org.hyacinthbots.lilybot.database.collections.ModerationConfigCollection import org.hyacinthbots.lilybot.database.collections.WarnCollection import org.hyacinthbots.lilybot.extensions.config.ConfigOptions -import org.hyacinthbots.lilybot.extensions.config.ConfigType import org.hyacinthbots.lilybot.utils.baseModerationEmbed import org.hyacinthbots.lilybot.utils.botHasChannelPerms -import org.hyacinthbots.lilybot.utils.configPresent import org.hyacinthbots.lilybot.utils.dmNotificationStatusEmbedField import org.hyacinthbots.lilybot.utils.getLoggingChannelWithPerms import org.hyacinthbots.lilybot.utils.isBotOrModerator +import org.hyacinthbots.lilybot.utils.requiredConfigs import java.lang.Integer.min import kotlin.time.Duration @@ -75,7 +74,7 @@ class TemporaryModeration : Extension() { check { anyGuild() - configPresent(ConfigOptions.MODERATION_ENABLED, ConfigOptions.MODERATOR_ROLE, ConfigOptions.ACTION_LOG) + requiredConfigs(ConfigOptions.MODERATION_ENABLED) hasPermission(Permission.ManageMessages) requireBotPermissions(Permission.ManageMessages) botHasChannelPerms(Permissions(Permission.ManageMessages)) @@ -83,15 +82,6 @@ class TemporaryModeration : Extension() { action { val config = ModerationConfigCollection().getConfig(guild!!.id)!! - - val actionLog = - getLoggingChannelWithPerms( - guild!!.asGuild(), - config.channel!!, - ConfigType.MODERATION, - interactionResponse - ) - ?: return@action val messageAmount = arguments.messages val textChannel = channel.asChannelOf() @@ -106,6 +96,14 @@ class TemporaryModeration : Extension() { content = "Messages cleared." } + if (config.publicLogging != null && config.publicLogging == true) { + channel.createEmbed { + title = "$messageAmount messages have been cleared." + color = DISCORD_BLACK + } + } + + val actionLog = getLoggingChannelWithPerms(ConfigOptions.ACTION_LOG, this.getGuild()!!) ?: return@action actionLog.createEmbed { title = "$messageAmount messages have been cleared." description = "Action occurred in ${textChannel.mention}" @@ -115,13 +113,6 @@ class TemporaryModeration : Extension() { } color = DISCORD_BLACK } - - if (config.publicLogging != null && config.publicLogging == true) { - channel.createEmbed { - title = "$messageAmount messages have been cleared." - color = DISCORD_BLACK - } - } } } @@ -136,21 +127,15 @@ class TemporaryModeration : Extension() { check { anyGuild() - configPresent(ConfigOptions.MODERATION_ENABLED, ConfigOptions.MODERATOR_ROLE, ConfigOptions.ACTION_LOG) + // todo this code doesn't actually hard require action log and needs a refactor to make it optional + requiredConfigs(ConfigOptions.MODERATION_ENABLED, ConfigOptions.ACTION_LOG) hasPermission(Permission.ModerateMembers) requireBotPermissions(Permission.ModerateMembers) } action { val config = ModerationConfigCollection().getConfig(guild!!.id)!! - val actionLog = - getLoggingChannelWithPerms( - guild!!.asGuild(), - config.channel!!, - ConfigType.MODERATION, - interactionResponse - ) - ?: return@action + val actionLog = getLoggingChannelWithPerms(ConfigOptions.ACTION_LOG, this.getGuild()!!) ?: return@action val userArg = arguments.userArgument isBotOrModerator(userArg, "warn") ?: return@action @@ -298,23 +283,13 @@ class TemporaryModeration : Extension() { check { anyGuild() - configPresent(ConfigOptions.MODERATION_ENABLED, ConfigOptions.MODERATOR_ROLE, ConfigOptions.ACTION_LOG) + requiredConfigs(ConfigOptions.MODERATION_ENABLED) hasPermission(Permission.ModerateMembers) requireBotPermissions(Permission.ModerateMembers) } action { - val config = ModerationConfigCollection().getConfig(guild!!.id)!! - val actionLog = - getLoggingChannelWithPerms( - guild!!.asGuild(), - config.channel!!, - ConfigType.MODERATION, - interactionResponse - ) - ?: return@action val userArg = arguments.userArgument - val targetUser = guild?.getMember(userArg.id) val userStrikes = WarnCollection().getWarn(targetUser!!.id, guild!!.id)?.strikes if (userStrikes == 0 || userStrikes == null) { @@ -342,6 +317,7 @@ class TemporaryModeration : Extension() { } } + val actionLog = getLoggingChannelWithPerms(ConfigOptions.ACTION_LOG, this.getGuild()!!) ?: return@action actionLog.createEmbed { title = "Warning Removal" color = DISCORD_BLACK @@ -370,21 +346,12 @@ class TemporaryModeration : Extension() { check { anyGuild() - configPresent(ConfigOptions.MODERATION_ENABLED, ConfigOptions.MODERATOR_ROLE, ConfigOptions.ACTION_LOG) + requiredConfigs(ConfigOptions.MODERATION_ENABLED) hasPermission(Permission.ModerateMembers) requireBotPermissions(Permission.ModerateMembers) } action { - val config = ModerationConfigCollection().getConfig(guild!!.id)!! - val actionLog = - getLoggingChannelWithPerms( - guild!!.asGuild(), - config.channel!!, - ConfigType.MODERATION, - interactionResponse - ) - ?: return@action val userArg = arguments.userArgument val duration = Clock.System.now().plus(arguments.duration, TimeZone.UTC) @@ -421,22 +388,7 @@ class TemporaryModeration : Extension() { content = "Timed out ${userArg.id}" } - actionLog.createMessage { - embed { - title = "Timeout" - image = arguments.image?.url - baseModerationEmbed(arguments.reason, userArg, user) - dmNotificationStatusEmbedField(arguments.dm, dm) - timestamp = Clock.System.now() - field { - name = "Duration:" - value = duration.toDiscord(TimestampType.Default) + " (" + arguments.duration.toString() - .replace("PT", "") + ")" - inline = false - } - } - } - + val config = ModerationConfigCollection().getConfig(guild!!.id)!! if (config.publicLogging != null && config.publicLogging == true) { channel.createEmbed { title = "Timeout" @@ -450,6 +402,21 @@ class TemporaryModeration : Extension() { } } } + + val actionLog = getLoggingChannelWithPerms(ConfigOptions.ACTION_LOG, this.getGuild()!!) ?: return@action + actionLog.createEmbed { + title = "Timeout" + image = arguments.image?.url + baseModerationEmbed(arguments.reason, userArg, user) + dmNotificationStatusEmbedField(arguments.dm, dm) + timestamp = Clock.System.now() + field { + name = "Duration:" + value = duration.toDiscord(TimestampType.Default) + " (" + arguments.duration.toString() + .replace("PT", "") + ")" + inline = false + } + } } } @@ -465,21 +432,12 @@ class TemporaryModeration : Extension() { check { anyGuild() - configPresent(ConfigOptions.MODERATION_ENABLED, ConfigOptions.MODERATOR_ROLE, ConfigOptions.ACTION_LOG) + requiredConfigs(ConfigOptions.MODERATION_ENABLED) hasPermission(Permission.ModerateMembers) requireBotPermissions(Permission.ModerateMembers) } action { - val config = ModerationConfigCollection().getConfig(guild!!.id)!! - val actionLog = - getLoggingChannelWithPerms( - guild!!.asGuild(), - config.channel!!, - ConfigType.MODERATION, - interactionResponse - ) - ?: return@action val userArg = arguments.userArgument // Set timeout to null, or no timeout @@ -491,6 +449,7 @@ class TemporaryModeration : Extension() { content = "Removed timeout on ${userArg.id}" } + val actionLog = getLoggingChannelWithPerms(ConfigOptions.ACTION_LOG, this.getGuild()!!) ?: return@action actionLog.createEmbed { title = "Timeout Removed" field { @@ -524,10 +483,8 @@ class TemporaryModeration : Extension() { check { anyGuild() - configPresent( - ConfigOptions.MODERATION_ENABLED, - ConfigOptions.MODERATOR_ROLE, - ConfigOptions.ACTION_LOG + requiredConfigs( + ConfigOptions.MODERATION_ENABLED ) hasPermission(Permission.ModerateMembers) requireBotPermissions(Permission.ManageChannels) @@ -536,16 +493,6 @@ class TemporaryModeration : Extension() { @Suppress("DuplicatedCode") action { - val config = ModerationConfigCollection().getConfig(guild!!.id)!! - val actionLog = - getLoggingChannelWithPerms( - guild!!.asGuild(), - config.channel!!, - ConfigType.MODERATION, - interactionResponse - ) - ?: return@action - val channelArg = arguments.channel ?: event.interaction.getChannel() var channelParent: TextChannel? = null if (channelArg is TextChannelThread) { @@ -572,6 +519,9 @@ class TemporaryModeration : Extension() { denied += Permission.UseApplicationCommands } + respond { content = "${targetChannel.mention} has been locked." } + + val actionLog = getLoggingChannelWithPerms(ConfigOptions.ACTION_LOG, this.getGuild()!!) ?: return@action actionLog.createEmbed { title = "Channel Locked" description = "${targetChannel.mention} has been locked.\n\n**Reason:** ${arguments.reason}" @@ -582,8 +532,6 @@ class TemporaryModeration : Extension() { timestamp = Clock.System.now() color = DISCORD_RED } - - respond { content = "${targetChannel.mention} has been locked." } } } @@ -593,25 +541,14 @@ class TemporaryModeration : Extension() { check { anyGuild() - configPresent( - ConfigOptions.MODERATION_ENABLED, - ConfigOptions.MODERATOR_ROLE, - ConfigOptions.ACTION_LOG + requiredConfigs( + ConfigOptions.MODERATION_ENABLED ) hasPermission(Permission.ModerateMembers) requireBotPermissions(Permission.ManageChannels) } action { - val config = ModerationConfigCollection().getConfig(guild!!.id)!! - val actionLog = - getLoggingChannelWithPerms( - guild!!.asGuild(), - config.channel!!, - ConfigType.MODERATION, - interactionResponse - ) - ?: return@action val everyoneRole = guild!!.getRole(guild!!.id) if (!everyoneRole.permissions.contains(Permission.SendMessages)) { @@ -627,6 +564,9 @@ class TemporaryModeration : Extension() { .minus(Permission.UseApplicationCommands) } + respond { content = "Server locked." } + + val actionLog = getLoggingChannelWithPerms(ConfigOptions.ACTION_LOG, this.getGuild()!!) ?: return@action actionLog.createEmbed { title = "Server locked" description = "**Reason:** ${arguments.reason}" @@ -637,8 +577,6 @@ class TemporaryModeration : Extension() { timestamp = Clock.System.now() color = DISCORD_RED } - - respond { content = "Server locked." } } } } @@ -659,10 +597,8 @@ class TemporaryModeration : Extension() { check { anyGuild() - configPresent( - ConfigOptions.MODERATION_ENABLED, - ConfigOptions.MODERATOR_ROLE, - ConfigOptions.ACTION_LOG + requiredConfigs( + ConfigOptions.MODERATION_ENABLED ) hasPermission(Permission.ModerateMembers) requireBotPermissions(Permission.ManageChannels) @@ -671,16 +607,6 @@ class TemporaryModeration : Extension() { @Suppress("DuplicatedCode") action { - val config = ModerationConfigCollection().getConfig(guild!!.id)!! - val actionLog = - getLoggingChannelWithPerms( - guild!!.asGuild(), - config.channel!!, - ConfigType.MODERATION, - interactionResponse - ) - ?: return@action - val channelArg = arguments.channel ?: event.interaction.getChannel() var channelParent: TextChannel? = null if (channelArg is TextChannelThread) { @@ -712,6 +638,9 @@ class TemporaryModeration : Extension() { color = DISCORD_GREEN } + respond { content = "${targetChannel.mention} has been unlocked." } + + val actionLog = getLoggingChannelWithPerms(ConfigOptions.ACTION_LOG, this.getGuild()!!) ?: return@action actionLog.createEmbed { title = "Channel Unlocked" description = "${targetChannel.mention} has been unlocked." @@ -722,8 +651,6 @@ class TemporaryModeration : Extension() { timestamp = Clock.System.now() color = DISCORD_GREEN } - - respond { content = "${targetChannel.mention} has been unlocked." } } } @@ -733,25 +660,14 @@ class TemporaryModeration : Extension() { check { anyGuild() - configPresent( - ConfigOptions.MODERATION_ENABLED, - ConfigOptions.MODERATOR_ROLE, - ConfigOptions.ACTION_LOG + requiredConfigs( + ConfigOptions.MODERATION_ENABLED ) hasPermission(Permission.ModerateMembers) requireBotPermissions(Permission.ManageChannels) } action { - val config = ModerationConfigCollection().getConfig(guild!!.id)!! - val actionLog = - getLoggingChannelWithPerms( - guild!!.asGuild(), - config.channel!!, - ConfigType.MODERATION, - interactionResponse - ) - ?: return@action val everyoneRole = guild!!.getRole(guild!!.id) if (everyoneRole.permissions.contains(Permission.SendMessages)) { @@ -767,6 +683,9 @@ class TemporaryModeration : Extension() { .plus(Permission.UseApplicationCommands) } + respond { content = "Server unlocked." } + + val actionLog = getLoggingChannelWithPerms(ConfigOptions.ACTION_LOG, this.getGuild()!!) ?: return@action actionLog.createEmbed { title = "Server unlocked" footer { @@ -776,8 +695,6 @@ class TemporaryModeration : Extension() { timestamp = Clock.System.now() color = DISCORD_GREEN } - - respond { content = "Server unlocked." } } } } diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/TerminalModeration.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/TerminalModeration.kt index 580351c0..22ddf025 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/TerminalModeration.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/TerminalModeration.kt @@ -20,7 +20,6 @@ import com.kotlindiscord.kord.extensions.utils.toDuration import dev.kord.common.entity.Permission import dev.kord.core.behavior.ban import dev.kord.core.behavior.channel.createEmbed -import dev.kord.core.behavior.channel.createMessage import dev.kord.core.behavior.edit import dev.kord.core.entity.Message import dev.kord.core.exception.EntityNotFoundException @@ -32,12 +31,11 @@ import kotlinx.datetime.TimeZone import mu.KotlinLogging import org.hyacinthbots.lilybot.database.collections.ModerationConfigCollection import org.hyacinthbots.lilybot.extensions.config.ConfigOptions -import org.hyacinthbots.lilybot.extensions.config.ConfigType import org.hyacinthbots.lilybot.utils.baseModerationEmbed -import org.hyacinthbots.lilybot.utils.configPresent import org.hyacinthbots.lilybot.utils.dmNotificationStatusEmbedField import org.hyacinthbots.lilybot.utils.getLoggingChannelWithPerms import org.hyacinthbots.lilybot.utils.isBotOrModerator +import org.hyacinthbots.lilybot.utils.requiredConfigs /** * The class for permanent moderation actions, such as ban and kick. @@ -61,21 +59,12 @@ class TerminalModeration : Extension() { check { anyGuild() - configPresent(ConfigOptions.MODERATION_ENABLED, ConfigOptions.MODERATOR_ROLE, ConfigOptions.ACTION_LOG) + requiredConfigs(ConfigOptions.MODERATION_ENABLED) hasPermission(Permission.BanMembers) requireBotPermissions(Permission.BanMembers, Permission.ManageMessages) } action { - val config = ModerationConfigCollection().getConfig(guild!!.id)!! - val actionLog = - getLoggingChannelWithPerms( - guild!!.asGuild(), - config.channel!!, - ConfigType.MODERATION, - interactionResponse - ) - ?: return@action val userArg = arguments.userArgument // Clarify the user is not a bot or moderator @@ -115,22 +104,7 @@ class TerminalModeration : Extension() { content = "Banned a user" } - actionLog.createMessage { - embed { - title = "Banned a user" - description = "${userArg.mention} has been banned!" - image = arguments.image?.url - baseModerationEmbed(arguments.reason, userArg, user) - dmNotificationStatusEmbedField(arguments.dm, dm) - timestamp = Clock.System.now() - field { - name = "Days of messages deleted:" - value = arguments.messages.toString() - inline = false - } - } - } - + val config = ModerationConfigCollection().getConfig(guild!!.id) ?: return@action if (config.publicLogging != null && config.publicLogging == true) { channel.createEmbed { title = "Banned a user" @@ -138,6 +112,21 @@ class TerminalModeration : Extension() { color = DISCORD_BLACK } } + + val actionLog = getLoggingChannelWithPerms(ConfigOptions.ACTION_LOG, this.getGuild()!!) ?: return@action + actionLog.createEmbed { + title = "Banned a user" + description = "${userArg.mention} has been banned!" + image = arguments.image?.url + baseModerationEmbed(arguments.reason, userArg, user) + dmNotificationStatusEmbedField(arguments.dm, dm) + timestamp = Clock.System.now() + field { + name = "Days of messages deleted:" + value = arguments.messages.toString() + inline = false + } + } } } @@ -152,21 +141,12 @@ class TerminalModeration : Extension() { check { anyGuild() - configPresent(ConfigOptions.MODERATION_ENABLED, ConfigOptions.MODERATOR_ROLE, ConfigOptions.ACTION_LOG) + requiredConfigs(ConfigOptions.MODERATION_ENABLED) hasPermission(Permission.BanMembers) requireBotPermissions(Permission.BanMembers) } action { - val config = ModerationConfigCollection().getConfig(guild!!.id)!! - val actionLog = - getLoggingChannelWithPerms( - guild!!.asGuild(), - config.channel!!, - ConfigType.MODERATION, - interactionResponse - ) - ?: return@action val userArg = arguments.userArgument // Get all the bans into a list val bans = guild!!.bans.toList().map { it.userId } @@ -184,6 +164,7 @@ class TerminalModeration : Extension() { content = "Unbanned user" } + val actionLog = getLoggingChannelWithPerms(ConfigOptions.ACTION_LOG, this.getGuild()!!) ?: return@action actionLog.createEmbed { title = "Unbanned a user" description = "${userArg.mention} has been unbanned!\n${userArg.id} (${userArg.tag})" @@ -212,21 +193,12 @@ class TerminalModeration : Extension() { check { anyGuild() - configPresent(ConfigOptions.MODERATION_ENABLED, ConfigOptions.MODERATOR_ROLE, ConfigOptions.ACTION_LOG) + requiredConfigs(ConfigOptions.MODERATION_ENABLED) hasPermission(Permission.BanMembers) requireBotPermissions(Permission.BanMembers, Permission.ManageMessages) } action { - val config = ModerationConfigCollection().getConfig(guild!!.id)!! - val actionLog = - getLoggingChannelWithPerms( - guild!!.asGuild(), - config.channel!!, - ConfigType.MODERATION, - interactionResponse - ) - ?: return@action val userArg = arguments.userArgument isBotOrModerator(userArg, "soft-ban") ?: return@action @@ -266,22 +238,7 @@ class TerminalModeration : Extension() { content = "Soft-Banned User" } - actionLog.createMessage { - embed { - title = "Soft-Banned a user" - description = "${userArg.mention} has been soft-banned!" - image = arguments.image?.url - baseModerationEmbed(arguments.reason, userArg, user) - dmNotificationStatusEmbedField(arguments.dm, dm) - timestamp = Clock.System.now() - field { - name = "Days of messages deleted" - value = arguments.messages.toString() - inline = false - } - } - } - + val config = ModerationConfigCollection().getConfig(guild!!.id) ?: return@action if (config.publicLogging != null && config.publicLogging == true) { channel.createEmbed { title = "Soft-Banned a user" @@ -291,6 +248,21 @@ class TerminalModeration : Extension() { // Unban the user, as you're supposed to in soft-ban guild?.unban(userArg.id) + + val actionLog = getLoggingChannelWithPerms(ConfigOptions.ACTION_LOG, this.getGuild()!!) ?: return@action + actionLog.createEmbed { + title = "Soft-Banned a user" + description = "${userArg.mention} has been soft-banned!" + image = arguments.image?.url + baseModerationEmbed(arguments.reason, userArg, user) + dmNotificationStatusEmbedField(arguments.dm, dm) + timestamp = Clock.System.now() + field { + name = "Days of messages deleted" + value = arguments.messages.toString() + inline = false + } + } } } @@ -305,21 +277,12 @@ class TerminalModeration : Extension() { check { anyGuild() - configPresent(ConfigOptions.MODERATION_ENABLED, ConfigOptions.MODERATOR_ROLE, ConfigOptions.ACTION_LOG) + requiredConfigs(ConfigOptions.MODERATION_ENABLED) hasPermission(Permission.KickMembers) requireBotPermissions(Permission.KickMembers) } action { - val config = ModerationConfigCollection().getConfig(guild!!.id)!! - val actionLog = - getLoggingChannelWithPerms( - guild!!.asGuild(), - config.channel!!, - ConfigType.MODERATION, - interactionResponse - ) - ?: return@action val userArg = arguments.userArgument // Clarify the user isn't a bot or a moderator @@ -349,23 +312,23 @@ class TerminalModeration : Extension() { content = "Kicked User" } - actionLog.createMessage { - embed { - title = "Kicked a user" - description = "${userArg.mention} has been kicked!" - image = arguments.image?.url - baseModerationEmbed(arguments.reason, userArg, user) - dmNotificationStatusEmbedField(arguments.dm, dm) - timestamp = Clock.System.now() - } - } - + val config = ModerationConfigCollection().getConfig(guild!!.id) ?: return@action if (config.publicLogging != null && config.publicLogging == true) { channel.createEmbed { title = "Kicked a user" description = "${userArg.mention} has been kicked!" } } + + val actionLog = getLoggingChannelWithPerms(ConfigOptions.ACTION_LOG, this.getGuild()!!) ?: return@action + actionLog.createEmbed { + title = "Kicked a user" + description = "${userArg.mention} has been kicked!" + image = arguments.image?.url + baseModerationEmbed(arguments.reason, userArg, user) + dmNotificationStatusEmbedField(arguments.dm, dm) + timestamp = Clock.System.now() + } } } } diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/GalleryChannel.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/GalleryChannel.kt index 47172e3b..b69508f5 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/GalleryChannel.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/GalleryChannel.kt @@ -23,9 +23,7 @@ import dev.kord.rest.builder.message.create.embed import kotlinx.coroutines.delay import org.hyacinthbots.lilybot.database.collections.GalleryChannelCollection import org.hyacinthbots.lilybot.extensions.config.ConfigOptions -import org.hyacinthbots.lilybot.extensions.config.ConfigType import org.hyacinthbots.lilybot.utils.botHasChannelPerms -import org.hyacinthbots.lilybot.utils.getChannelOrFirstUsable import org.hyacinthbots.lilybot.utils.getLoggingChannelWithPerms /** @@ -61,15 +59,6 @@ class GalleryChannel : Extension() { } action { - val utilityLog = - getLoggingChannelWithPerms( - guild!!.asGuild(), - getChannelOrFirstUsable(ConfigOptions.UTILITY_LOG, guild)?.id, - ConfigType.UTILITY, - interactionResponse - ) - ?: return@action - GalleryChannelCollection().getChannels(guildFor(event)!!.id).forEach { if (channel.asChannel().id == it.channelId) { respond { @@ -85,6 +74,8 @@ class GalleryChannel : Extension() { content = "Set channel as gallery channel." } + val utilityLog = getLoggingChannelWithPerms(ConfigOptions.UTILITY_LOG, this.getGuild()!!) + ?: return@action utilityLog.createEmbed { title = "New Gallery channel" description = "${channel.mention} was added as a Gallery channel" @@ -112,15 +103,6 @@ class GalleryChannel : Extension() { } action { - val utilityLog = - getLoggingChannelWithPerms( - guild!!.asGuild(), - getChannelOrFirstUsable(ConfigOptions.UTILITY_LOG, guild)?.id, - ConfigType.UTILITY, - interactionResponse - ) - ?: return@action - var channelFound = false GalleryChannelCollection().getChannels(guildFor(event)!!.id).forEach { @@ -135,6 +117,8 @@ class GalleryChannel : Extension() { content = "Unset channel as gallery channel." } + val utilityLog = getLoggingChannelWithPerms(ConfigOptions.UTILITY_LOG, this.getGuild()!!) + ?: return@action utilityLog.createEmbed { title = "Removed Gallery channel" description = "${channel.mention} was removed as a Gallery channel" diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/ModUtilities.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/ModUtilities.kt index 73e1f1f2..597c3baf 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/ModUtilities.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/ModUtilities.kt @@ -68,12 +68,10 @@ import org.hyacinthbots.lilybot.database.collections.ThreadsCollection import org.hyacinthbots.lilybot.database.collections.UtilityConfigCollection import org.hyacinthbots.lilybot.database.collections.WarnCollection import org.hyacinthbots.lilybot.extensions.config.ConfigOptions -import org.hyacinthbots.lilybot.extensions.config.ConfigType import org.hyacinthbots.lilybot.utils.TEST_GUILD_ID import org.hyacinthbots.lilybot.utils.botHasChannelPerms -import org.hyacinthbots.lilybot.utils.configPresent -import org.hyacinthbots.lilybot.utils.getChannelOrFirstUsable import org.hyacinthbots.lilybot.utils.getLoggingChannelWithPerms +import org.hyacinthbots.lilybot.utils.requiredConfigs import org.hyacinthbots.lilybot.utils.updateDefaultPresence import kotlin.time.Duration.Companion.seconds @@ -98,22 +96,11 @@ class ModUtilities : Extension() { check { anyGuild() - configPresent(ConfigOptions.MODERATION_ENABLED, ConfigOptions.ACTION_LOG) hasPermission(Permission.ModerateMembers) requireBotPermissions(Permission.SendMessages, Permission.EmbedLinks) botHasChannelPerms(Permissions(Permission.SendMessages, Permission.EmbedLinks)) } action { - val config = ModerationConfigCollection().getConfig(guild!!.id)!! - val actionLog = - getLoggingChannelWithPerms( - guild!!.asGuild(), - config.channel!!, - ConfigType.MODERATION, - interactionResponse - ) - ?: return@action - val targetChannel: GuildMessageChannel? try { targetChannel = @@ -149,7 +136,8 @@ class ModUtilities : Extension() { respond { content = "Message sent." } - actionLog.createMessage { + val utilityLog = getLoggingChannelWithPerms(ConfigOptions.UTILITY_LOG, this.getGuild()!!) ?: return@action + utilityLog.createMessage { embed { title = "Say command used" description = "```${arguments.message}```" @@ -212,16 +200,6 @@ class ModUtilities : Extension() { } else { channel } - - val utilityLog = - getLoggingChannelWithPerms( - guild!!.asGuild(), - getChannelOrFirstUsable(ConfigOptions.UTILITY_LOG, guild)?.id, - ConfigType.UTILITY, - interactionResponse - ) - ?: return@action - val message: Message try { @@ -261,6 +239,8 @@ class ModUtilities : Extension() { respond { content = "Message edited" } + val utilityLog = getLoggingChannelWithPerms(ConfigOptions.UTILITY_LOG, this.getGuild()!!) + ?: return@action utilityLog.createMessage { embed { title = "Say message edited" @@ -311,6 +291,8 @@ class ModUtilities : Extension() { respond { content = "Embed updated" } + val utilityLog = getLoggingChannelWithPerms(ConfigOptions.UTILITY_LOG, this.getGuild()!!) + ?: return@action utilityLog.createMessage { embed { title = "Say message edited" @@ -377,7 +359,7 @@ class ModUtilities : Extension() { check { hasPermission(Permission.Administrator) - configPresent(ConfigOptions.MODERATION_ENABLED, ConfigOptions.ACTION_LOG) + requiredConfigs(ConfigOptions.MODERATION_ENABLED, ConfigOptions.ACTION_LOG) } action { @@ -414,7 +396,7 @@ class ModUtilities : Extension() { check { hasPermission(Permission.Administrator) - configPresent(ConfigOptions.MODERATION_ENABLED, ConfigOptions.ACTION_LOG) + requiredConfigs(ConfigOptions.MODERATION_ENABLED, ConfigOptions.ACTION_LOG) } action { @@ -424,19 +406,11 @@ class ModUtilities : Extension() { updateDefaultPresence() val guilds = this@ephemeralSlashCommand.kord.guilds.toList().size - val config = ModerationConfigCollection().getConfig(guild!!.id)!! - val actionLog = - getLoggingChannelWithPerms( - guild!!.asGuild(), - config.channel!!, - ConfigType.MODERATION, - interactionResponse - ) - ?: return@action - respond { content = "Presence set to default" } - actionLog.createEmbed { + val utilityLog = getLoggingChannelWithPerms(ConfigOptions.UTILITY_LOG, this.getGuild()!!) + ?: return@action + utilityLog.createEmbed { title = "Presence changed" description = "Lily's presence has been set to default." field { diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/PublicUtilities.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/PublicUtilities.kt index c82a4fed..ab00060e 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/PublicUtilities.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/PublicUtilities.kt @@ -29,7 +29,7 @@ import dev.kord.rest.request.KtorRequestException import kotlinx.datetime.Clock import org.hyacinthbots.lilybot.database.collections.UtilityConfigCollection import org.hyacinthbots.lilybot.extensions.config.ConfigOptions -import org.hyacinthbots.lilybot.utils.configPresent +import org.hyacinthbots.lilybot.utils.requiredConfigs /** * This class contains a few utility commands that can be used by the public in guilds, or that are often seen by the @@ -85,7 +85,7 @@ class PublicUtilities : Extension() { check { anyGuild() - configPresent(ConfigOptions.UTILITY_LOG) + requiredConfigs(ConfigOptions.UTILITY_LOG) } action { @@ -293,7 +293,7 @@ class PublicUtilities : Extension() { check { anyGuild() - configPresent(ConfigOptions.UTILITY_LOG) + requiredConfigs(ConfigOptions.UTILITY_LOG) } action { diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/RoleMenu.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/RoleMenu.kt index fc04aec6..24ff003b 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/RoleMenu.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/RoleMenu.kt @@ -39,9 +39,7 @@ import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.toList import org.hyacinthbots.lilybot.database.collections.RoleMenuCollection import org.hyacinthbots.lilybot.extensions.config.ConfigOptions -import org.hyacinthbots.lilybot.extensions.config.ConfigType import org.hyacinthbots.lilybot.utils.botHasChannelPerms -import org.hyacinthbots.lilybot.utils.getChannelOrFirstUsable import org.hyacinthbots.lilybot.utils.getLoggingChannelWithPerms import org.hyacinthbots.lilybot.utils.utilsLogger @@ -119,14 +117,8 @@ class RoleMenu : Extension() { mutableListOf(arguments.initialRole.id) ) - val utilityLog = - getLoggingChannelWithPerms( - guild!!.asGuild(), - getChannelOrFirstUsable(ConfigOptions.UTILITY_LOG, guild)?.id, - ConfigType.UTILITY, - interactionResponse - ) - ?: return@action + val utilityLog = getLoggingChannelWithPerms(ConfigOptions.UTILITY_LOG, this.getGuild()!!) + ?: return@action utilityLog.createMessage { embed { @@ -216,14 +208,8 @@ class RoleMenu : Extension() { data.roles ) - val utilityLog = - getLoggingChannelWithPerms( - guild!!.asGuild(), - getChannelOrFirstUsable(ConfigOptions.UTILITY_LOG, guild)?.id, - ConfigType.UTILITY, - interactionResponse - ) - ?: return@action + val utilityLog = getLoggingChannelWithPerms(ConfigOptions.UTILITY_LOG, this.getGuild()!!) + ?: return@action utilityLog.createMessage { embed { title = "Role Added to Role Menu" @@ -285,15 +271,9 @@ class RoleMenu : Extension() { } RoleMenuCollection().removeRoleFromMenu(menuMessage!!.id, arguments.role.id) - val utilityLog = - getLoggingChannelWithPerms( - guild!!.asGuild(), - getChannelOrFirstUsable(ConfigOptions.UTILITY_LOG, guild)?.id, - ConfigType.UTILITY, - interactionResponse - ) - ?: return@action + val utilityLog = getLoggingChannelWithPerms(ConfigOptions.UTILITY_LOG, this.getGuild()!!) + ?: return@action utilityLog.createMessage { embed { title = "Role Removed from Role Menu" @@ -392,15 +372,8 @@ class RoleMenu : Extension() { roles ) - val utilityLog = - getLoggingChannelWithPerms( - guild!!.asGuild(), - getChannelOrFirstUsable(ConfigOptions.UTILITY_LOG, guild)?.id, - ConfigType.UTILITY, - interactionResponse - ) - ?: return@action - + val utilityLog = getLoggingChannelWithPerms(ConfigOptions.UTILITY_LOG, this.getGuild()!!) + ?: return@action utilityLog.createMessage { embed { title = "Pronoun Role Menu Created" diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/Tags.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/Tags.kt index 385dc2d5..bd22867e 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/Tags.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/Tags.kt @@ -32,10 +32,7 @@ import kotlinx.datetime.Clock import org.hyacinthbots.lilybot.database.collections.TagsCollection import org.hyacinthbots.lilybot.database.collections.UtilityConfigCollection import org.hyacinthbots.lilybot.extensions.config.ConfigOptions -import org.hyacinthbots.lilybot.extensions.config.ConfigType import org.hyacinthbots.lilybot.utils.botHasChannelPerms -import org.hyacinthbots.lilybot.utils.configPresent -import org.hyacinthbots.lilybot.utils.getChannelOrFirstUsable import org.hyacinthbots.lilybot.utils.getLoggingChannelWithPerms /** @@ -246,15 +243,6 @@ class Tags : Extension() { } action { - val utilityLog = - getLoggingChannelWithPerms( - guild!!.asGuild(), - getChannelOrFirstUsable(ConfigOptions.UTILITY_LOG, guild)?.id, - ConfigType.UTILITY, - interactionResponse - ) - ?: return@action - if (TagsCollection().getTag(guild!!.id, arguments.tagName) != null) { respond { content = "A tag with that name already exists in this guild." } return@action @@ -276,6 +264,7 @@ class Tags : Extension() { arguments.tagAppearance ) + val utilityLog = getLoggingChannelWithPerms(ConfigOptions.UTILITY_LOG, this.getGuild()!!) ?: return@action utilityLog.createEmbed { title = "Tag created!" description = "The tag `${arguments.tagName}` has been created" @@ -333,17 +322,9 @@ class Tags : Extension() { return@action } - val utilityLog = - getLoggingChannelWithPerms( - guild!!.asGuild(), - getChannelOrFirstUsable(ConfigOptions.UTILITY_LOG, guild)?.id, - ConfigType.UTILITY, - interactionResponse - ) - ?: return@action - TagsCollection().removeTag(guild!!.id, arguments.tagName) + val utilityLog = getLoggingChannelWithPerms(ConfigOptions.UTILITY_LOG, this.getGuild()!!) ?: return@action utilityLog.createEmbed { title = "Tag deleted!" description = "The tag ${arguments.tagName} was deleted" @@ -365,22 +346,12 @@ class Tags : Extension() { check { anyGuild() - configPresent(ConfigOptions.UTILITY_LOG) hasPermission(Permission.ModerateMembers) requireBotPermissions(Permission.SendMessages, Permission.EmbedLinks) botHasChannelPerms(Permissions(Permission.SendMessages, Permission.EmbedLinks)) } action { - val utilityLog = - getLoggingChannelWithPerms( - guild!!.asGuild(), - getChannelOrFirstUsable(ConfigOptions.UTILITY_LOG, guild)?.id, - ConfigType.UTILITY, - interactionResponse - ) - ?: return@action - if (TagsCollection().getTag(guild!!.id, arguments.tagName) == null) { respond { content = "Unable to find tag `${arguments.tagName}`! Does this tag exist?" } return@action @@ -410,53 +381,52 @@ class Tags : Extension() { arguments.newAppearance ?: originalAppearance ) - utilityLog.createMessage { - embed { - title = "Tag Edited" - description = "The tag `${arguments.tagName}` was edited" - field { - name = "Name" - value = if (arguments.newName.isNullOrEmpty()) { - originalName - } else { - "$originalName -> ${arguments.newName!!}" - } - } - field { - name = "Title" - value = if (arguments.newTitle.isNullOrEmpty()) { - originalTitle - } else { - "${arguments.newTitle} -> ${arguments.newTitle!!}" - } + respond { + content = "Tag edited!" + } + + val utilityLog = getLoggingChannelWithPerms(ConfigOptions.UTILITY_LOG, this.getGuild()!!) ?: return@action + utilityLog.createEmbed { + title = "Tag Edited" + description = "The tag `${arguments.tagName}` was edited" + field { + name = "Name" + value = if (arguments.newName.isNullOrEmpty()) { + originalName + } else { + "$originalName -> ${arguments.newName!!}" } - field { - name = "Value" - value = if (arguments.newValue.isNullOrEmpty()) { - originalValue - } else { - "$originalValue -> ${arguments.newValue!!}" - } + } + field { + name = "Title" + value = if (arguments.newTitle.isNullOrEmpty()) { + originalTitle + } else { + "${arguments.newTitle} -> ${arguments.newTitle!!}" } - field { - name = "Tag appearance" - value = if (arguments.newAppearance.isNullOrEmpty()) { - originalAppearance - } else { - "$originalAppearance -> ${arguments.newAppearance}" - } + } + field { + name = "Value" + value = if (arguments.newValue.isNullOrEmpty()) { + originalValue + } else { + "$originalValue -> ${arguments.newValue!!}" } - footer { - text = "Edited by ${user.asUser().tag}" - icon = user.asUser().avatar?.url + } + field { + name = "Tag appearance" + value = if (arguments.newAppearance.isNullOrEmpty()) { + originalAppearance + } else { + "$originalAppearance -> ${arguments.newAppearance}" } - timestamp = Clock.System.now() - color = DISCORD_YELLOW } - } - - respond { - content = "Tag edited!" + footer { + text = "Edited by ${user.asUser().tag}" + icon = user.asUser().avatar?.url + } + timestamp = Clock.System.now() + color = DISCORD_YELLOW } } } diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/ThreadControl.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/ThreadControl.kt index c2a464da..6258e3ff 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/ThreadControl.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/ThreadControl.kt @@ -42,9 +42,7 @@ import kotlinx.datetime.Clock import org.hyacinthbots.lilybot.database.collections.ModerationConfigCollection import org.hyacinthbots.lilybot.database.collections.ThreadsCollection import org.hyacinthbots.lilybot.extensions.config.ConfigOptions -import org.hyacinthbots.lilybot.extensions.config.ConfigType import org.hyacinthbots.lilybot.utils.botHasChannelPerms -import org.hyacinthbots.lilybot.utils.getChannelOrFirstUsable import org.hyacinthbots.lilybot.utils.getLoggingChannelWithPerms class ThreadControl : Extension() { @@ -159,14 +157,6 @@ class ThreadControl : Extension() { action { val threadChannel = channel.asChannelOf() val member = user.asMember(guild!!.id) - val utilityLog = - getLoggingChannelWithPerms( - guild!!.asGuild(), - getChannelOrFirstUsable(ConfigOptions.UTILITY_LOG, guild)?.id, - ConfigType.UTILITY, - interactionResponse - ) - ?: return@action val oldOwnerId = ThreadsCollection().getThread(threadChannel.id)?.ownerId ?: threadChannel.ownerId val oldOwner = guild!!.getMember(oldOwnerId) @@ -194,6 +184,8 @@ class ThreadControl : Extension() { threadChannel.createMessage(content) + val utilityLog = getLoggingChannelWithPerms(ConfigOptions.UTILITY_LOG, this.getGuild()!!) + ?: return@action utilityLog.createMessage { embed { title = "Thread ownership transferred" @@ -229,15 +221,6 @@ class ThreadControl : Extension() { } action { - val utilityLog = - getLoggingChannelWithPerms( - guild!!.asGuild(), - getChannelOrFirstUsable(ConfigOptions.UTILITY_LOG, guild)?.id, - ConfigType.UTILITY, - interactionResponse - ) - ?: return@action - val threadChannel = channel.asChannelOf() val member = user.asMember(guild!!.id) if (!ownsThreadOrModerator(threadChannel, member)) return@action @@ -265,24 +248,28 @@ class ThreadControl : Extension() { label = "Yes" style = ButtonStyle.Primary - action { + action button@{ ThreadsCollection().setThreadOwner(thread.guildId, thread.threadId, thread.ownerId, false) edit { content = "Thread archiving will no longer be prevented" } + val utilityLog = getLoggingChannelWithPerms( + ConfigOptions.UTILITY_LOG, + this.getGuild()!! + ) ?: return@button utilityLog.createMessage { - embed { - title = "Thread archive prevention disabled" - color = DISCORD_FUCHSIA - - field { - name = "User" - value = user.asUser().tag - } - field { - name = "Thread" - value = threadChannel.mention - } + embed { + title = "Thread archive prevention disabled" + color = DISCORD_FUCHSIA + + field { + name = "User" + value = user.asUser().tag + } + field { + name = "Thread" + value = threadChannel.mention } } + } message!!.edit { components { removeAll() } } } } @@ -301,6 +288,8 @@ class ThreadControl : Extension() { } else if (thread?.preventArchiving == false) { ThreadsCollection().setThreadOwner(thread.guildId, thread.threadId, thread.ownerId, true) try { + val utilityLog = getLoggingChannelWithPerms(ConfigOptions.UTILITY_LOG, this.getGuild()!!) + ?: return@action utilityLog.createMessage { embed { title = "Thread archive prevention enabled" diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/utils/_Utils.kt b/src/main/kotlin/org/hyacinthbots/lilybot/utils/_Utils.kt index 16aa63a6..46c7de94 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/utils/_Utils.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/utils/_Utils.kt @@ -18,10 +18,7 @@ import dev.kord.core.behavior.GuildBehavior import dev.kord.core.behavior.RoleBehavior import dev.kord.core.behavior.channel.asChannelOf import dev.kord.core.behavior.channel.asChannelOfOrNull -import dev.kord.core.behavior.getChannelOf import dev.kord.core.behavior.getChannelOfOrNull -import dev.kord.core.behavior.interaction.response.FollowupPermittingInteractionResponseBehavior -import dev.kord.core.behavior.interaction.response.createEphemeralFollowup import dev.kord.core.entity.Guild import dev.kord.core.entity.Message import dev.kord.core.entity.User @@ -32,7 +29,6 @@ import dev.kord.core.entity.channel.thread.ThreadChannel import dev.kord.core.exception.EntityNotFoundException import dev.kord.core.supplier.EntitySupplyStrategy import dev.kord.rest.builder.message.EmbedBuilder -import kotlinx.coroutines.delay import kotlinx.coroutines.flow.count import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.map @@ -55,7 +51,6 @@ import org.hyacinthbots.lilybot.database.collections.ThreadsCollection import org.hyacinthbots.lilybot.database.collections.UtilityConfigCollection import org.hyacinthbots.lilybot.database.collections.WarnCollection import org.hyacinthbots.lilybot.extensions.config.ConfigOptions -import org.hyacinthbots.lilybot.extensions.config.ConfigType import org.koin.dsl.bind @PublishedApi @@ -69,7 +64,7 @@ internal val utilsLogger = KotlinLogging.logger("Checks Logger") * @author NoComment1105 * @since 3.2.0 */ -suspend inline fun CheckContext<*>.configPresent(vararg configOptions: ConfigOptions) { +suspend inline fun CheckContext<*>.requiredConfigs(vararg configOptions: ConfigOptions) { if (!passed) { return } @@ -269,6 +264,134 @@ suspend inline fun CheckContext<*>.configPresent(vararg configOptions: ConfigOpt } } +/** + * This function checks if a single config exists and is valid. Returns true if it is or false otherwise. + * + * @param option The config option to check the database for. Only takes a single option. + * @return True if the selected [option] is valid and enabled and false if it isn't + * @author NoComment1105 + * @since 3.2.0 + */ +suspend inline fun configIsUsable(option: ConfigOptions, guildId: Snowflake): Boolean { + when (option) { + ConfigOptions.SUPPORT_ENABLED -> return SupportConfigCollection().getConfig(guildId)?.enabled ?: false + + ConfigOptions.SUPPORT_CHANNEL -> { + val supportConfig = SupportConfigCollection().getConfig(guildId) ?: return false + return supportConfig.channel != null + } + + ConfigOptions.SUPPORT_ROLE -> { + val supportConfig = SupportConfigCollection().getConfig(guildId) ?: return false + return supportConfig.role != null + } + + ConfigOptions.MODERATION_ENABLED -> return ModerationConfigCollection().getConfig(guildId)?.enabled ?: false + + ConfigOptions.MODERATOR_ROLE -> { + val moderationConfig = ModerationConfigCollection().getConfig(guildId) ?: return false + return moderationConfig.role != null + } + + ConfigOptions.ACTION_LOG -> { + val moderationConfig = ModerationConfigCollection().getConfig(guildId) ?: return false + return moderationConfig.channel != null + } + + ConfigOptions.LOG_PUBLICLY -> { + val moderationConfig = ModerationConfigCollection().getConfig(guildId) ?: return false + return moderationConfig.publicLogging != null + } + + ConfigOptions.MESSAGE_DELETE_LOGGING_ENABLED -> + return LoggingConfigCollection().getConfig(guildId)?.enableMessageDeleteLogs ?: false + + ConfigOptions.MESSAGE_EDIT_LOGGING_ENABLED -> + return LoggingConfigCollection().getConfig(guildId)?.enableMessageEditLogs ?: false + + ConfigOptions.MESSAGE_LOG -> { + val loggingConfig = LoggingConfigCollection().getConfig(guildId) ?: return false + return loggingConfig.messageChannel != null + } + + ConfigOptions.MEMBER_LOGGING_ENABLED -> return LoggingConfigCollection().getConfig(guildId)?.enableMemberLogs ?: false + + ConfigOptions.MEMBER_LOG -> { + val loggingConfig = LoggingConfigCollection().getConfig(guildId) ?: return false + return loggingConfig.memberLog != null + } + + ConfigOptions.LOG_UPLOADS_ENABLED -> { + val utilityConfig = UtilityConfigCollection().getConfig(guildId) ?: return false + return utilityConfig.disableLogUploading + } + + ConfigOptions.UTILITY_LOG -> { + val utilityConfig = UtilityConfigCollection().getConfig(guildId) ?: return false + return utilityConfig.utilityLogChannel != null + } + } +} + +/** + * This function checks if a single config exists and is valid. Returns true if it is or false otherwise. + * + * @param channelType The type of logging channel desired + * @param guild The guild the desired channel is in + * @return The logging channel of [channelType] for the [guild] or null if it doesn't exist + * @author tempest15 + * @since 4.1.0 + */ +suspend inline fun getLoggingChannelWithPerms(channelType: ConfigOptions, guild: GuildBehavior): GuildMessageChannel? { + val guildId = guild.id + + if (!configIsUsable(channelType, guildId)) return null + + val channelId = when (channelType) { + ConfigOptions.SUPPORT_CHANNEL -> SupportConfigCollection().getConfig(guildId)?.channel ?: return null + ConfigOptions.ACTION_LOG -> ModerationConfigCollection().getConfig(guildId)?.channel ?: return null + ConfigOptions.UTILITY_LOG -> UtilityConfigCollection().getConfig(guildId)?.utilityLogChannel ?: return null + ConfigOptions.MESSAGE_LOG -> LoggingConfigCollection().getConfig(guildId)?.messageChannel ?: return null + ConfigOptions.MEMBER_LOG -> LoggingConfigCollection().getConfig(guildId)?.memberLog ?: return null + else -> throw IllegalArgumentException("$channelType does not point to a channel.") + } + val channel = guild.getChannelOfOrNull(channelId) ?: return null + + if (!channel.botHasPermissions(Permission.ViewChannel) || !channel.botHasPermissions(Permission.SendMessages)) { + when (channelType) { + ConfigOptions.SUPPORT_CHANNEL -> SupportConfigCollection().clearConfig(guildId) + ConfigOptions.ACTION_LOG -> ModerationConfigCollection().clearConfig(guildId) + ConfigOptions.UTILITY_LOG -> UtilityConfigCollection().clearConfig(guildId) + ConfigOptions.MESSAGE_LOG -> LoggingConfigCollection().clearConfig(guildId) + ConfigOptions.MEMBER_LOG -> LoggingConfigCollection().clearConfig(guildId) + else -> throw IllegalArgumentException("$channelType does not point to a channel.") + } + getFirstUsableChannel(guild)?.createMessage( + "Lily is unable to send messages in the configured " + + "${channelType.toString().lowercase()} for this guild. " + + "As a result, the corresponding config has been reset. \n\n" + + "*Note:* this channel has been used to send this message because it's the first channel " + + "in the guild Lily could use. Please inform this guild's staff about this message." + ) + return null + } + + return channel +} + +/** + * Get the first text channel the bot can send a message in. + * + * @param inputGuild The guild in which to get the channel. + * @return The first text channel the bot can send a message in or null if there isn't one. + * @author tempest15 + * @since 3.5.4 + */ +suspend inline fun getFirstUsableChannel(inputGuild: GuildBehavior): GuildMessageChannel? = + inputGuild.channels.first { + it.botHasPermissions(Permission.ViewChannel, Permission.SendMessages) + }.asChannelOfOrNull() + /** * Gets the channel of the event and checks that the bot has the required [permissions]. * @@ -458,131 +581,6 @@ suspend inline fun ExtensibleBotBuilder.database(migrate: Boolean) { } } -/** - * Get the first text channel the bot can send a message in. - * - * @param inputGuild The guild in which to get the channel. - * @return The first text channel the bot can send a message in or null if there isn't one. - * @author tempest15 - * @since 3.5.4 - */ -suspend inline fun getFirstUsableChannel(inputGuild: Guild): GuildMessageChannel? = inputGuild.channels.first { - it.botHasPermissions(Permission.ViewChannel, Permission.SendMessages) -}.fetchChannelOrNull()?.asChannelOfOrNull() - -/** - * Check if the bot can send messages in a guild's configured logging channel. - * If the bot can't, reset a config and send a message in the top usable channel saying that the config was reset or - * if this function is in a command, an [interactionResponse] is provided, allowing a response to be given on the - * command. - * If the bot can, return the channel. - * - * **DO NOT USE THIS FUNCTION ON NON-MODERATION CHANNELS!** Use the [botHasChannelPerms] check instead. - * - * @param inputGuild The guild to check in. - * @param targetChannel The channel to check permissions for - * @param configType The config the channel will be in - * @param interactionResponse The interactionResponse to respond to if this function is in a command. - * @return The channel or null if it does not have the correct permissions. - * @author tempest15 - * @since 3.5.4 - */ -suspend inline fun getLoggingChannelWithPerms( - inputGuild: Guild, - targetChannel: Snowflake?, - configType: ConfigType, - interactionResponse: T? = null -): GuildMessageChannel? { - val channel = targetChannel?.let { inputGuild.getChannelOfOrNull(it) } - - // Check each permission in a separate check because all in one expects all to be there or not. This allows for - // some permissions to be false and some to be true while still producing the correct result. - if (channel?.botHasPermissions(Permission.ViewChannel) != true || - !channel.botHasPermissions(Permission.SendMessages) || - !channel.botHasPermissions(Permission.EmbedLinks) - ) { - val usableChannel = getFirstUsableChannel(inputGuild) ?: return null - - if (interactionResponse == null) { - usableChannel.createMessage( - "Lily cannot send messages in ${channel?.mention}. " + - "As a result, your config has been reset. " + - "Please fix the permissions before setting a new config." - ) - } else { - interactionResponse.createEphemeralFollowup { - content = "Lily cannot send messages in ${channel?.mention}. " + - "As a result, your config has been reset. " + - "Please fix the permissions before setting a new config." - } - } - - delay(3000) // So that other events may finish firing - when (configType) { - ConfigType.MODERATION -> ModerationConfigCollection().clearConfig(usableChannel.guildId) - ConfigType.LOGGING -> LoggingConfigCollection().clearConfig(usableChannel.guildId) - ConfigType.SUPPORT -> SupportConfigCollection().clearConfig(usableChannel.guildId) - ConfigType.UTILITY -> UtilityConfigCollection().clearConfig(usableChannel.guildId) - ConfigType.ALL -> { - ModerationConfigCollection().clearConfig(usableChannel.guildId) - LoggingConfigCollection().clearConfig(usableChannel.guildId) - SupportConfigCollection().clearConfig(usableChannel.guildId) - UtilityConfigCollection().clearConfig(usableChannel.guildId) - } - } - - return null - } - - return channel -} - -/** - * Overload function for [getLoggingChannelWithPerms] that does not take an interaction response allowing the type - * variable not be specified in the function. - * - * **DO NOT USE THIS FUNCTION ON NON-MODERATION CHANNELS!** Use the [botHasChannelPerms] check instead. - * - * @see getLoggingChannelWithPerms - * - * @param inputGuild The guild to check in. - * @param targetChannel The channel to check permissions for - * @param configType The config the channel will be in - * @return The channel or null if it does not have the correct permissions. - * @author NoComment1105 - */ -suspend inline fun getLoggingChannelWithPerms( - inputGuild: Guild, - targetChannel: Snowflake?, - configType: ConfigType -): GuildMessageChannel? = - getLoggingChannelWithPerms(inputGuild, targetChannel, configType, null) - -/** - * A small function to get a log of a guild or the first available channel. - * - * @param configOption The option to get the channel of - * @param guild The guild for the channel - * @return The utility log or the first usable channel - * @throws IllegalArgumentException when the [configOption] is invalid - * @author NoComment1105 - * @since 4.0.1 - */ -suspend inline fun getChannelOrFirstUsable(configOption: ConfigOptions, guild: GuildBehavior?): GuildMessageChannel? { - val channel = when (configOption) { - ConfigOptions.ACTION_LOG -> ModerationConfigCollection().getConfig(guild!!.id)?.channel - ConfigOptions.MESSAGE_LOG -> LoggingConfigCollection().getConfig(guild!!.id)?.messageChannel - ConfigOptions.MEMBER_LOG -> LoggingConfigCollection().getConfig(guild!!.id)?.memberLog - ConfigOptions.UTILITY_LOG -> UtilityConfigCollection().getConfig(guild!!.id)?.utilityLogChannel - else -> throw IllegalArgumentException("Config Option $configOption does not point to a channel.") - } - return if (channel != null) { - guild.getChannelOf(channel) - } else { - guild.asGuild().getSystemChannel() ?: getFirstUsableChannel(guild.asGuild()) - } -} - /** * Utility to get a string or a default value. * Basically String.ifEmpty but works with nullable strings From 9068edb5938863d8ea7d8c9a924ed4e971724e88 Mon Sep 17 00:00:00 2001 From: tempest15 Date: Wed, 28 Sep 2022 21:58:57 -0400 Subject: [PATCH 21/25] check channel perms on config set --- .../lilybot/extensions/config/Config.kt | 78 ++++++++++++++++--- 1 file changed, 69 insertions(+), 9 deletions(-) diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/config/Config.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/config/Config.kt index a0418c5e..2adaf3f6 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/config/Config.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/config/Config.kt @@ -17,14 +17,17 @@ import com.kotlindiscord.kord.extensions.modules.unsafe.types.InitialSlashComman import com.kotlindiscord.kord.extensions.modules.unsafe.types.ackEphemeral import com.kotlindiscord.kord.extensions.modules.unsafe.types.respondEphemeral import com.kotlindiscord.kord.extensions.types.respond +import com.kotlindiscord.kord.extensions.utils.botHasPermissions import com.kotlindiscord.kord.extensions.utils.waitFor import dev.kord.common.entity.Permission import dev.kord.common.entity.TextInputStyle import dev.kord.core.behavior.channel.createMessage +import dev.kord.core.behavior.getChannelOfOrNull import dev.kord.core.behavior.interaction.modal import dev.kord.core.behavior.interaction.respondEphemeral import dev.kord.core.behavior.interaction.response.createEphemeralFollowup import dev.kord.core.behavior.interaction.response.respond +import dev.kord.core.entity.channel.TextChannel import dev.kord.core.event.interaction.ModalSubmitInteractionCreateEvent import dev.kord.rest.builder.message.EmbedBuilder import dev.kord.rest.builder.message.create.embed @@ -88,6 +91,20 @@ suspend fun Config.configCommand() = unsafeSlashCommand { return@action } + var supportChannel: TextChannel? = null + if (arguments.enable && arguments.channel != null) { + supportChannel = guild!!.getChannelOfOrNull(arguments.channel!!.id) + if (supportChannel?.botHasPermissions(Permission.ViewChannel, Permission.SendMessages) != true) { + ackEphemeral() + respondEphemeral { + content = "The mod action log you've selected is invalid, or I can't view it. " + + "Please attempt to resolve this and try again." + } + return@action + } + } + supportChannel ?: return@action + suspend fun EmbedBuilder.supportEmbed() { title = "Configuration: Support" field { @@ -231,6 +248,19 @@ suspend fun Config.configCommand() = unsafeSlashCommand { return@action } + var modActionLog: TextChannel? = null + if (arguments.enabled && arguments.modActionLog != null) { + modActionLog = guild!!.getChannelOfOrNull(arguments.modActionLog!!.id) + if (modActionLog?.botHasPermissions(Permission.ViewChannel, Permission.SendMessages) != true) { + respond { + content = "The mod action log you've selected is invalid, or I can't view it. " + + "Please attempt to resolve this and try again." + } + return@action + } + } + modActionLog ?: return@action + suspend fun EmbedBuilder.moderationEmbed() { title = "Configuration: Moderation" field { @@ -314,6 +344,32 @@ suspend fun Config.configCommand() = unsafeSlashCommand { return@action } + var memberLog: TextChannel? = null + if (arguments.enableMemberLogging && arguments.memberLog != null) { + memberLog = guild!!.getChannelOfOrNull(arguments.memberLog!!.id) + if (memberLog?.botHasPermissions(Permission.ViewChannel, Permission.SendMessages) != true) { + respond { + content = "The member log you've selected is invalid, or I can't view it. " + + "Please attempt to resolve this and try again." + } + return@action + } + } + memberLog ?: return@action + + var messageLog: TextChannel? = null + if ((arguments.enableMessageDeleteLogs || arguments.enableMessageEditLogs) && arguments.memberLog != null) { + messageLog = guild!!.getChannelOfOrNull(arguments.messageLogs!!.id) + if (messageLog?.botHasPermissions(Permission.ViewChannel, Permission.SendMessages) != true) { + respond { + content = "The message log you've selected is invalid, or I can't view it. " + + "Please attempt to resolve this and try again." + } + return@action + } + } + messageLog ?: return@action + suspend fun EmbedBuilder.loggingEmbed() { title = "Configuration: Logging" field { @@ -400,6 +456,19 @@ suspend fun Config.configCommand() = unsafeSlashCommand { return@action } + var utilityLog: TextChannel? = null + if (arguments.utilityLogChannel != null) { + utilityLog = guild!!.getChannelOfOrNull(arguments.utilityLogChannel!!.id) + if (utilityLog?.botHasPermissions(Permission.ViewChannel, Permission.SendMessages) != true) { + respond { + content = "The utility log you've selected is invalid, or I can't view it. " + + "Please attempt to resolve this and try again." + } + return@action + } + } + utilityLog ?: return@action + suspend fun EmbedBuilder.utilityEmbed() { title = "Configuration: Utility" field { @@ -439,15 +508,6 @@ suspend fun Config.configCommand() = unsafeSlashCommand { ) ) - val utilityLog = getLoggingChannelWithPerms(ConfigOptions.UTILITY_LOG, this.getGuild()!!) - - if (utilityLog == null) { - respond { - content = "Consider setting a utility config to log changes to configurations." - } - return@action - } - utilityLog.createMessage { embed { utilityEmbed() From 3066f357693cbdabf83ffd701b045ed82ad212b3 Mon Sep 17 00:00:00 2001 From: tempest15 Date: Wed, 28 Sep 2022 22:54:20 -0400 Subject: [PATCH 22/25] add changelog-template.md --- docs/changelogs/changelog-template.md | 46 +++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 docs/changelogs/changelog-template.md diff --git a/docs/changelogs/changelog-template.md b/docs/changelogs/changelog-template.md new file mode 100644 index 00000000..aa412992 --- /dev/null +++ b/docs/changelogs/changelog-template.md @@ -0,0 +1,46 @@ +# LilyBot X.X.X + +Throughout this file, examples are placed in code blocks. + +X.X.X above should be replaced with the version number where +* breaking versions are something that requires a significant change in the environment to run Lily +* major versions have a fair few features, changes, and fixes +* minor versions are simply a patch + +Next should be a description of the features of the update. +``` +This long-awaited update rewrites Lily in Rust. This will be the last update, as the bot is now perfect. +``` + +It's important that this end with the following statement. +``` +You can find the full changelog below. +``` + +The changelog should then be split into three categories: New, Change, & Fix. +These are fairly self-explanatory buckets, with new features going in the first, +changes to existing functionality going in the second, and restorations of intended functionality in the third. + +``` +New: +* very memory safe +* lots of crabs + +Change: +* use rust instead of Kotlin +* auto-ban anyone who says the word Kotlin + +Fix: +* literally every bug, Rust is perfect +* we've even fixed bugs Discord hasn't thought of +``` + +The changelog should then end with the following +``` +You can find a list of all the commits in this update [here](https://github.com/hyacinthbots/LilyBot/compare/vP.P.P...vX.X.X) +``` +where P.P.P is replaced with the previous version number and X.X.X is the new version number. + +The changelog should be copied and pasted into the GitHub release, excepting the header. +This changelog can then be trimmed or adjusted if necessary for publication on Discord. +If need be, a more concise version can be sent out via the Lily's announcement system. From 41862b7b740037799d09eb8e46bd786a1b6511a1 Mon Sep 17 00:00:00 2001 From: tempest15 Date: Wed, 28 Sep 2022 22:56:42 -0400 Subject: [PATCH 23/25] 4.1.0 changelog --- docs/changelogs/4.1.0.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 docs/changelogs/4.1.0.md diff --git a/docs/changelogs/4.1.0.md b/docs/changelogs/4.1.0.md new file mode 100644 index 00000000..eeb18104 --- /dev/null +++ b/docs/changelogs/4.1.0.md @@ -0,0 +1,29 @@ +# LilyBot 4.1.0 + +This release tracks down many old bugs, fixes a lot of errors, and adds a nice new feature or two. +You can find the full changelog below. + +New: +* message edit logging +* add an announcements system to allow distribution of messages across every guild Lily is in +* command to completely reset the database for a guild +* command to view set configs +* log when message tags are sent + +Change: +* require only the bare minimum config for each feature, + with additional functionality coming with additional configs +* re-add member counts to member logging +* images can now be attached to commands directly rather than providing a link +* check that Lily can view and send messages in configured channels +* check that roles can be pinged before adding them to the database + +Fix: +* "required content missing" errors in log uploading +* broken reminder interval field +* missing parameter on log uploading command +* log channel name instead of enabled or disabled when setting a utility log +* inconsistent timestamps when editing messages sent through Lily +* pk;e being logged as a deleted message + +You can find a list of all the commits in this update [here](https://github.com/hyacinthbots/LilyBot/compare/v4.0.1...v4.1.0) From 2f68a5019455ef56677bd6afb0091a92ee548775 Mon Sep 17 00:00:00 2001 From: NoComment <67918617+NoComment1105@users.noreply.github.com> Date: Thu, 29 Sep 2022 13:11:53 +0100 Subject: [PATCH 24/25] Guild announcements (#267) * Initial work on announcement system * Send the message * Make announcements work and add docs * Catch when announcements are empty * add getSystemChannelWithPerms, add opt-out for config reset in getLoggingChannelWithPerms * simplify and condense announcement logic * re-add announcement header * Update dependencies Co-authored-by: tempest15 --- docs/commanddocs.toml | 6 + docs/commands.md | 12 ++ libs.versions.toml | 8 +- .../org/hyacinthbots/lilybot/LilyBot.kt | 2 + .../extensions/util/GuildAnnouncements.kt | 147 ++++++++++++++++++ .../org/hyacinthbots/lilybot/utils/_Utils.kt | 55 +++++-- 6 files changed, 211 insertions(+), 19 deletions(-) create mode 100644 src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/GuildAnnouncements.kt diff --git a/docs/commanddocs.toml b/docs/commanddocs.toml index 4ea74a23..e94a2655 100644 --- a/docs/commanddocs.toml +++ b/docs/commanddocs.toml @@ -37,6 +37,12 @@ name = "config clear" args = "* `config-type` - The type of config to clear, 'support', 'moderation', 'logging', 'miscellaneous', 'all' - String Choice" result = "Clears the config of the specified type." permissions = "Manage Guild" + +[[command]] +category = "Administration commands" +name = "announcement" +result = "Produces a modal for inputting the announcement content, then sends it to every guild the bot is in. Only works in the bots `TEST_GUILD_ID`" +permissions = "Administrator" # End administration commands # Moderation commands diff --git a/docs/commands.md b/docs/commands.md index fd78eac6..ed2300b6 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -78,6 +78,18 @@ These are commands for the maintenance of LilyBot. The can only be run by Server --- +### Name: `announcement` +**Arguments**: +None + +**Result**: Produces a modal for inputting the announcement content, then sends it to every guild the bot is in. Only works in the bots `TEST_GUILD_ID` + +**Required Permissions**: `Administrator` + +**Command category**: `Administration commands` + +--- + ## Moderation commands These commands are for use by moderators. They utilize built-in permission checks. All moderation commands are logged to the modActionLog established in the config. A Direct Message is sent to the target user containing the sanction they received and the provided reason. If Lily fails to DM them, this failure will be noted in the logging embed. diff --git a/libs.versions.toml b/libs.versions.toml index db566bfd..c63d5b72 100644 --- a/libs.versions.toml +++ b/libs.versions.toml @@ -1,11 +1,11 @@ [versions] kotlin = "1.7.10" # Note: Plugin versions must be updated in the settings.gradle.kts too -groovy = "3.0.12" -kord-extensions = "1.5.5-20220914.170355-31" -logging = "2.1.23" +groovy = "3.0.13" +kord-extensions = "1.5.5-20220925.092000-32" +logging = "2.1.23" # Cannot be updated to 3.0.0 because we need newer logback, which requires an XML file, which throws errors I cannot fix logback = "1.2.8" -github-api = "1.308" +github-api = "1.313" kmongo = "4.7.1" detekt = "1.21.0" koma = "1.1.0" diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/LilyBot.kt b/src/main/kotlin/org/hyacinthbots/lilybot/LilyBot.kt index cdcb3119..7c096bf0 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/LilyBot.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/LilyBot.kt @@ -25,6 +25,7 @@ import org.hyacinthbots.lilybot.extensions.moderation.TemporaryModeration import org.hyacinthbots.lilybot.extensions.moderation.TerminalModeration import org.hyacinthbots.lilybot.extensions.util.GalleryChannel import org.hyacinthbots.lilybot.extensions.util.Github +import org.hyacinthbots.lilybot.extensions.util.GuildAnnouncements import org.hyacinthbots.lilybot.extensions.util.InfoCommands import org.hyacinthbots.lilybot.extensions.util.ModUtilities import org.hyacinthbots.lilybot.extensions.util.PublicUtilities @@ -77,6 +78,7 @@ suspend fun main() { add(::Github) add(::GalleryChannel) add(::InfoCommands) + add(::GuildAnnouncements) add(::GuildLogging) add(::LogUploading) add(::MemberLogging) diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/GuildAnnouncements.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/GuildAnnouncements.kt new file mode 100644 index 00000000..cbec7ca4 --- /dev/null +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/util/GuildAnnouncements.kt @@ -0,0 +1,147 @@ +package org.hyacinthbots.lilybot.extensions.util + +import com.kotlindiscord.kord.extensions.checks.hasPermission +import com.kotlindiscord.kord.extensions.components.components +import com.kotlindiscord.kord.extensions.components.ephemeralButton +import com.kotlindiscord.kord.extensions.extensions.Extension +import com.kotlindiscord.kord.extensions.modules.unsafe.annotations.UnsafeAPI +import com.kotlindiscord.kord.extensions.modules.unsafe.extensions.unsafeSlashCommand +import com.kotlindiscord.kord.extensions.modules.unsafe.types.InitialSlashCommandResponse +import com.kotlindiscord.kord.extensions.utils.waitFor +import dev.kord.common.Color +import dev.kord.common.entity.ButtonStyle +import dev.kord.common.entity.Permission +import dev.kord.common.entity.TextInputStyle +import dev.kord.core.behavior.channel.createEmbed +import dev.kord.core.behavior.interaction.modal +import dev.kord.core.behavior.interaction.response.createEphemeralFollowup +import dev.kord.core.behavior.interaction.response.edit +import dev.kord.core.behavior.interaction.response.respond +import dev.kord.core.entity.interaction.response.EphemeralMessageInteractionResponse +import dev.kord.core.event.interaction.ModalSubmitInteractionCreateEvent +import dev.kord.rest.builder.message.create.embed +import kotlinx.coroutines.flow.toList +import org.hyacinthbots.lilybot.extensions.config.ConfigOptions +import org.hyacinthbots.lilybot.utils.TEST_GUILD_ID +import org.hyacinthbots.lilybot.utils.getFirstUsableChannel +import org.hyacinthbots.lilybot.utils.getLoggingChannelWithPerms +import org.hyacinthbots.lilybot.utils.getSystemChannelWithPerms +import kotlin.time.Duration.Companion.seconds + +@OptIn(UnsafeAPI::class) +class GuildAnnouncements : Extension() { + override val name = "guild-announcements" + + override suspend fun setup() { + unsafeSlashCommand { + name = "announcement" + description = "Send an announcement to all guilds Lily is in" + + initialResponse = InitialSlashCommandResponse.None + + guild(TEST_GUILD_ID) + + check { + hasPermission(Permission.Administrator) + } + + action { + val footer = "Sent by ${user.asUser().tag}" // Useless, just needed for length calculations + + val modal = event.interaction.modal("Send an announcement", "announcementModal") { + actionRow { + textInput(TextInputStyle.Short, "header", "Announcement Header") { + placeholder = "Version 7.6.5!" + allowedLength = IntRange(1, 250) + required = false + } + } + actionRow { + textInput(TextInputStyle.Paragraph, "body", "Announcement Body") { + placeholder = "We're happy to announce Lily is now written in Rust! " + + "It turns out we really like crabs." + allowedLength = IntRange(1, 1750 - footer.length) + required = false + } + } + } + + val interaction = + modal.kord.waitFor(300.seconds.inWholeMilliseconds) { + interaction.modalId == "announcementModal" + }?.interaction + + if (interaction == null) { + modal.createEphemeralFollowup { + embed { + description = "Announcement timed out" + } + } + return@action + } + + val body = interaction.textInputs["body"]!!.value + val header = interaction.textInputs["header"]!!.value + val modalResponse = interaction.deferEphemeralResponse() + + if (body.isNullOrEmpty() && header.isNullOrEmpty()) { + modalResponse.respond { + content = "Your announcement cannot be completely empty!" + } + return@action + } + + var response: EphemeralMessageInteractionResponse? = null + + response = modalResponse.respond { + content = "Would you like to send this message? It will be delivered to all servers this bot is in." + components { + ephemeralButton(0) { + label = "Yes" + style = ButtonStyle.Success + + action { + response?.edit { + content = "Message sent!" + components { removeAll() } + } + + event.kord.guilds.toList().chunked(15).forEach { chunk -> + for (it in chunk) { + val channel = + getLoggingChannelWithPerms(ConfigOptions.UTILITY_LOG, it) + ?: getLoggingChannelWithPerms(ConfigOptions.ACTION_LOG, it) + ?: getSystemChannelWithPerms(it) + ?: getFirstUsableChannel(it) + ?: return@forEach + + channel.createEmbed { + title = header + description = body + color = Color(0x7B52AE) + footer { + text = footer + } + } + } + } + } + } + + ephemeralButton(0) { + label = "No" + style = ButtonStyle.Danger + + action { + response?.edit { + content = "Message not sent." + components { removeAll() } + } + } + } + } + } + } + } + } +} diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/utils/_Utils.kt b/src/main/kotlin/org/hyacinthbots/lilybot/utils/_Utils.kt index 46c7de94..3534943e 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/utils/_Utils.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/utils/_Utils.kt @@ -338,11 +338,17 @@ suspend inline fun configIsUsable(option: ConfigOptions, guildId: Snowflake): Bo * * @param channelType The type of logging channel desired * @param guild The guild the desired channel is in + * @param resetConfig If configured channels should be reset if invalid. + * Should only be passed as false, and defaults to true. * @return The logging channel of [channelType] for the [guild] or null if it doesn't exist * @author tempest15 * @since 4.1.0 */ -suspend inline fun getLoggingChannelWithPerms(channelType: ConfigOptions, guild: GuildBehavior): GuildMessageChannel? { +suspend inline fun getLoggingChannelWithPerms( + channelType: ConfigOptions, + guild: GuildBehavior, + resetConfig: Boolean? = null +): GuildMessageChannel? { val guildId = guild.id if (!configIsUsable(channelType, guildId)) return null @@ -358,21 +364,24 @@ suspend inline fun getLoggingChannelWithPerms(channelType: ConfigOptions, guild: val channel = guild.getChannelOfOrNull(channelId) ?: return null if (!channel.botHasPermissions(Permission.ViewChannel) || !channel.botHasPermissions(Permission.SendMessages)) { - when (channelType) { - ConfigOptions.SUPPORT_CHANNEL -> SupportConfigCollection().clearConfig(guildId) - ConfigOptions.ACTION_LOG -> ModerationConfigCollection().clearConfig(guildId) - ConfigOptions.UTILITY_LOG -> UtilityConfigCollection().clearConfig(guildId) - ConfigOptions.MESSAGE_LOG -> LoggingConfigCollection().clearConfig(guildId) - ConfigOptions.MEMBER_LOG -> LoggingConfigCollection().clearConfig(guildId) - else -> throw IllegalArgumentException("$channelType does not point to a channel.") + if (resetConfig == true) { + when (channelType) { + ConfigOptions.SUPPORT_CHANNEL -> SupportConfigCollection().clearConfig(guildId) + ConfigOptions.ACTION_LOG -> ModerationConfigCollection().clearConfig(guildId) + ConfigOptions.UTILITY_LOG -> UtilityConfigCollection().clearConfig(guildId) + ConfigOptions.MESSAGE_LOG -> LoggingConfigCollection().clearConfig(guildId) + ConfigOptions.MEMBER_LOG -> LoggingConfigCollection().clearConfig(guildId) + else -> throw IllegalArgumentException("$channelType does not point to a channel.") + } + val informChannel = getSystemChannelWithPerms(guild as Guild) ?: getFirstUsableChannel(guild) + informChannel?.createMessage( + "Lily is unable to send messages in the configured " + + "${channelType.toString().lowercase()} for this guild. " + + "As a result, the corresponding config has been reset. \n\n" + + "*Note:* this channel has been used to send this message because it's the first channel " + + "in the guild Lily could use. Please inform this guild's staff about this message." + ) } - getFirstUsableChannel(guild)?.createMessage( - "Lily is unable to send messages in the configured " + - "${channelType.toString().lowercase()} for this guild. " + - "As a result, the corresponding config has been reset. \n\n" + - "*Note:* this channel has been used to send this message because it's the first channel " + - "in the guild Lily could use. Please inform this guild's staff about this message." - ) return null } @@ -392,6 +401,22 @@ suspend inline fun getFirstUsableChannel(inputGuild: GuildBehavior): GuildMessag it.botHasPermissions(Permission.ViewChannel, Permission.SendMessages) }.asChannelOfOrNull() +/** + * Gets a guild's system channel as designated by Discord, or null if said channel is invalid or doesn't exist. + * + * @param inputGuild The guild in which to get the channel. + * @return The guild's system channel or null if it's invalid + * @author tempest15 + * @since 4.1.0 + */ +suspend inline fun getSystemChannelWithPerms(inputGuild: Guild): GuildMessageChannel? { + val systemChannel = inputGuild.getSystemChannel() ?: return null + if (!systemChannel.botHasPermissions(Permission.ViewChannel) || + !systemChannel.botHasPermissions(Permission.SendMessages) + ) return null + return systemChannel +} + /** * Gets the channel of the event and checks that the bot has the required [permissions]. * From 4c4d244b619a0225d130053d8089f9eed8704148 Mon Sep 17 00:00:00 2001 From: tempest15 Date: Thu, 29 Sep 2022 08:13:06 -0400 Subject: [PATCH 25/25] bump version to 4.1.0 --- build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index 65327001..b51ade74 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -12,7 +12,7 @@ plugins { } group = "org.hyacinthbots.lilybot" -version = "4.0.1" +version = "4.1.0" repositories { mavenCentral()