diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/database/Cleanups.kt b/src/main/kotlin/org/hyacinthbots/lilybot/database/Cleanups.kt index 7d5671ac..a004e1bc 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/database/Cleanups.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/database/Cleanups.kt @@ -10,6 +10,7 @@ import mu.KotlinLogging import org.hyacinthbots.lilybot.database.Cleanups.cleanupGuildData import org.hyacinthbots.lilybot.database.Cleanups.cleanupThreadData import org.hyacinthbots.lilybot.database.collections.GithubCollection +import org.hyacinthbots.lilybot.database.collections.LockedChannelCollection import org.hyacinthbots.lilybot.database.collections.LoggingConfigCollection import org.hyacinthbots.lilybot.database.collections.ModerationConfigCollection import org.hyacinthbots.lilybot.database.collections.NewsChannelPublishingCollection @@ -56,14 +57,16 @@ object Cleanups : KordExKoinComponent { cleanupsLogger.info("Starting guild cleanup...") val leaveTimeData = guildLeaveTimeCollection.find().toList() var deletedGuildData = 0 + val now = Clock.System.now() leaveTimeData.forEach { // Calculate the time since Lily left the guild. - val leaveDuration = Clock.System.now() - it.guildLeaveTime + val leaveDuration = now - it.guildLeaveTime if (leaveDuration.inWholeDays > 30) { // If the bot has been out of the guild for more than 30 days, delete any related data. GithubCollection().removeDefaultRepo(it.guildId) + LockedChannelCollection().removeAllLockedChannels(it.guildId) LoggingConfigCollection().clearConfig(it.guildId) ModerationConfigCollection().clearConfig(it.guildId) NewsChannelPublishingCollection().clearAutoPublishingForGuild(it.guildId) diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/LockedChannelCollection.kt b/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/LockedChannelCollection.kt new file mode 100644 index 00000000..2bffc04f --- /dev/null +++ b/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/LockedChannelCollection.kt @@ -0,0 +1,71 @@ +package org.hyacinthbots.lilybot.database.collections + +import com.kotlindiscord.kord.extensions.koin.KordExKoinComponent +import dev.kord.common.entity.Snowflake +import org.hyacinthbots.lilybot.database.Database +import org.hyacinthbots.lilybot.database.entities.LockedChannelData +import org.koin.core.component.inject +import org.litote.kmongo.eq + +/** + * This class contains the function for interacting with the [Locked Channel Database][LockedChannelData]. This class + * contains functions for getting, setting and removing locked channels + * + * @since 5.0.0 + * @see addLockedChannel + * @see removeLockedChannel + * @see removeAllLockedChannels + * @see getLockedChannel + */ +class LockedChannelCollection : KordExKoinComponent { + private val db: Database by inject() + + @PublishedApi + internal val collection = db.mainDatabase.getCollection() + + /** + * Adds the data about a newly locked channel to the database. + * + * @param data The [LockedChannelData] for the locked channel + * + * @author NoComment1105 + * @since 5.0.0 + */ + suspend inline fun addLockedChannel(data: LockedChannelData) = collection.insertOne(data) + + /** + * Removes a locked channel from the database. This is usually called when a channel is unlocked. + * + * @param inputGuildId The ID of the guild the locked channel is in + * @param inputChannelId The ID of the locked channel itself + * + * @author NoComment1105 + * @since 5.0.0 + */ + suspend inline fun removeLockedChannel(inputGuildId: Snowflake, inputChannelId: Snowflake) = + collection.deleteOne(LockedChannelData::guildId eq inputGuildId, LockedChannelData::channelId eq inputChannelId) + + /** + * Removes all locked channels for a given guild from the database. Used in guild cleanups. + * + * @param inputGuildId The ID of the guild to remove the locked channels from + * + * @author NoComment1105 + * @since 5.0.0 + */ + suspend inline fun removeAllLockedChannels(inputGuildId: Snowflake) = + collection.deleteMany(LockedChannelData::guildId eq inputGuildId) + + /** + * Gets a locked channel based on the input parameters. + * + * @param inputGuildId The ID of the guild the locked channel is in + * @param inputChannelId The ID of the channel to get the locked data for + * @return A [LockedChannelData] object for the given channel + * + * @author NoComment1105 + * @since 5.0.0 + */ + suspend inline fun getLockedChannel(inputGuildId: Snowflake, inputChannelId: Snowflake): LockedChannelData? = + collection.findOne(LockedChannelData::guildId eq inputGuildId, LockedChannelData::channelId eq inputChannelId) +} diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/database/entities/LockedChannelData.kt b/src/main/kotlin/org/hyacinthbots/lilybot/database/entities/LockedChannelData.kt new file mode 100644 index 00000000..a739485a --- /dev/null +++ b/src/main/kotlin/org/hyacinthbots/lilybot/database/entities/LockedChannelData.kt @@ -0,0 +1,21 @@ +package org.hyacinthbots.lilybot.database.entities + +import dev.kord.common.entity.Snowflake +import kotlinx.serialization.Serializable + +/** + * The data for locked channels. + * + * @property guildId The ID of the guild the locked channel is in + * @property channelId The ID of the channel that is locked + * @property allowed The Discord Bit Set code for the allowed permissions, formatted as a string + * @property denied The Discord Bit Set code for the denied permissions, formatted as a string + * @since 5.0.0 + */ +@Serializable +data class LockedChannelData( + val guildId: Snowflake, + val channelId: Snowflake, + val allowed: String, + val denied: String, +) diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/main/mainV10.kt b/src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/main/mainV10.kt index cf2f47c6..05722cdd 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/main/mainV10.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/database/migrations/main/mainV10.kt @@ -9,5 +9,6 @@ suspend fun mainV10(db: CoroutineDatabase) { with(db.getCollection()) { updateMany(AutoThreadingData::extraRoleIds exists false, setValue(AutoThreadingData::extraRoleIds, emptyList())) } + db.createCollection("lockedChannelData") db.createCollection("temporaryBanData") } diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/LockingCommands.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/LockingCommands.kt index abd3c9d8..6e5e8dc8 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/LockingCommands.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/LockingCommands.kt @@ -11,6 +11,7 @@ import com.kotlindiscord.kord.extensions.commands.converters.impl.optionalChanne import com.kotlindiscord.kord.extensions.extensions.Extension import com.kotlindiscord.kord.extensions.extensions.ephemeralSlashCommand import com.kotlindiscord.kord.extensions.types.EphemeralInteractionContext +import dev.kord.common.DiscordBitSet import dev.kord.common.entity.Permission import dev.kord.common.entity.Permissions import dev.kord.core.behavior.channel.asChannelOfOrNull @@ -22,6 +23,8 @@ import dev.kord.core.entity.channel.TextChannel import dev.kord.core.entity.channel.ThreadParentChannel import dev.kord.core.entity.channel.thread.TextChannelThread import kotlinx.datetime.Clock +import org.hyacinthbots.lilybot.database.collections.LockedChannelCollection +import org.hyacinthbots.lilybot.database.entities.LockedChannelData import org.hyacinthbots.lilybot.extensions.config.ConfigOptions import org.hyacinthbots.lilybot.utils.botHasChannelPerms import org.hyacinthbots.lilybot.utils.getLoggingChannelWithPerms @@ -49,15 +52,9 @@ class LockingCommands : Extension() { check { anyGuild() - requiredConfigs( - ConfigOptions.MODERATION_ENABLED - ) + requiredConfigs(ConfigOptions.MODERATION_ENABLED) hasPermission(Permission.ModerateMembers) - requireBotPermissions( - Permission.ManageChannels, - Permission.ManageRoles, - Permission.SendMessages - ) + requireBotPermissions(Permission.ManageChannels, Permission.ManageRoles) botHasChannelPerms(Permissions(Permission.ManageChannels)) } @@ -66,12 +63,28 @@ class LockingCommands : Extension() { val channelParent = getChannelParent(channelArg) val targetChannel = getTargetChannel(channelParent, channelArg) - val channelPerms = targetChannel!!.getPermissionOverwritesForRole(guild!!.id) - if (channelPerms != null && channelPerms.denied.contains(Permission.SendMessages)) { - respond { content = "This channel is already locked!" } + val currentChannelPerms = targetChannel?.getPermissionOverwritesForRole(guild!!.id) + if (currentChannelPerms == null) { + respond { + content = "There was an error getting the permissions for this channel. Please try again." + } return@action } + if (LockedChannelCollection().getLockedChannel(guild!!.id, targetChannel.id) != null) { + respond { content = "This channel is already locked" } + return@action + } + + LockedChannelCollection().addLockedChannel( + LockedChannelData( + guildId = guild!!.id, + channelId = targetChannel.id, + allowed = currentChannelPerms.data.allowed.code.value, + denied = currentChannelPerms.data.denied.code.value + ) + ) + val everyoneRole = guild!!.getRoleOrNull(guild!!.id) if (everyoneRole == null) { respond { content = "I was unable to get the `@everyone` role. Please try again." } @@ -119,15 +132,9 @@ class LockingCommands : Extension() { check { anyGuild() - requiredConfigs( - ConfigOptions.MODERATION_ENABLED - ) + requiredConfigs(ConfigOptions.MODERATION_ENABLED) hasPermission(Permission.ModerateMembers) - requireBotPermissions( - Permission.ManageChannels, - Permission.ManageRoles, - Permission.SendMessages - ) + requireBotPermissions(Permission.ManageChannels, Permission.ManageRoles, Permission.SendMessages) } action { @@ -187,11 +194,7 @@ class LockingCommands : Extension() { anyGuild() requiredConfigs(ConfigOptions.MODERATION_ENABLED) hasPermission(Permission.ModerateMembers) - requireBotPermissions( - Permission.ManageChannels, - Permission.ManageRoles, - Permission.SendMessages - ) + requireBotPermissions(Permission.ManageChannels, Permission.ManageRoles) botHasChannelPerms(Permissions(Permission.ManageChannels)) } @@ -209,30 +212,31 @@ class LockingCommands : Extension() { return@action } - val channelPerms = targetChannel!!.getPermissionOverwritesForRole(guild!!.id) + val channelPerms = targetChannel?.getPermissionOverwritesForRole(guild!!.id) if (channelPerms == null) { respond { content = "This channel is not locked!" } return@action } - if (!channelPerms.denied.contains(Permission.SendMessages)) { + val lockedChannel = LockedChannelCollection().getLockedChannel(guild!!.id, targetChannel.id) + if (lockedChannel == null) { respond { content = "This channel is not locked!" } return@action } targetChannel.editRolePermission(guild!!.id) { - denied -= Permission.SendMessages - denied -= Permission.SendMessagesInThreads - denied -= Permission.AddReactions - denied -= Permission.UseApplicationCommands + denied = Permissions.Builder(DiscordBitSet(lockedChannel.denied)).build() + allowed = Permissions.Builder(DiscordBitSet(lockedChannel.allowed)).build() } targetChannel.createEmbed { title = "Channel Unlocked" description = "This channel has been unlocked by a moderator.\n" + - "Please be aware of the rules when continuing discussion." + "Please be aware of the rules when continuing discussion." color = DISCORD_GREEN } + LockedChannelCollection().removeLockedChannel(guild!!.id, targetChannel.id) + respond { content = "${targetChannel.mention} has been unlocked." } val actionLog = @@ -258,15 +262,9 @@ class LockingCommands : Extension() { check { anyGuild() - requiredConfigs( - ConfigOptions.MODERATION_ENABLED - ) + requiredConfigs(ConfigOptions.MODERATION_ENABLED) hasPermission(Permission.ModerateMembers) - requireBotPermissions( - Permission.ManageChannels, - Permission.ManageRoles, - Permission.SendMessages - ) + requireBotPermissions(Permission.ManageChannels, Permission.ManageRoles, Permission.SendMessages) } action { @@ -334,9 +332,9 @@ class LockingCommands : Extension() { * @since 4.8.0 */ private suspend inline fun EphemeralInteractionContext.getTargetChannel( - channelParent: TextChannel?, - channelArg: Channel? - ): TextChannel? { + channelParent: TextChannel?, + channelArg: Channel? + ): TextChannel? { val targetChannel = channelParent ?: channelArg?.asChannelOfOrNull() if (targetChannel == null) { respond {