diff --git a/src/main/java/org/cascadebot/cascadebot/data/database/DatabaseManager.java b/src/main/java/org/cascadebot/cascadebot/data/database/DatabaseManager.java index d18ba1d40..05e677b90 100644 --- a/src/main/java/org/cascadebot/cascadebot/data/database/DatabaseManager.java +++ b/src/main/java/org/cascadebot/cascadebot/data/database/DatabaseManager.java @@ -19,6 +19,7 @@ import org.bson.Document; import org.bson.codecs.configuration.CodecRegistries; import org.bson.codecs.configuration.CodecRegistry; +import org.cascadebot.cascadebot.data.managers.LockPermissionState; import org.cascadebot.cascadebot.utils.lists.WeightedList; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -42,7 +43,7 @@ public class DatabaseManager { "org.cascadebot.cascadebot.utils.lists", "org.cascadebot.cascadebot.scheduler", "org.cascadebot.shared" - ).register(WeightedList.WeightPair.class).build()) + ).register(WeightedList.WeightPair.class, LockPermissionState.class).build()) ); @Getter diff --git a/src/main/kotlin/org/cascadebot/cascadebot/commands/moderation/LockCommand.kt b/src/main/kotlin/org/cascadebot/cascadebot/commands/moderation/LockCommand.kt new file mode 100644 index 000000000..e2768c1b9 --- /dev/null +++ b/src/main/kotlin/org/cascadebot/cascadebot/commands/moderation/LockCommand.kt @@ -0,0 +1,87 @@ +package org.cascadebot.cascadebot.commands.moderation + +import net.dv8tion.jda.api.Permission +import net.dv8tion.jda.api.entities.IMentionable +import net.dv8tion.jda.api.entities.IPermissionHolder +import net.dv8tion.jda.api.entities.ISnowflake +import net.dv8tion.jda.api.entities.Member +import net.dv8tion.jda.api.entities.Role +import net.dv8tion.jda.api.entities.TextChannel +import net.dv8tion.jda.api.exceptions.PermissionException +import org.cascadebot.cascadebot.commandmeta.CommandContext +import org.cascadebot.cascadebot.commandmeta.MainCommand +import org.cascadebot.cascadebot.commandmeta.Module +import org.cascadebot.cascadebot.data.managers.LockManager +import org.cascadebot.cascadebot.permissions.CascadePermission +import org.cascadebot.cascadebot.utils.DiscordUtils +import java.lang.IllegalStateException + +class LockCommand : MainCommand() { + override fun onCommand(sender: Member, context: CommandContext) { + var channel: TextChannel = context.channel + if (context.args.isNotEmpty()) { + channel = DiscordUtils.getTextChannel(context.guild, context.getArg(0)) + ?: return context.typedMessaging.replyDanger(context.i18n("responses.cannot_find_channel_matching", context.getArg(0))) + + } + + val target: ISnowflake = if (context.args.size == 2) { + DiscordUtils.getRole(context.getArg(1), context.guild) + ?: DiscordUtils.getMember(context.guild, context.getArg(1)) + ?: return context.typedMessaging.replyDanger(context.i18n("commands.lock.invalid_argument", context.getArg(0))) + } else { + context.guild.publicRole + } + + // Member and Role are both IPermissionHolder so this should not happen + // This check is here to smart-cast target to IPermissionHolder for later code + if (target !is IPermissionHolder) error("Target must be a IPermissionHolder") + + if (LockManager.isLocked(channel, target)) { + when (target) { + is Role -> context.typedMessaging.replyWarning(context.i18n("commands.lock.already_locked_role", channel.name, target.asMention)) + is Member -> context.typedMessaging.replyWarning(context.i18n("commands.lock.already_locked_member", channel.name, target.asMention)) + } + return + } + + val success = { + val name: String = when(target) { + is Role -> target.asMention + is Member -> target.asMention + else -> error("Target must be either a Role or a Member") + } + + if (target is Member) { + context.typedMessaging.replySuccess((context.i18n("commands.lock.success_member", channel.name, target.asMention))) + } else if (target is Role) { + context.typedMessaging.replySuccess((context.i18n("commands.lock.success_role", channel.name, target.asMention))) + } + + } + + val failure = { throwable: Throwable -> + if (throwable is PermissionException) { + context.uiMessaging.sendBotDiscordPermError(throwable.permission) + } else { + context.typedMessaging.replyException("Something went wrong!", throwable) + } + } + + LockManager.lock(channel, target, success, failure) + } + + + override fun command(): String { + return "lock" + } + + override fun permission(): CascadePermission? { + return CascadePermission.of("lock", false, Permission.MANAGE_CHANNEL) + } + + override fun module(): Module { + return Module.MODERATION + } + +} \ No newline at end of file diff --git a/src/main/kotlin/org/cascadebot/cascadebot/commands/moderation/TempLockCommand.kt b/src/main/kotlin/org/cascadebot/cascadebot/commands/moderation/TempLockCommand.kt new file mode 100644 index 000000000..6fead6036 --- /dev/null +++ b/src/main/kotlin/org/cascadebot/cascadebot/commands/moderation/TempLockCommand.kt @@ -0,0 +1,113 @@ +package org.cascadebot.cascadebot.commands.moderation + +import net.dv8tion.jda.api.Permission +import net.dv8tion.jda.api.entities.IPermissionHolder +import net.dv8tion.jda.api.entities.ISnowflake +import net.dv8tion.jda.api.entities.Member +import net.dv8tion.jda.api.entities.Role +import net.dv8tion.jda.api.entities.TextChannel +import net.dv8tion.jda.api.exceptions.PermissionException +import org.cascadebot.cascadebot.commandmeta.CommandContext +import org.cascadebot.cascadebot.commandmeta.MainCommand +import org.cascadebot.cascadebot.commandmeta.Module +import org.cascadebot.cascadebot.data.managers.LockManager +import org.cascadebot.cascadebot.data.managers.Status +import org.cascadebot.cascadebot.data.managers.ScheduledActionManager +import org.cascadebot.cascadebot.permissions.CascadePermission +import org.cascadebot.cascadebot.scheduler.ActionType +import org.cascadebot.cascadebot.scheduler.ScheduledAction +import org.cascadebot.cascadebot.utils.DiscordUtils +import org.cascadebot.cascadebot.utils.FormatUtils +import org.cascadebot.cascadebot.utils.ParserUtils +import java.time.Instant +import java.time.OffsetDateTime +import java.time.temporal.ChronoUnit + +class TempLockCommand : MainCommand() { + override fun onCommand(sender: Member, context: CommandContext) { + if (context.args.isEmpty()) { + context.uiMessaging.replyUsage() + return + } + + val longDuration = ParserUtils.parseTextTime(context.getArg(0), false) + if (longDuration <= 0) { + context.typedMessaging.replyDanger(context.i18n("responses.invalid_duration")) + return + } + + var channel: TextChannel = context.channel + if (context.args.size == 2) { + val tempChannel = DiscordUtils.getTextChannel(context.guild, context.getArg(1)) + if (tempChannel != null) { + channel = tempChannel + } else { + context.typedMessaging.replyDanger(context.i18n("responses.cannot_find_channel_matching", context.getArg(1))) + return + } + } + + val target: ISnowflake = if (context.args.size == 3) { + DiscordUtils.getRole(context.getArg(1), context.guild) + ?: DiscordUtils.getMember(context.guild, context.getArg(2)) + ?: return context.typedMessaging.replyDanger(context.i18n("commands.templock.invalid_argument", context.getArg(2))) + } else { + context.guild.publicRole + } + + // Member and Role are both IPermissionHolder so this should not happen + // This check is here to smart-cast target to IPermissionHolder for later code + if (target !is IPermissionHolder) error("Target must be a IPermissionHolder") + + val unlockFutureData = ScheduledAction.LockActionData(channel.idLong, Status.NEUTRAL, 0, 0) + unlockFutureData.oldPermission = LockManager.getPerm(channel, target).target + unlockFutureData.targetRoleID = target.idLong + + val success = { + ScheduledActionManager.registerScheduledAction(ScheduledAction( + ActionType.UNLOCK, + unlockFutureData, + context.guild.idLong, + context.channel.idLong, + context.member.idLong, + Instant.now(), + longDuration + )) + + val textDuration = FormatUtils.formatTime(longDuration, context.locale, true).replace("(0[hm])".toRegex(), "") + + " (" + context.i18n("words.until") + " " + FormatUtils.formatDateTime(OffsetDateTime.now().plus(longDuration, ChronoUnit.MILLIS), context.locale) + ")" + + val message = when (target) { + is Role -> context.i18n("commands.templock.success_role", channel.name, target.asMention, textDuration) + is Member -> context.i18n("commands.templock.success_member", channel.name, target.asMention, textDuration) + else -> error("Target should be either Role or Member!") + } + + context.typedMessaging.replySuccess(message) + } + + val failure = { throwable: Throwable -> + if (throwable is PermissionException) { + context.uiMessaging.sendBotDiscordPermError(throwable.permission) + } else { + context.typedMessaging.replyException("Something went wrong!", throwable) + } + } + + LockManager.lock(channel, target, success, failure); + } + + + override fun command(): String { + return "templock" + } + + override fun permission(): CascadePermission? { + return CascadePermission.of("templock", false, Permission.MANAGE_CHANNEL) + } + + override fun module(): Module { + return Module.MODERATION + } + +} \ No newline at end of file diff --git a/src/main/kotlin/org/cascadebot/cascadebot/commands/moderation/UnlockCommand.kt b/src/main/kotlin/org/cascadebot/cascadebot/commands/moderation/UnlockCommand.kt new file mode 100644 index 000000000..19361cf4e --- /dev/null +++ b/src/main/kotlin/org/cascadebot/cascadebot/commands/moderation/UnlockCommand.kt @@ -0,0 +1,78 @@ +package org.cascadebot.cascadebot.commands.moderation + +import javassist.NotFoundException +import net.dv8tion.jda.api.Permission +import net.dv8tion.jda.api.entities.IPermissionHolder +import net.dv8tion.jda.api.entities.ISnowflake +import net.dv8tion.jda.api.entities.Member +import net.dv8tion.jda.api.entities.Role +import net.dv8tion.jda.api.entities.TextChannel +import net.dv8tion.jda.api.exceptions.PermissionException +import org.cascadebot.cascadebot.commandmeta.CommandContext +import org.cascadebot.cascadebot.commandmeta.MainCommand +import org.cascadebot.cascadebot.commandmeta.Module +import org.cascadebot.cascadebot.data.managers.LockManager +import org.cascadebot.cascadebot.permissions.CascadePermission +import org.cascadebot.cascadebot.utils.DiscordUtils + +class UnlockCommand : MainCommand() { + override fun onCommand(sender: Member, context: CommandContext) { + var channel: TextChannel = context.channel + if (context.args.isNotEmpty()) { + channel = DiscordUtils.getTextChannel(context.guild, context.getArg(0)) + ?: return context.typedMessaging.replyDanger(context.i18n("responses.cannot_find_channel_matching", context.getArg(0))) + + } + + val target: ISnowflake = if (context.args.size == 2) { + DiscordUtils.getRole(context.getArg(1), context.guild) + ?: DiscordUtils.getMember(context.guild, context.getArg(1)) + ?: return context.typedMessaging.replyDanger(context.i18n("commands.unlock.invalid_argument", context.getArg(1))) + } else { + context.guild.publicRole + } + + // Member and Role are both IPermissionHolder so this should not happen + // This check is here to smart-cast target to IPermissionHolder for later code + if (target !is IPermissionHolder) error("Target must be a IPermissionHolder") + + val success = { completed: Boolean -> + if (completed) { + when (target) { + is Role -> context.typedMessaging.replySuccess(context.i18n("commands.unlock.success_role", channel.name, target.asMention)) + is Member -> context.typedMessaging.replySuccess(context.i18n("commands.unlock.success_member", channel.name, target.asMention)) + else -> error("Target should be one of Role or Member") + } + } else { + when (target) { + is Role -> context.typedMessaging.replySuccess(context.i18n("commands.unlock.failure_role", channel.name, target.asMention)) + is Member -> context.typedMessaging.replySuccess(context.i18n("commands.unlock.failure_member", channel.name, target.asMention)) + else -> error("Target should be one of Role or Member") + } + } + } + + val failure = { throwable: Throwable -> + if (throwable is PermissionException) { + context.uiMessaging.sendBotDiscordPermError(throwable.permission) + } else { + context.typedMessaging.replyException("Something went wrong!", throwable) + } + } + + LockManager.unlock(context.guild, channel, target, success, failure) + } + + override fun command(): String { + return "unlock" + } + + override fun permission(): CascadePermission? { + return CascadePermission.of("unlock", false, Permission.MANAGE_CHANNEL) + } + + override fun module(): Module { + return Module.MODERATION + } + +} \ No newline at end of file diff --git a/src/main/kotlin/org/cascadebot/cascadebot/data/managers/LockManager.kt b/src/main/kotlin/org/cascadebot/cascadebot/data/managers/LockManager.kt new file mode 100644 index 000000000..af16f50e2 --- /dev/null +++ b/src/main/kotlin/org/cascadebot/cascadebot/data/managers/LockManager.kt @@ -0,0 +1,132 @@ +package org.cascadebot.cascadebot.data.managers + + +import net.dv8tion.jda.api.Permission +import net.dv8tion.jda.api.entities.Guild +import net.dv8tion.jda.api.entities.IPermissionHolder +import net.dv8tion.jda.api.entities.TextChannel +import net.dv8tion.jda.api.requests.restaction.PermissionOverrideAction +import java.time.OffsetDateTime +import java.util.Date +import java.util.EnumSet +import java.util.concurrent.CompletableFuture + +enum class Status { + ALLOW, + DENY, + NEUTRAL; + + fun apply(action: PermissionOverrideAction, perm: EnumSet) { + when (this) { + ALLOW -> action.grant(perm) + DENY -> action.deny(perm) + NEUTRAL -> action.clear(perm) + } + } + +} + +data class LockPermissionState( + val target: Status, + val selfMember: Status, + val createdAt: Date = Date() +) { + + private constructor() : this(Status.NEUTRAL, Status.NEUTRAL) + +} + +object LockManager { + + fun getPerm(channel: TextChannel, target: IPermissionHolder): LockPermissionState { + var perm = LockPermissionState(Status.NEUTRAL, Status.NEUTRAL) + + val targetOverride = channel.getPermissionOverride(target) + if (targetOverride != null) { + if (targetOverride.allowed.contains(Permission.MESSAGE_WRITE)) perm = perm.copy(target = Status.ALLOW) + if (targetOverride.denied.contains(Permission.MESSAGE_WRITE)) perm = perm.copy(target = Status.DENY) + } + + val selfMemberOverride = channel.getPermissionOverride(channel.guild.selfMember) + if (selfMemberOverride != null) { + if (selfMemberOverride.allowed.contains(Permission.MESSAGE_WRITE)) perm = perm.copy(selfMember = Status.ALLOW) + if (selfMemberOverride.denied.contains(Permission.MESSAGE_WRITE)) perm = perm.copy(selfMember = Status.DENY) + } + return perm + } + + private fun storePermissions(channel: TextChannel, target: IPermissionHolder) { + val lockedChannels = GuildDataManager.getGuildData(channel.guild.idLong).lockedChannels + val mutableMap = lockedChannels[channel.id] + if (mutableMap == null) { + lockedChannels[channel.id] = mutableMapOf(Pair(target.id, getPerm(channel, target))) + } else { + mutableMap[target.id] = getPerm(channel, target) + } + } + + fun lock(channel: TextChannel, target: IPermissionHolder, success: () -> Unit, failure: (Throwable) -> Unit) { + storePermissions(channel, target) + + CompletableFuture.allOf( + channel.upsertPermissionOverride(channel.guild.selfMember).grant(Permission.MESSAGE_WRITE).submit(), + channel.upsertPermissionOverride(target).deny(Permission.MESSAGE_WRITE).submit() + ).handle { _, throwable -> if (throwable == null) success() else failure(throwable) } + } + + fun isLocked(channel: TextChannel, target: IPermissionHolder): Boolean { + val lockedChannels = GuildDataManager.getGuildData(channel.guild.idLong).lockedChannels + val mutableMap = lockedChannels[channel.id] ?: return false + return mutableMap.containsKey(target.id) + } + + fun unlock(guild: Guild, channel: TextChannel, target: IPermissionHolder, success: (Boolean) -> Unit, failure: (Throwable) -> Unit) { + val previousState = GuildDataManager.getGuildData(channel.guild.idLong).lockedChannels[channel.id]?.get(target.id) + // If there is no state to restore, we can't do anything! + ?: return success(false) + val perm = EnumSet.of(Permission.MESSAGE_WRITE) + + val futures: MutableList> = mutableListOf() + + val selfPermissionAction = channel.getPermissionOverride(guild.selfMember)?.manager + if (selfPermissionAction != null) { + // Apply the self member's previous state + previousState.selfMember.apply(selfPermissionAction, perm) + // Apply the changes to the permission override + val submit = selfPermissionAction.submit() + // If the permission override has no permissions set after we have reset the perms, delete it to tidy. + submit.thenAccept { if (it.allowedRaw == 0L && it.deniedRaw == 0L) it.delete().queue() } + futures.add(submit) + } + + val targetPermissionAction = channel.getPermissionOverride(target)?.manager + if (targetPermissionAction != null) { + // Apply the target's previous state + previousState.target.apply(targetPermissionAction, perm) + // Apply thr changes to the permission override + val submit = targetPermissionAction.submit() + // Remove the locked channels entry for this permission override and delete the + // override if there are no permissions left on it to tidy. + submit.thenAccept { + GuildDataManager.getGuildData(guild.idLong).lockedChannels[channel.idLong.toString()]?.remove(target.idLong.toString()) + if (it.allowedRaw == 0L && it.deniedRaw == 0L) it.delete().queue() + } + futures.add(submit) + } + + val bothFutures = CompletableFuture.allOf(*futures.toTypedArray()) + + bothFutures.handle { _, throwable -> + if (throwable == null) { + success(true) + } else { + failure(throwable) + } + } + + if (futures.isEmpty()) { + success(false) + } + } + +} diff --git a/src/main/kotlin/org/cascadebot/cascadebot/data/objects/GuildData.kt b/src/main/kotlin/org/cascadebot/cascadebot/data/objects/GuildData.kt index e7e0d81ca..fe5d7b9ed 100644 --- a/src/main/kotlin/org/cascadebot/cascadebot/data/objects/GuildData.kt +++ b/src/main/kotlin/org/cascadebot/cascadebot/data/objects/GuildData.kt @@ -7,6 +7,7 @@ import net.dv8tion.jda.api.entities.Message import net.dv8tion.jda.api.entities.MessageChannel import org.cascadebot.cascadebot.CascadeBot import org.cascadebot.cascadebot.data.language.Locale +import org.cascadebot.cascadebot.data.managers.LockPermissionState import org.cascadebot.cascadebot.music.CascadeLavalinkPlayer import org.cascadebot.cascadebot.utils.buttons.ButtonGroup import org.cascadebot.cascadebot.utils.buttons.ButtonsCache @@ -42,6 +43,9 @@ class GuildData(@field:Id val guildId: Long) { val music = GuildSettingsMusic() //endregion + // > + var lockedChannels: MutableMap> = mutableMapOf() + //region Transient fields @Transient val buttonsCache = ButtonsCache(5) diff --git a/src/main/kotlin/org/cascadebot/cascadebot/messaging/Messaging.kt b/src/main/kotlin/org/cascadebot/cascadebot/messaging/Messaging.kt index 163488c5c..72affaee2 100644 --- a/src/main/kotlin/org/cascadebot/cascadebot/messaging/Messaging.kt +++ b/src/main/kotlin/org/cascadebot/cascadebot/messaging/Messaging.kt @@ -39,7 +39,10 @@ object Messaging { return if (embed) { channel.sendMessage(MessagingObjects.getMessageTypeEmbedBuilder(type).setDescription(message).build()).submit() } else { - channel.sendMessage(MessagingObjects.getMessageTypeMessageBuilder(type).append(message).build()).submit() + channel.sendMessage(MessagingObjects.getMessageTypeMessageBuilder(type) + .append(message) + .denyMentions(Message.MentionType.EVERYONE, Message.MentionType.HERE, Message.MentionType.ROLE).build()) + .submit() } } @@ -49,7 +52,10 @@ object Messaging { return if (embed) { channel.sendMessage(builder.setColor(type.color).build()).submit() } else { - channel.sendMessage(type.emoji + " " + FormatUtils.formatEmbed(builder.build())).submit() + channel.sendMessage(MessageBuilder() + .denyMentions(Message.MentionType.EVERYONE, Message.MentionType.HERE, Message.MentionType.ROLE) + .setContent(type.emoji + " " + FormatUtils.formatEmbed(builder.build())) + .build()).submit() } } diff --git a/src/main/kotlin/org/cascadebot/cascadebot/scheduler/ActionType.kt b/src/main/kotlin/org/cascadebot/cascadebot/scheduler/ActionType.kt index 05efdcba1..ccddacb96 100644 --- a/src/main/kotlin/org/cascadebot/cascadebot/scheduler/ActionType.kt +++ b/src/main/kotlin/org/cascadebot/cascadebot/scheduler/ActionType.kt @@ -1,7 +1,12 @@ package org.cascadebot.cascadebot.scheduler import net.dv8tion.jda.api.MessageBuilder +import net.dv8tion.jda.api.Permission import net.dv8tion.jda.api.entities.IMentionable +import net.dv8tion.jda.api.entities.IPermissionHolder +import org.cascadebot.cascadebot.data.managers.LockManager +import net.dv8tion.jda.api.entities.TextChannel +import net.dv8tion.jda.api.entities.ISnowflake import net.dv8tion.jda.api.exceptions.PermissionException import org.cascadebot.cascadebot.data.language.Language import org.cascadebot.cascadebot.data.language.Language.i18n @@ -14,9 +19,11 @@ import org.cascadebot.cascadebot.utils.toCapitalized import java.time.Duration import java.time.Instant import java.time.ZoneOffset +import java.util.* import kotlin.reflect.KClass import kotlin.reflect.full.isSubclassOf + enum class ActionType(val expectedClass: KClass<*>, val dataConsumer: (ScheduledAction) -> Unit) { REMINDER(ScheduledAction.ReminderActionData::class, ::reminderAction), UNMUTE(ScheduledAction.ModerationActionData::class, { action -> @@ -25,7 +32,8 @@ enum class ActionType(val expectedClass: KClass<*>, val dataConsumer: (Scheduled val member = guild.getMemberById(action.data.targetId) if (member != null) { guild.removeRoleFromMember(member, guild.getMutedRole()).apply { - val userName = guild.getMemberById(action.data.targetId)?.user?.asTag ?: Language.getGuildLocale(guild.idLong).i18n("words.unknown").toCapitalized() + val userName = guild.getMemberById(action.data.targetId)?.user?.asTag + ?: Language.getGuildLocale(guild.idLong).i18n("words.unknown").toCapitalized() reason(Language.getGuildLocale(guild.idLong).i18n("mod_actions.temp_mute.unmute_reason", userName)) queue(null) { action.channel?.let channelLet@{ channel -> @@ -65,14 +73,46 @@ enum class ActionType(val expectedClass: KClass<*>, val dataConsumer: (Scheduled if (action.data is ScheduledAction.SlowmodeActionData) { action.guild?.let { guild -> val targetChannel = guild.getGuildChannelById(action.data.targetId) - targetChannel?.manager?.setSlowmode(action.data.oldSlowmode)?.queue(null, { + targetChannel?.manager?.setSlowmode(action.data.oldSlowmode)?.queue(null) { if (it is PermissionException) { - action.channel?.let { channel -> Messaging.sendMessage(MessageType.DANGER, channel, i18n(action.guildId, "responses.no_discord_perm_bot", it.permission.name)) } + action.channel?.let { channel -> + Messaging.sendMessage( + MessageType.DANGER, + channel, + i18n(action.guildId, "responses.no_discord_perm_bot", it.permission.name) + ) + } } else { - action.channel?.let { channel -> Messaging.sendExceptionMessage(channel, "Couldn't unslowmode %s".format(targetChannel), it) } + action.channel?.let { channel -> + Messaging.sendExceptionMessage( + channel, + "Couldn't unslowmode %s".format(targetChannel), + it + ) + } } - }) + } } + } + }), + UNLOCK(ScheduledAction.LockActionData::class, { action -> + if (action.data is ScheduledAction.LockActionData) { + action.guild?.let { guild -> + var targetChannel = action.channel?.id?.let { guild.getGuildChannelById(it) } + if (action.data.targetChannelID != 0L) { + targetChannel = guild.getGuildChannelById(action.data.targetChannelID) + } + val target: ISnowflake = + guild.getRoleById(action.data.targetRoleID) + ?: guild.getMemberById(action.data.targetMemberID) + ?: guild.publicRole + + // TODO: Do something on failure? + LockManager.unlock(guild, targetChannel as TextChannel, target as IPermissionHolder, {}, {}) + + } + + } }); @@ -91,11 +131,11 @@ private fun reminderAction(action: ScheduledAction) { action.user?.openPrivateChannel()?.queue { channel -> channel.sendMessage(MessageBuilder() .setEmbed( - embed(MessageType.INFO) { - description = (warningText?.let { "$it\n\n" } ?: "") + - Language.i18n(action.guildId, "scheduled_actions.reminder_text") + - "\n```\n${action.data.reminder}\n```" - }.build() + embed(MessageType.INFO) { + description = (warningText?.let { "$it\n\n" } ?: "") + + Language.i18n(action.guildId, "scheduled_actions.reminder_text") + + "\n```\n${action.data.reminder}\n```" + }.build() ) .build() ).queue(null) { diff --git a/src/main/kotlin/org/cascadebot/cascadebot/scheduler/ScheduledAction.kt b/src/main/kotlin/org/cascadebot/cascadebot/scheduler/ScheduledAction.kt index 39ec5310e..408964d53 100644 --- a/src/main/kotlin/org/cascadebot/cascadebot/scheduler/ScheduledAction.kt +++ b/src/main/kotlin/org/cascadebot/cascadebot/scheduler/ScheduledAction.kt @@ -6,6 +6,7 @@ import net.dv8tion.jda.api.entities.TextChannel import net.dv8tion.jda.api.entities.User import org.bson.types.ObjectId import org.cascadebot.cascadebot.CascadeBot +import org.cascadebot.cascadebot.data.managers.Status import org.cascadebot.cascadebot.data.managers.ScheduledActionManager import java.time.Instant import java.time.temporal.ChronoUnit @@ -84,4 +85,10 @@ data class ScheduledAction( private constructor() : this(0L, 0) } + class LockActionData(var targetChannelID: Long, var oldPermission: Status, var targetMemberID: Long = 0, var targetRoleID: Long = 0) : ActionData { + // Mongo Constructor + @Suppress("unused") + private constructor() : this(0L, Status.NEUTRAL, 0L, 0L) + } + } diff --git a/src/main/resources/arguments.json b/src/main/resources/arguments.json index 229e243f8..c49efd543 100644 --- a/src/main/resources/arguments.json +++ b/src/main/resources/arguments.json @@ -333,11 +333,41 @@ "_type": "optional" } }, + "lock": { + "channel": { + "_type": "required", + "role*": { + "_aliases": ["member"], + "_type": "required" + } + } + }, + "templock*" : { + "duration" : { + "_type" : "required", + "channel": { + "_type": "required", + "role*": { + "_aliases": ["member"], + "_type": "required" + } + } + } + }, "loop*": { "mode": { "_type": "optional" } }, + "unlock": { + "channel": { + "_type": "required", + "role*": { + "_aliases": ["member"], + "_type": "required" + } + } + }, "play*": { "url": { "_type": "required", diff --git a/src/main/resources/lang/en-GB.json b/src/main/resources/lang/en-GB.json index b6b8d11ff..f57a50db6 100644 --- a/src/main/resources/lang/en-GB.json +++ b/src/main/resources/lang/en-GB.json @@ -127,16 +127,40 @@ "description": "Sets the slowmode of a channel", "time_exceeded": "Maximum time for slowmode of channels is 6 hours", "success": "Slowmode set to {0} in {1}", - "reset" : { - "command" : "reset", + "reset": { + "command": "reset", "description": "Resets the slowmode to off", - "reset_success" : "Slowmode reset in {0}" + "reset_success": "Slowmode reset in {0}" } }, - "tempslowmode":{ - "command" : "tempslowmode", - "description" : "Sets the slowmode of a channel temporarily", - "success" : "Slowmode set to {0} in {1}\nThis will be reset in {2}" + "tempslowmode": { + "command": "tempslowmode", + "description": "Sets the slowmode of a channel temporarily", + "success": "Slowmode set to {0} in {1}\nThis will be reset in {2}" + }, + "lock": { + "command": "lock", + "description": "Locks a channel for a member, role or everyone", + "success_role": "The channel `{0}` has been locked for the role {1}", + "success_member": "The channel `{0}` has been locked for the member {1}", + "already_locked_role": "The channel `{0}` is already locked for the role {1}!", + "already_locked_member": "The channel `{0}` is already locked for the member {1}!", + "invalid_argument": "Could not find a channel, role or member matching `{0}`" + }, + "templock": { + "command": "templock", + "descripton": "Locks a channel for a member, role or everyone for a given amount of time", + "success_role": "The channel `{0}` has been locked for the role {1}\nThis will be reset in {2}", + "success_member": "The channel `{0}` has been locked for the member {1}\nThis will be reset in {2}" + }, + "unlock": { + "command": "unlock", + "description": "Unlocks a channel for a member, role or everyone", + "success_role" : "The channel `{0}` has been unlocked for the role {1}", + "success_member" : "The channel `{0}` has been unlocked for the member {1}", + "failure_role": "The channel `{0}` is not locked for the role {1}", + "failure_member": "The channel `{0}` is not locked for the member {1}", + "invalid_argument": "Could not find a channel, role or member matching `{0}`" }, "userinfo": { "command": "userinfo", @@ -1671,11 +1695,13 @@ "volume": "volume", "weight": "weight", "words": "words", - "filters#type#whitelist": { - "name": "whitelist" + "guild#flag#list": { + "name": "list", + "description": "Lists all flags." }, - "filters#operator#or": { - "name": "or" + "guild#save#all": { + "name": "all", + "description": "Saves all guilds." }, "userperms#list#user#permissions": { "name": "permissions", @@ -1685,13 +1711,8 @@ "name": "groups", "description": "Lists the groups a user has." }, - "guild#save#all": { - "name": "all", - "description": "Saves all guilds." - }, - "guild#flag#list": { - "name": "list", - "description": "Lists all flags." + "color#red#green#blue": { + "description": "Gets the information of a colour from the red, green and blue values." }, "random#max": { "description": "Random number between 1 and the max you specify." @@ -1699,8 +1720,11 @@ "random#min#max": { "description": "Random number between the min and the max." }, - "color#red#green#blue": { - "description": "Gets the information of a colour from the red, green and blue values." + "filters#operator#or": { + "name": "or" + }, + "filters#type#whitelist": { + "name": "whitelist" } }, "utils": {