Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix permissions being lost by locked channels when unlocked #395

Merged
merged 3 commits into from
Apr 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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)
}
Original file line number Diff line number Diff line change
@@ -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,
)
Original file line number Diff line number Diff line change
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")
}
Original file line number Diff line number Diff line change
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
Loading