No longer throw exceptions for role/channel queries

This commit is contained in:
kageru 2019-11-12 14:02:31 +01:00
parent 3d813384e2
commit 50b97fdec7
Signed by: kageru
GPG Key ID: 8282A2BEA4ADA3D2
8 changed files with 66 additions and 49 deletions

View File

@ -1,7 +1,9 @@
package moe.kageru.kagebot
import arrow.core.Option
import arrow.core.*
import arrow.core.extensions.either.monad.flatMap
import arrow.core.extensions.list.foldable.find
import arrow.typeclasses.Monad
import moe.kageru.kagebot.config.Config
import moe.kageru.kagebot.extensions.*
import moe.kageru.kagebot.config.Config.server
@ -37,21 +39,17 @@ object Util {
private val channelIdRegex = Regex("\\d{18}")
private fun String.isEntityId() = channelIdRegex.matches(this)
@Throws(IllegalArgumentException::class)
fun findRole(idOrName: String): Role {
fun findRole(idOrName: String): Either<String, Role> {
return when {
idOrName.isEntityId() -> server.getRoleById(idOrName).ifNotEmpty { it }
?: throw IllegalArgumentException("Role $idOrName not found.")
else -> server.getRolesByNameIgnoreCase(idOrName).getOnlyElementOrError(idOrName)
}
idOrName.isEntityId() -> server.getRoleById(idOrName).asOption().toEither { 0 }
else -> server.rolesByName(idOrName).getOnly()
}.mapLeft { "Found $it results, expected 1" }
}
private inline fun <reified T> List<T>.getOnlyElementOrError(identifier: String): T {
val className = T::class.simpleName!!
private fun <T> ListK<T>.getOnly(): Either<Int, T> {
return when (size) {
0 -> throw IllegalArgumentException("$className $identifier not found.")
1 -> first()
else -> throw IllegalArgumentException("More than one ${className.toLowerCase()} found with name $identifier. Please specify the role ID instead")
1 -> Either.right(first())
else -> Either.left(size)
}
}
@ -71,6 +69,16 @@ object Util {
}
}
fun <T> CompletableFuture<T>.asOption(): Option<T> {
return try {
Option.just(join())
} catch (e: CompletionException) {
Option.empty()
}
}
fun <T> Either<*, T>.unwrap(): T = this.getOrElse { error("Attempted to unwrap Either.left") }
fun <T> CompletableFuture<T>.failed(): Boolean {
try {
join()
@ -87,20 +95,15 @@ object Util {
return isCompletedExceptionally
}
@Throws(IllegalArgumentException::class)
fun findChannel(idOrName: String): TextChannel {
fun findChannel(idOrName: String): Either<String, TextChannel> {
return when {
idOrName.isEntityId() -> server.getTextChannelById(idOrName).ifNotEmpty { it }
?: throw IllegalArgumentException("Channel ID $idOrName not found.")
else -> if (idOrName.startsWith('@')) {
Globals.api.getCachedUserByDiscriminatedName(idOrName.removePrefix("@")).ifNotEmpty { user ->
user.openPrivateChannel().joinOr {
throw IllegalArgumentException("Could not open private channel with user $idOrName for redirection.")
}
} ?: throw IllegalArgumentException("Can’t find user $idOrName for redirection.")
} else {
server.getTextChannelsByName(idOrName).getOnlyElementOrError(idOrName)
}
idOrName.isEntityId() -> server.getTextChannelById(idOrName).asOption().toEither { "Channel $idOrName not found" }
idOrName.startsWith('@') -> Globals.api.getCachedUserByDiscriminatedName(idOrName.removePrefix("@")).asOption()
.toEither { "User $idOrName not found" }
.flatMap { user ->
user.openPrivateChannel().asOption().toEither { "Can’t DM user $idOrName" }
}
else -> server.channelByName(idOrName).getOnly().mapLeft { "Found $it channels for $idOrName, expected 1" }
}
}

View File

@ -5,13 +5,14 @@ import moe.kageru.kagebot.MessageUtil
import moe.kageru.kagebot.Util
import moe.kageru.kagebot.Util.applyIf
import moe.kageru.kagebot.Util.failed
import moe.kageru.kagebot.Util.unwrap
import moe.kageru.kagebot.config.Config
import moe.kageru.kagebot.config.LocalizationSpec
import org.javacord.api.entity.channel.TextChannel
import org.javacord.api.event.message.MessageCreateEvent
class MessageRedirect(target: String, private val anonymous: Boolean = false) {
private val targetChannel: TextChannel = Util.findChannel(target)
private val targetChannel: TextChannel = Util.findChannel(target).unwrap()
fun execute(message: MessageCreateEvent, command: Command) {
val embed = MessageUtil.withEmbed {

View File

@ -2,6 +2,7 @@ package moe.kageru.kagebot.command
import arrow.core.Option
import moe.kageru.kagebot.Util
import moe.kageru.kagebot.Util.unwrap
import org.javacord.api.entity.permission.Role
import org.javacord.api.event.message.MessageCreateEvent
@ -10,14 +11,18 @@ class Permissions(
hasNoneOf: List<String>?,
private val onlyDM: Boolean = false
) {
private val hasOneOf: Option<Set<Role>> = Option.fromNullable(hasOneOf?.mapTo(mutableSetOf(), Util::findRole))
private val hasNoneOf: Option<Set<Role>> = Option.fromNullable(hasNoneOf?.mapTo(mutableSetOf(), Util::findRole))
private val hasOneOf: Option<Set<Role>> = resolveRoles(hasOneOf)
private val hasNoneOf: Option<Set<Role>> = resolveRoles(hasNoneOf)
private fun resolveRoles(hasOneOf: List<String>?): Option<Set<Role>> {
return Option.fromNullable(hasOneOf?.mapTo(mutableSetOf(), { Util.findRole(it).unwrap() }))
}
fun isAllowed(message: MessageCreateEvent): Boolean = when {
message.messageAuthor.isBotOwner -> true
onlyDM && !message.isPrivateMessage -> false
// returns true if the Option is empty (case for no restrictions)
else -> hasOneOf.forall { Util.hasOneOf(message.messageAuthor, it) }
&& hasNoneOf.forall { !Util.hasOneOf(message.messageAuthor, it) }
&& hasNoneOf.forall { !Util.hasOneOf(message.messageAuthor, it) }
}
}

View File

@ -4,10 +4,11 @@ import com.fasterxml.jackson.annotation.JsonProperty
import moe.kageru.kagebot.Log
import moe.kageru.kagebot.Util
import moe.kageru.kagebot.Util.getUser
import moe.kageru.kagebot.Util.unwrap
import org.javacord.api.event.message.MessageCreateEvent
class RoleAssignment(@JsonProperty("role") role: String) {
private val role = Util.findRole(role)
private val role = Util.findRole(role).unwrap()
fun assign(message: MessageCreateEvent) =
message.getUser()?.addRole(role, "Requested via command.")

View File

@ -1,11 +1,15 @@
package moe.kageru.kagebot.features
import arrow.core.ListK
import arrow.core.extensions.list.monad.map
import arrow.core.k
import com.fasterxml.jackson.annotation.JsonProperty
import moe.kageru.kagebot.Log
import moe.kageru.kagebot.MessageUtil.sendEmbed
import moe.kageru.kagebot.Util.asOption
import moe.kageru.kagebot.Util.findRole
import moe.kageru.kagebot.Util.findUser
import moe.kageru.kagebot.Util.ifNotEmpty
import moe.kageru.kagebot.Util.unwrap
import moe.kageru.kagebot.config.Config
import moe.kageru.kagebot.config.LocalizationSpec
import moe.kageru.kagebot.persistence.Dao
@ -15,7 +19,7 @@ import java.time.Duration
import java.time.Instant
class TimeoutFeature(@JsonProperty("role") role: String) : MessageFeature {
private val timeoutRole: Role = findRole(role)
private val timeoutRole: Role = findRole(role).unwrap()
override fun handle(message: MessageCreateEvent) {
val timeout = message.readableMessageContent.split(' ', limit = 4).let { args ->
@ -57,30 +61,30 @@ class TimeoutFeature(@JsonProperty("role") role: String) : MessageFeature {
val now = Instant.now().epochSecond
Dao.getAllTimeouts()
.filter { releaseTime -> now > releaseTime }
.map {
Dao.deleteTimeout(it).let { rawIds ->
UserInTimeout.ofLongs(rawIds).toPair()
}
}.forEach { (userId, roleIds) ->
Config.server.getMemberById(userId).ifNotEmpty { user ->
roleIds.forEach { roleId ->
user.addRole(findRole("$roleId"))
.map { Dao.deleteTimeout(it) }
.map { UserInTimeout.ofLongs(it).toPair() }
.forEach { (userId, roleIds) ->
Config.server.getMemberById(userId).asOption().fold(
{ Log.warn("Tried to free user $userId, but couldn’t find them on the server anymore") }, { user ->
roleIds.forEach { roleId ->
findRole("$roleId").map(user::addRole)
}
user.removeRole(timeoutRole)
Log.info("Lifted timeout from user ${user.discriminatedName}. Stored roles ${roleIds.joinToString()}")
}
user.removeRole(timeoutRole)
Log.info("Lifted timeout from user ${user.discriminatedName}. Stored roles ${roleIds.joinToString()}")
} ?: Log.warn("Tried to free user $userId, but couldn’t find them on the server anymore")
)
}
}
}
class UserInTimeout(private val id: Long, private val roles: List<Long>) {
class UserInTimeout(private val id: Long, private val roles: ListK<Long>) {
fun toPair() = Pair(id, roles)
companion object {
fun ofLongs(longs: LongArray): UserInTimeout = longs.run {
val userId = first()
val roles = if (size > 1) slice(1 until size) else emptyList()
return UserInTimeout(userId, roles)
return UserInTimeout(userId, roles.k())
}
}
}

View File

@ -5,6 +5,7 @@ import moe.kageru.kagebot.MessageUtil
import moe.kageru.kagebot.Util
import moe.kageru.kagebot.Util.checked
import moe.kageru.kagebot.Util.failed
import moe.kageru.kagebot.Util.unwrap
import org.javacord.api.DiscordApi
import org.javacord.api.entity.channel.TextChannel
import org.javacord.api.entity.message.embed.EmbedBuilder
@ -45,10 +46,10 @@ class WelcomeFeature(
private fun hasFallback(): Boolean = fallbackChannel != null && fallbackMessage != null
private val fallbackChannel: TextChannel? = fallbackChannel?.let {
private val fallbackChannel: TextChannel? = fallbackChannel?.let { channel ->
requireNotNull(fallbackMessage) {
"[feature.welcome.fallbackMessage] must not be null if fallbackChannel is defined"
}
Util.findChannel(it)
Util.findChannel(channel).unwrap()
}
}

View File

@ -1,5 +1,6 @@
package moe.kageru.kagebot.persistence
import arrow.core.k
import org.mapdb.DBMaker
import org.mapdb.Serializer
@ -21,7 +22,7 @@ object Dao {
fun close() = db.close()
fun getAllTimeouts() = prisoners.keys
fun getAllTimeouts() = prisoners.keys.k()
fun deleteTimeout(releaseTime: Long): LongArray {
val timeout = prisoners[releaseTime]!!

View File

@ -15,6 +15,7 @@ import moe.kageru.kagebot.TestUtil.prepareTestEnvironment
import moe.kageru.kagebot.TestUtil.testMessageSuccess
import moe.kageru.kagebot.TestUtil.withCommands
import moe.kageru.kagebot.Util
import moe.kageru.kagebot.Util.unwrap
import moe.kageru.kagebot.config.Config
import moe.kageru.kagebot.persistence.Dao
import org.javacord.api.entity.message.embed.EmbedBuilder
@ -275,7 +276,7 @@ class CommandTest : StringSpec({
}
every { Config.server.getMemberById(1) } returns Optional.of(user)
mockMessage("!assign").process()
roles shouldBe mutableListOf(Util.findRole("testrole"))
roles shouldBe mutableListOf(Util.findRole("testrole").unwrap())
}
}
"should create VC" {