Skip to content

Commit

Permalink
Fix permissions being lost by locked channels when unlocked (#395)
Browse files Browse the repository at this point in the history
* Begin work on storing original permissions when locking channels

* Complete work on storing original permission for locked channels
  • Loading branch information
NoComment1105 committed Apr 12, 2024
1 parent a61c526 commit ce9eac2
Show file tree
Hide file tree
Showing 5 changed files with 137 additions and 43 deletions.
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
@@ -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<LockedChannelData>()

/**
* 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)
}
@@ -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,
)
Expand Up @@ -9,5 +9,6 @@ suspend fun mainV10(db: CoroutineDatabase) {
with(db.getCollection<AutoThreadingData>()) {
updateMany(AutoThreadingData::extraRoleIds exists false, setValue(AutoThreadingData::extraRoleIds, emptyList()))
}
db.createCollection("lockedChannelData")
db.createCollection("temporaryBanData")
}
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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))
}

Expand All @@ -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." }
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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))
}

Expand All @@ -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 =
Expand All @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down

0 comments on commit ce9eac2

Please sign in to comment.