diff --git a/docs/commands.md b/docs/commands.md index 69fbe5b4..24924bab 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -345,22 +345,32 @@ None * `user` - Person to ban - User * `delete-message-days` - The number of days worth of messages to delete - Int * `reason` - The reason for the ban - Defaulting String + * `soft-ban` - Weather to soft-ban this user (unban them once messages are deleted) - Defaulting Boolean * `dm` - Whether to send a direct message to the user about the ban - Defaulting Boolean * `image` - An image you'd like to provide as extra context for the action - Optional Attachment --- -### Command name: `soft-ban` -**Description**: Soft-bans a user. +### Command name: `temp-ban add` +**Description**: Temporarily bans a user **Required Member Permissions**: Ban Members -* Arguments: - * `user` - Person to Soft ban - User - * `delete-message-days` - The number of days worth of messages to delete - Optional Int/Long +* **Arguments**: + * `user` - Person to ban - User + * `delete-message-days` - The number of days worth of messages to delete - Int + * `duration` - The duration of the temporary ban. - Coalescing Duration * `reason` - The reason for the ban - Defaulting String - * `dm` - Whether to send a direct message to the user about the soft-ban - Defaulting Boolean + * `dm` - Whether to send a direct message to the user about the ban - Defaulting Boolean * `image` - An image you'd like to provide as extra context for the action - Optional Attachment +--- +### Command name: `temp-ban view-all` +**Description**: View all temporary bans for this guild + +**Required Member Permissions**: Ban Members + +* **Arguments**: +None --- ### Command name: `unban` **Description**: Unbans a user. diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 39eb3086..3a7bce39 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -2,16 +2,16 @@ # Plugins kotlin = "1.9.23" shadow = "8.1.1" -detekt = "1.23.5" +detekt = "1.23.6" git-hooks = "0.0.2" grgit = "5.2.2" blossom = "2.1.0" # Libraries kord-extensions = "1.8.0-20240319.115836-21" -logging = "6.0.3" +logging = "6.0.4" logback = "1.5.3" -github-api = "1.319" +github-api = "1.321" kmongo = "4.11.0" docgenerator = "0.1.5-SNAPSHOT" diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index d64cd491..e6441136 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index a80b22ce..b82aa23a 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/ReminderCollection.kt b/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/ReminderCollection.kt index 90e719d2..006a1263 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/ReminderCollection.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/ReminderCollection.kt @@ -11,7 +11,7 @@ import org.koin.core.component.inject import org.litote.kmongo.eq /** - * This class contains the functions for interacting with []the reminder database][ReminderData]. This + * This class contains the functions for interacting with [the reminder database][ReminderData]. This * class contains functions for setting reminders, getting reminders based of various parameters, removing reminders and * repeating them. * diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/TemporaryBanCollection.kt b/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/TemporaryBanCollection.kt new file mode 100644 index 00000000..7413f561 --- /dev/null +++ b/src/main/kotlin/org/hyacinthbots/lilybot/database/collections/TemporaryBanCollection.kt @@ -0,0 +1,79 @@ +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.TemporaryBanData +import org.koin.core.component.inject +import org.litote.kmongo.eq + +/** + * This class contains the functions for interacting with [the temporary ban database][TemporaryBanData]. This class + * contains functions for settings temporary bans, getting temporary bans based off of various parameters and removing + * them. + * + * @since 5.0.0 + * @see getAllTempBans + * @see getTempBansForGuild + * @see getUserTempBan + * @see setTempBan + * @see removeTempBan + */ +class TemporaryBanCollection : KordExKoinComponent { + private val db: Database by inject() + + @PublishedApi + internal val collection = db.mainDatabase.getCollection() + + /** + * Gets all the temporary bans currently in the database. + * + * @return A list of temporary bans in the database + * @author NoComment1105 + * @since 5.0.0 + */ + suspend inline fun getAllTempBans(): List = collection.find().toList() + + /** + * Gets all the temporary bans for a given guild. + * + * @param guildId The ID of the guild to get the bans in + * @return A list of Temporary bans for the given [guildId] + * @author NoComment1105 + * @since 5.0.0 + */ + suspend inline fun getTempBansForGuild(guildId: Snowflake): List = + collection.find(TemporaryBanData::guildId eq guildId).toList() + + /** + * Gets a temporary ban for a given user. + * + * @param guildId The ID of the guild the temporary ban occurred in + * @param bannedUserId The ID of the user that was temporarily banned + * @return The [TemporaryBanData] for the [bannedUserId] + * @author NoComment1105 + * @since 5.0.0 + */ + suspend inline fun getUserTempBan(guildId: Snowflake, bannedUserId: Snowflake): TemporaryBanData? = + collection.findOne(TemporaryBanData::guildId eq guildId, TemporaryBanData::bannedUserId eq bannedUserId) + + /** + * Sets a temporary ban. + * + * @param tempBanData The data for the temporary ban + * @author NoComment1105 + * @since 5.0.0 + */ + suspend inline fun setTempBan(tempBanData: TemporaryBanData) = collection.insertOne(tempBanData) + + /** + * Removes the temporary ban for a user in a given guild. This is called once a temporary ban is completed. + * + * @param guildId The guild the temporary ban is being removed from + * @param bannedUserId The ID of the user to remove the temporary ban from + * @author NoComment1105 + * @since 5.0.0 + */ + suspend inline fun removeTempBan(guildId: Snowflake, bannedUserId: Snowflake) = + collection.deleteOne(TemporaryBanData::guildId eq guildId, TemporaryBanData::bannedUserId eq bannedUserId) +} diff --git a/src/main/kotlin/org/hyacinthbots/lilybot/database/entities/TemporaryBanData.kt b/src/main/kotlin/org/hyacinthbots/lilybot/database/entities/TemporaryBanData.kt new file mode 100644 index 00000000..e955ca9f --- /dev/null +++ b/src/main/kotlin/org/hyacinthbots/lilybot/database/entities/TemporaryBanData.kt @@ -0,0 +1,25 @@ +package org.hyacinthbots.lilybot.database.entities + +import dev.kord.common.entity.Snowflake +import kotlinx.datetime.Instant +import kotlinx.serialization.Serializable + +/** + * The data for temporary bans in a guild. + * + * @property guildId The ID of the guild the ban occurred in + * @property bannedUserId The ID of the user that was banned + * @property moderatorUserId The ID of the moderator that applied the ban + * @property startTime The time the ban was applied + * @property endTime The time the ban will complete + * + * @since 5.0.0 + */ +@Serializable +data class TemporaryBanData( + val guildId: Snowflake, + val bannedUserId: Snowflake, + val moderatorUserId: Snowflake, + val startTime: Instant, + val endTime: Instant +) 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 d363965b..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 @@ -10,4 +10,5 @@ suspend fun mainV10(db: CoroutineDatabase) { 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/ModerationCommands.kt b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/ModerationCommands.kt index 7e89626a..2a4235c1 100644 --- a/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/ModerationCommands.kt +++ b/src/main/kotlin/org/hyacinthbots/lilybot/extensions/moderation/ModerationCommands.kt @@ -6,12 +6,13 @@ import com.kotlindiscord.kord.extensions.DISCORD_RED import com.kotlindiscord.kord.extensions.annotations.DoNotChain import com.kotlindiscord.kord.extensions.checks.hasPermissions import com.kotlindiscord.kord.extensions.commands.Arguments +import com.kotlindiscord.kord.extensions.commands.application.slash.ephemeralSubCommand +import com.kotlindiscord.kord.extensions.commands.converters.impl.coalescingDuration import com.kotlindiscord.kord.extensions.commands.converters.impl.coalescingOptionalDuration 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.optionalInt import com.kotlindiscord.kord.extensions.commands.converters.impl.user import com.kotlindiscord.kord.extensions.components.components import com.kotlindiscord.kord.extensions.components.ephemeralStringSelectMenu @@ -19,11 +20,16 @@ import com.kotlindiscord.kord.extensions.extensions.Extension import com.kotlindiscord.kord.extensions.extensions.ephemeralMessageCommand import com.kotlindiscord.kord.extensions.extensions.ephemeralSlashCommand import com.kotlindiscord.kord.extensions.modules.extra.pluralkit.api.PluralKit +import com.kotlindiscord.kord.extensions.pagination.EphemeralResponsePaginator +import com.kotlindiscord.kord.extensions.pagination.pages.Page +import com.kotlindiscord.kord.extensions.pagination.pages.Pages import com.kotlindiscord.kord.extensions.time.TimestampType import com.kotlindiscord.kord.extensions.time.toDiscord import com.kotlindiscord.kord.extensions.utils.dm import com.kotlindiscord.kord.extensions.utils.isNullOrBot import com.kotlindiscord.kord.extensions.utils.kordExUserAgent +import com.kotlindiscord.kord.extensions.utils.scheduling.Scheduler +import com.kotlindiscord.kord.extensions.utils.scheduling.Task import com.kotlindiscord.kord.extensions.utils.timeout import com.kotlindiscord.kord.extensions.utils.timeoutUntil import com.kotlindiscord.kord.extensions.utils.toDuration @@ -34,10 +40,13 @@ 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.behavior.getChannelOfOrNull import dev.kord.core.behavior.interaction.followup.edit import dev.kord.core.behavior.reply +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.interaction.followup.EphemeralFollowupMessage import dev.kord.rest.builder.message.EmbedBuilder import dev.kord.rest.builder.message.embed @@ -47,7 +56,9 @@ import kotlinx.datetime.DateTimePeriod import kotlinx.datetime.TimeZone import kotlinx.datetime.plus import org.hyacinthbots.lilybot.database.collections.ModerationConfigCollection +import org.hyacinthbots.lilybot.database.collections.TemporaryBanCollection import org.hyacinthbots.lilybot.database.collections.WarnCollection +import org.hyacinthbots.lilybot.database.entities.TemporaryBanData import org.hyacinthbots.lilybot.extensions.config.ConfigOptions import org.hyacinthbots.lilybot.utils.HYACINTH_GITHUB import org.hyacinthbots.lilybot.utils.baseModerationEmbed @@ -57,6 +68,7 @@ import org.hyacinthbots.lilybot.utils.interval import org.hyacinthbots.lilybot.utils.isBotOrModerator import org.hyacinthbots.lilybot.utils.modCommandChecks import kotlin.time.Duration +import kotlin.time.toDuration class ModerationCommands : Extension() { override val name = "moderation" @@ -65,8 +77,15 @@ class ModerationCommands : Extension() { "For more information about the warn system, please see [this document]" + "($HYACINTH_GITHUB/LilyBot/blob/main/docs/commands.md#name-warn)" + /** The scheduler that will track the time for un-banning in temp bans. */ + private val tempBanScheduler = Scheduler() + + /** The task that will run the [tempBanScheduler]. */ + private lateinit var tempBanTask: Task + @OptIn(DoNotChain::class) override suspend fun setup() { + tempBanTask = tempBanScheduler.schedule(120, repeat = true, callback = ::removeTempBans) ephemeralMessageCommand { name = "Moderate" locking = true @@ -486,21 +505,31 @@ class ModerationCommands : Extension() { } else { arguments.reason } + }\n${if (arguments.softBan) { + "You were soft-banned. You are free to rejoin without the need to be unbanned" + } else { + "" + } }" } } } getLoggingChannelWithPerms(ConfigOptions.ACTION_LOG, guild!!)?.createMessage { embed { - title = "Banned a User" - description = "${arguments.userArgument.mention} has been banned!" + if (arguments.softBan) { + title = "Soft-Banned a User" + description = "${arguments.userArgument.mention} has been soft-banned!" + } else { + title = "Banned a User" + description = "${arguments.userArgument.mention} has been banned!" + } baseModerationEmbed(arguments.reason, arguments.userArgument, user) image = arguments.image?.url dmNotificationStatusEmbedField(dmStatus, arguments.dm) timestamp = Clock.System.now() field { name = "Days of messages deleted:" - value = arguments.messages.toString() + value = if (arguments.softBan && arguments.messages == 0) "3" else arguments.messages.toString() inline = false } } @@ -508,89 +537,157 @@ class ModerationCommands : Extension() { if (modConfig?.publicLogging == true) { event.interaction.channel.createEmbed { - title = "Banned a user" - description = "${arguments.userArgument.mention} has been banned!" + if (arguments.softBan) { + title = "Soft-Banned a user" + description = "${arguments.userArgument.mention} has been soft-banned!" + } else { + title = "Banned a user" + description = "${arguments.userArgument.mention} has been banned!" + } color = DISCORD_BLACK } } guild?.ban(arguments.userArgument.id) { - reason = arguments.reason - deleteMessageDuration = DateTimePeriod(days = arguments.messages).toDuration(TimeZone.UTC) + reason = arguments.reason + if (arguments.softBan) " **SOFT-BAN**" else "" + deleteMessageDuration = if (arguments.softBan && arguments.messages == 0) { + DateTimePeriod(days = 3).toDuration(TimeZone.UTC) + } else { + DateTimePeriod(days = arguments.messages).toDuration(TimeZone.UTC) + } } + if (arguments.softBan) guild?.unban(arguments.userArgument.id, "User was soft-banned") + respond { - content = "Banned ${arguments.userArgument.mention}" + content = if (arguments.softBan) { "Soft-banned " } else { "Banned " } + arguments.userArgument.mention } } } - ephemeralSlashCommand(::SoftBanArgs) { - name = "soft-ban" - description = "Soft-bans a user." - - requirePermission(Permission.BanMembers) - - check { - modCommandChecks(Permission.BanMembers) - requireBotPermissions(Permission.BanMembers) - } + ephemeralSlashCommand { + name = "temp-ban" + description = "The parent command for temporary ban commands" - action { - isBotOrModerator(event.kord, arguments.userArgument, guild, "soft-ban") ?: return@action + ephemeralSubCommand(::TempBanArgs) { + name = "add" + description = "Temporarily bans a user" - // The discord limit for deleting days of messages in a ban is 7, so we should catch invalid inputs. - if (arguments.messages != null && (arguments.messages!! > 7 || arguments.messages!! < 0)) { - respond { - content = "Invalid `delete-message-days` parameter! This number must be between 0 and 7 days!" - } - return@action + requirePermission(Permission.BanMembers) + check { + modCommandChecks(Permission.BanMembers) + requireBotPermissions(Permission.BanMembers) } - val modConfig = ModerationConfigCollection().getConfig(guild!!.id) - var dmStatus: Message? = null - if (arguments.dm) { - dmStatus = arguments.userArgument.dm { + action { + isBotOrModerator(event.kord, arguments.userArgument, guild, "temp-ban add") + val now = Clock.System.now() + val duration = now.plus(arguments.duration, TimeZone.UTC) + val modConfig = ModerationConfigCollection().getConfig(guild!!.id) + var dmStatus: Message? = null + if (arguments.dm) { + dmStatus = arguments.userArgument.dm { + embed { + title = "You have been temporarily banned from ${guild?.fetchGuild()?.name}" + description = "**Reason:**\n${arguments.reason}\n\n" + + "You are banned until $duration" + } + } + } + getLoggingChannelWithPerms(ConfigOptions.ACTION_LOG, guild!!)?.createMessage { embed { - title = "You have been soft-banned from ${guild?.fetchGuild()?.name}" - description = "**Reason:**\n${arguments.reason}\n\n" + - "You are free to rejoin without the need to be unbanned" + title = "Temporarily banned a user" + description = + "${arguments.userArgument.mention} has been temporarily banned!" + image = arguments.image?.url + baseModerationEmbed(arguments.reason, arguments.userArgument, user) + dmNotificationStatusEmbedField(dmStatus, arguments.dm) + field { + name = "Duration:" + value = + duration.toDiscord(TimestampType.Default) + " (${arguments.duration.interval()})" + } + timestamp = now } } - } - getLoggingChannelWithPerms(ConfigOptions.ACTION_LOG, guild!!)?.createMessage { - embed { - title = "Soft-Banned a User" - description = "${arguments.userArgument.mention} has been soft-banned!" - baseModerationEmbed(arguments.reason, arguments.userArgument, user) - image = arguments.image?.url - dmNotificationStatusEmbedField(dmStatus, arguments.dm) - timestamp = Clock.System.now() - field { - name = "Days of messages deleted:" - value = "${arguments.messages ?: "3"}" - inline = false + + if (modConfig?.publicLogging == true) { + event.interaction.channel.createEmbed { + title = "Temp Banned a user" + description = "${arguments.userArgument.mention} has been Temp Banned!" + color = DISCORD_BLACK } } - } - if (modConfig?.publicLogging == true) { - event.interaction.channel.createEmbed { - title = "Soft-Banned a user" - description = "${arguments.userArgument.mention} has been soft-banned!" - color = DISCORD_BLACK + TemporaryBanCollection().setTempBan( + TemporaryBanData(guild!!.id, arguments.userArgument.id, user.id, now, duration) + ) + + guild?.ban(arguments.userArgument.id) { + reason = arguments.reason + deleteMessageDuration = DateTimePeriod(days = arguments.messages).toDuration(TimeZone.UTC) + } + + respond { + content = "Temporarily banned ${arguments.userArgument.mention}" } } + } - guild?.ban(arguments.userArgument.id) { - reason = arguments.reason + " **SOFT-BAN**" - deleteMessageDuration = DateTimePeriod(days = arguments.messages ?: 3).toDuration(TimeZone.UTC) + ephemeralSubCommand { + name = "view-all" + description = "View all temporary bans for this guild" + + requirePermission(Permission.BanMembers) + + check { + modCommandChecks(Permission.BanMembers) + requireBotPermissions(Permission.BanMembers) } - guild?.unban(arguments.userArgument.id, "Soft-ban unban") + action { + val pagesObj = Pages() + val tempBans = TemporaryBanCollection().getTempBansForGuild(guild!!.id) + if (tempBans.isEmpty()) { + pagesObj.addPage( + Page { + description = "There are no temporary bans in this guild." + } + ) + } else { + tempBans.chunked(4).forEach { tempBan -> + var content = "" + tempBan.forEach { + content = """ + User: ${this@ephemeralSubCommand.kord.getUser(it.bannedUserId)?.username} + Moderator: ${guild?.getMemberOrNull(it.moderatorUserId)?.username} + Start Time: ${it.startTime.toDiscord(TimestampType.ShortDateTime)} (${ + it.startTime.toDiscord(TimestampType.RelativeTime) + }) + End Time: ${it.endTime.toDiscord(TimestampType.ShortDateTime)} (${ + it.endTime.toDiscord(TimestampType.RelativeTime) + }) + --- + """.trimIndent() + } + + pagesObj.addPage( + Page { + title = "Temporary bans for ${guild?.asGuildOrNull()?.name ?: "this guild"}" + description = content + } + ) + } + } - respond { - content = "Soft-banned ${arguments.userArgument.mention}" + val paginator = EphemeralResponsePaginator( + pages = pagesObj, + owner = event.interaction.user, + timeoutSeconds = 300, + interaction = interactionResponse + ) + + paginator.send() } } } @@ -607,22 +704,53 @@ class ModerationCommands : Extension() { } action { - getLoggingChannelWithPerms(ConfigOptions.ACTION_LOG, guild!!)?.createMessage { - embed { - title = "Unbanned a user" - description = "${arguments.userArgument.mention} has been unbanned!\n${ - arguments.userArgument.id - } (${arguments.userArgument.username})" - field { - name = "Reason:" - value = arguments.reason + val tempBan = TemporaryBanCollection().getUserTempBan(this.getGuild()!!.id, arguments.userArgument.id) + if (tempBan == null) { + guild?.unban(arguments.userArgument.id) + getLoggingChannelWithPerms(ConfigOptions.ACTION_LOG, guild!!)?.createMessage { + embed { + title = "Unbanned a user" + description = "${arguments.userArgument.mention} has been unbanned!\n${ + arguments.userArgument.id + } (${arguments.userArgument.username})" + field { + name = "Reason:" + value = arguments.reason + } + footer { + text = user.asUserOrNull()?.username ?: "Unable to get user username" + icon = user.asUserOrNull()?.avatar?.cdnUrl?.toUrl() + } + timestamp = Clock.System.now() + color = DISCORD_GREEN } - footer { - text = user.asUserOrNull()?.username ?: "Unable to get user username" - icon = user.asUserOrNull()?.avatar?.cdnUrl?.toUrl() + } + } else { + guild?.unban(arguments.userArgument.id) + TemporaryBanCollection().removeTempBan(guild!!.id, arguments.userArgument.id) + getLoggingChannelWithPerms(ConfigOptions.ACTION_LOG, guild!!)?.createMessage { + embed { + title = "Temporary Ban Removed" + description = "${arguments.userArgument.mention} has had their temporary ban removed!\n${ + arguments.userArgument.id + } (${arguments.userArgument.username})" + field { + name = "Reason:" + value = arguments.reason + } + field { + name = "Original Action taker:" + value = guild?.getMemberOrNull(tempBan.moderatorUserId)?.username + ?: this@ephemeralSlashCommand.kord.getUser(tempBan.moderatorUserId)?.username + ?: "Unable to get username" + } + footer { + text = user.asUserOrNull()?.username ?: "Unable to get user username" + icon = user.asUserOrNull()?.avatar?.cdnUrl?.toUrl() + } + timestamp = Clock.System.now() + color = DISCORD_GREEN } - timestamp = Clock.System.now() - color = DISCORD_GREEN } } respond { @@ -956,6 +1084,49 @@ class ModerationCommands : Extension() { } } + private suspend fun removeTempBans() { + val tempBans = TemporaryBanCollection().getAllTempBans() + val dueTempBans = + tempBans.filter { it.endTime.toEpochMilliseconds() - Clock.System.now().toEpochMilliseconds() <= 0 } + + for (it in dueTempBans) { + var guild: Guild? + try { + guild = kord.getGuildOrNull(it.guildId) + } catch (_: KtorRequestException) { + TemporaryBanCollection().removeTempBan(it.guildId, it.bannedUserId) + continue + } + + if (guild == null) { + TemporaryBanCollection().removeTempBan(it.guildId, it.bannedUserId) + continue + } + + guild.unban(it.bannedUserId, "Temporary Ban expired") + val modChannelId = ModerationConfigCollection().getConfig(guild.id)?.channel + if (modChannelId != null) { + val modChannel = guild.getChannelOfOrNull(modChannelId) + modChannel?.createMessage { + embed { + title = "Temporary ban Completed" + description = "${kord.getUser(it.bannedUserId)?.username} has served their temporary ban" + field { + name = "Initial Ban date:" + value = it.startTime.toDiscord(TimestampType.ShortDateTime) + } + color = DISCORD_GREEN + footer { + text = "Initial action taker: ${kord.getUser(it.moderatorUserId)?.username}" + icon = kord.getUser(it.moderatorUserId)?.avatar?.cdnUrl?.toUrl() + } + } + } + } + TemporaryBanCollection().removeTempBan(it.guildId, it.bannedUserId) + } + } + inner class BanArgs : Arguments() { /** The user to ban. */ val userArgument by user { @@ -976,6 +1147,13 @@ class ModerationCommands : Extension() { defaultValue = "No reason provided" } + /** Weather to softban this user or not. */ + val softBan by defaultingBoolean { + name = "soft-ban" + description = "Weather to soft-ban this user (unban them once messages are deleted)" + defaultValue = false + } + /** Whether to DM the user or not. */ val dm by defaultingBoolean { name = "dm" @@ -990,20 +1168,26 @@ class ModerationCommands : Extension() { } } - inner class SoftBanArgs : Arguments() { - /** The user to soft-ban. */ + inner class TempBanArgs : Arguments() { + /** The user to ban. */ val userArgument by user { name = "user" - description = "Person to Soft ban" + description = "Person to ban" } - /** The number of days worth of messages to delete, defaults to 3 days. */ - val messages by optionalInt { + /** The number of days worth of messages to delete. */ + val messages by int { name = "delete-message-days" description = "The number of days worth of messages to delete" } - /** The reason for the soft-ban. */ + /** The duration of the temporary ban. */ + val duration by coalescingDuration { + name = "duration" + description = "The duration of the temporary ban." + } + + /** The reason for the ban. */ val reason by defaultingString { name = "reason" description = "The reason for the ban" @@ -1013,11 +1197,11 @@ class ModerationCommands : Extension() { /** Whether to DM the user or not. */ val dm by defaultingBoolean { name = "dm" - description = "Whether to send a direct message to the user about the soft-ban" + description = "Whether to send a direct message to the user about the ban" defaultValue = true } - /** An image that the user wishes to provide for context to the soft-ban. */ + /** An image that the user wishes to provide for context to the ban. */ val image by optionalAttachment { name = "image" description = "An image you'd like to provide as extra context for the action"