No longer throw exceptions for role/channel queries
This commit is contained in:
parent
3d813384e2
commit
50b97fdec7
|
@ -1,7 +1,9 @@
|
||||||
package moe.kageru.kagebot
|
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.core.extensions.list.foldable.find
|
||||||
|
import arrow.typeclasses.Monad
|
||||||
import moe.kageru.kagebot.config.Config
|
import moe.kageru.kagebot.config.Config
|
||||||
import moe.kageru.kagebot.extensions.*
|
import moe.kageru.kagebot.extensions.*
|
||||||
import moe.kageru.kagebot.config.Config.server
|
import moe.kageru.kagebot.config.Config.server
|
||||||
|
@ -37,21 +39,17 @@ object Util {
|
||||||
private val channelIdRegex = Regex("\\d{18}")
|
private val channelIdRegex = Regex("\\d{18}")
|
||||||
private fun String.isEntityId() = channelIdRegex.matches(this)
|
private fun String.isEntityId() = channelIdRegex.matches(this)
|
||||||
|
|
||||||
@Throws(IllegalArgumentException::class)
|
fun findRole(idOrName: String): Either<String, Role> {
|
||||||
fun findRole(idOrName: String): Role {
|
|
||||||
return when {
|
return when {
|
||||||
idOrName.isEntityId() -> server.getRoleById(idOrName).ifNotEmpty { it }
|
idOrName.isEntityId() -> server.getRoleById(idOrName).asOption().toEither { 0 }
|
||||||
?: throw IllegalArgumentException("Role $idOrName not found.")
|
else -> server.rolesByName(idOrName).getOnly()
|
||||||
else -> server.getRolesByNameIgnoreCase(idOrName).getOnlyElementOrError(idOrName)
|
}.mapLeft { "Found $it results, expected 1" }
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private inline fun <reified T> List<T>.getOnlyElementOrError(identifier: String): T {
|
private fun <T> ListK<T>.getOnly(): Either<Int, T> {
|
||||||
val className = T::class.simpleName!!
|
|
||||||
return when (size) {
|
return when (size) {
|
||||||
0 -> throw IllegalArgumentException("$className $identifier not found.")
|
1 -> Either.right(first())
|
||||||
1 -> first()
|
else -> Either.left(size)
|
||||||
else -> throw IllegalArgumentException("More than one ${className.toLowerCase()} found with name $identifier. Please specify the role ID instead")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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 {
|
fun <T> CompletableFuture<T>.failed(): Boolean {
|
||||||
try {
|
try {
|
||||||
join()
|
join()
|
||||||
|
@ -87,20 +95,15 @@ object Util {
|
||||||
return isCompletedExceptionally
|
return isCompletedExceptionally
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(IllegalArgumentException::class)
|
fun findChannel(idOrName: String): Either<String, TextChannel> {
|
||||||
fun findChannel(idOrName: String): TextChannel {
|
|
||||||
return when {
|
return when {
|
||||||
idOrName.isEntityId() -> server.getTextChannelById(idOrName).ifNotEmpty { it }
|
idOrName.isEntityId() -> server.getTextChannelById(idOrName).asOption().toEither { "Channel $idOrName not found" }
|
||||||
?: throw IllegalArgumentException("Channel ID $idOrName not found.")
|
idOrName.startsWith('@') -> Globals.api.getCachedUserByDiscriminatedName(idOrName.removePrefix("@")).asOption()
|
||||||
else -> if (idOrName.startsWith('@')) {
|
.toEither { "User $idOrName not found" }
|
||||||
Globals.api.getCachedUserByDiscriminatedName(idOrName.removePrefix("@")).ifNotEmpty { user ->
|
.flatMap { user ->
|
||||||
user.openPrivateChannel().joinOr {
|
user.openPrivateChannel().asOption().toEither { "Can’t DM user $idOrName" }
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
else -> server.channelByName(idOrName).getOnly().mapLeft { "Found $it channels for $idOrName, expected 1" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,13 +5,14 @@ import moe.kageru.kagebot.MessageUtil
|
||||||
import moe.kageru.kagebot.Util
|
import moe.kageru.kagebot.Util
|
||||||
import moe.kageru.kagebot.Util.applyIf
|
import moe.kageru.kagebot.Util.applyIf
|
||||||
import moe.kageru.kagebot.Util.failed
|
import moe.kageru.kagebot.Util.failed
|
||||||
|
import moe.kageru.kagebot.Util.unwrap
|
||||||
import moe.kageru.kagebot.config.Config
|
import moe.kageru.kagebot.config.Config
|
||||||
import moe.kageru.kagebot.config.LocalizationSpec
|
import moe.kageru.kagebot.config.LocalizationSpec
|
||||||
import org.javacord.api.entity.channel.TextChannel
|
import org.javacord.api.entity.channel.TextChannel
|
||||||
import org.javacord.api.event.message.MessageCreateEvent
|
import org.javacord.api.event.message.MessageCreateEvent
|
||||||
|
|
||||||
class MessageRedirect(target: String, private val anonymous: Boolean = false) {
|
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) {
|
fun execute(message: MessageCreateEvent, command: Command) {
|
||||||
val embed = MessageUtil.withEmbed {
|
val embed = MessageUtil.withEmbed {
|
||||||
|
|
|
@ -2,6 +2,7 @@ package moe.kageru.kagebot.command
|
||||||
|
|
||||||
import arrow.core.Option
|
import arrow.core.Option
|
||||||
import moe.kageru.kagebot.Util
|
import moe.kageru.kagebot.Util
|
||||||
|
import moe.kageru.kagebot.Util.unwrap
|
||||||
import org.javacord.api.entity.permission.Role
|
import org.javacord.api.entity.permission.Role
|
||||||
import org.javacord.api.event.message.MessageCreateEvent
|
import org.javacord.api.event.message.MessageCreateEvent
|
||||||
|
|
||||||
|
@ -10,8 +11,12 @@ class Permissions(
|
||||||
hasNoneOf: List<String>?,
|
hasNoneOf: List<String>?,
|
||||||
private val onlyDM: Boolean = false
|
private val onlyDM: Boolean = false
|
||||||
) {
|
) {
|
||||||
private val hasOneOf: Option<Set<Role>> = Option.fromNullable(hasOneOf?.mapTo(mutableSetOf(), Util::findRole))
|
private val hasOneOf: Option<Set<Role>> = resolveRoles(hasOneOf)
|
||||||
private val hasNoneOf: Option<Set<Role>> = Option.fromNullable(hasNoneOf?.mapTo(mutableSetOf(), Util::findRole))
|
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 {
|
fun isAllowed(message: MessageCreateEvent): Boolean = when {
|
||||||
message.messageAuthor.isBotOwner -> true
|
message.messageAuthor.isBotOwner -> true
|
||||||
|
|
|
@ -4,10 +4,11 @@ import com.fasterxml.jackson.annotation.JsonProperty
|
||||||
import moe.kageru.kagebot.Log
|
import moe.kageru.kagebot.Log
|
||||||
import moe.kageru.kagebot.Util
|
import moe.kageru.kagebot.Util
|
||||||
import moe.kageru.kagebot.Util.getUser
|
import moe.kageru.kagebot.Util.getUser
|
||||||
|
import moe.kageru.kagebot.Util.unwrap
|
||||||
import org.javacord.api.event.message.MessageCreateEvent
|
import org.javacord.api.event.message.MessageCreateEvent
|
||||||
|
|
||||||
class RoleAssignment(@JsonProperty("role") role: String) {
|
class RoleAssignment(@JsonProperty("role") role: String) {
|
||||||
private val role = Util.findRole(role)
|
private val role = Util.findRole(role).unwrap()
|
||||||
|
|
||||||
fun assign(message: MessageCreateEvent) =
|
fun assign(message: MessageCreateEvent) =
|
||||||
message.getUser()?.addRole(role, "Requested via command.")
|
message.getUser()?.addRole(role, "Requested via command.")
|
||||||
|
|
|
@ -1,11 +1,15 @@
|
||||||
package moe.kageru.kagebot.features
|
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 com.fasterxml.jackson.annotation.JsonProperty
|
||||||
import moe.kageru.kagebot.Log
|
import moe.kageru.kagebot.Log
|
||||||
import moe.kageru.kagebot.MessageUtil.sendEmbed
|
import moe.kageru.kagebot.MessageUtil.sendEmbed
|
||||||
|
import moe.kageru.kagebot.Util.asOption
|
||||||
import moe.kageru.kagebot.Util.findRole
|
import moe.kageru.kagebot.Util.findRole
|
||||||
import moe.kageru.kagebot.Util.findUser
|
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.Config
|
||||||
import moe.kageru.kagebot.config.LocalizationSpec
|
import moe.kageru.kagebot.config.LocalizationSpec
|
||||||
import moe.kageru.kagebot.persistence.Dao
|
import moe.kageru.kagebot.persistence.Dao
|
||||||
|
@ -15,7 +19,7 @@ import java.time.Duration
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
|
|
||||||
class TimeoutFeature(@JsonProperty("role") role: String) : MessageFeature {
|
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) {
|
override fun handle(message: MessageCreateEvent) {
|
||||||
val timeout = message.readableMessageContent.split(' ', limit = 4).let { args ->
|
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
|
val now = Instant.now().epochSecond
|
||||||
Dao.getAllTimeouts()
|
Dao.getAllTimeouts()
|
||||||
.filter { releaseTime -> now > releaseTime }
|
.filter { releaseTime -> now > releaseTime }
|
||||||
.map {
|
.map { Dao.deleteTimeout(it) }
|
||||||
Dao.deleteTimeout(it).let { rawIds ->
|
.map { UserInTimeout.ofLongs(it).toPair() }
|
||||||
UserInTimeout.ofLongs(rawIds).toPair()
|
.forEach { (userId, roleIds) ->
|
||||||
}
|
Config.server.getMemberById(userId).asOption().fold(
|
||||||
}.forEach { (userId, roleIds) ->
|
{ Log.warn("Tried to free user $userId, but couldn’t find them on the server anymore") }, { user ->
|
||||||
Config.server.getMemberById(userId).ifNotEmpty { user ->
|
|
||||||
roleIds.forEach { roleId ->
|
roleIds.forEach { roleId ->
|
||||||
user.addRole(findRole("$roleId"))
|
findRole("$roleId").map(user::addRole)
|
||||||
}
|
}
|
||||||
user.removeRole(timeoutRole)
|
user.removeRole(timeoutRole)
|
||||||
Log.info("Lifted timeout from user ${user.discriminatedName}. Stored roles ${roleIds.joinToString()}")
|
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)
|
fun toPair() = Pair(id, roles)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun ofLongs(longs: LongArray): UserInTimeout = longs.run {
|
fun ofLongs(longs: LongArray): UserInTimeout = longs.run {
|
||||||
val userId = first()
|
val userId = first()
|
||||||
val roles = if (size > 1) slice(1 until size) else emptyList()
|
val roles = if (size > 1) slice(1 until size) else emptyList()
|
||||||
return UserInTimeout(userId, roles)
|
return UserInTimeout(userId, roles.k())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import moe.kageru.kagebot.MessageUtil
|
||||||
import moe.kageru.kagebot.Util
|
import moe.kageru.kagebot.Util
|
||||||
import moe.kageru.kagebot.Util.checked
|
import moe.kageru.kagebot.Util.checked
|
||||||
import moe.kageru.kagebot.Util.failed
|
import moe.kageru.kagebot.Util.failed
|
||||||
|
import moe.kageru.kagebot.Util.unwrap
|
||||||
import org.javacord.api.DiscordApi
|
import org.javacord.api.DiscordApi
|
||||||
import org.javacord.api.entity.channel.TextChannel
|
import org.javacord.api.entity.channel.TextChannel
|
||||||
import org.javacord.api.entity.message.embed.EmbedBuilder
|
import org.javacord.api.entity.message.embed.EmbedBuilder
|
||||||
|
@ -45,10 +46,10 @@ class WelcomeFeature(
|
||||||
|
|
||||||
private fun hasFallback(): Boolean = fallbackChannel != null && fallbackMessage != null
|
private fun hasFallback(): Boolean = fallbackChannel != null && fallbackMessage != null
|
||||||
|
|
||||||
private val fallbackChannel: TextChannel? = fallbackChannel?.let {
|
private val fallbackChannel: TextChannel? = fallbackChannel?.let { channel ->
|
||||||
requireNotNull(fallbackMessage) {
|
requireNotNull(fallbackMessage) {
|
||||||
"[feature.welcome.fallbackMessage] must not be null if fallbackChannel is defined"
|
"[feature.welcome.fallbackMessage] must not be null if fallbackChannel is defined"
|
||||||
}
|
}
|
||||||
Util.findChannel(it)
|
Util.findChannel(channel).unwrap()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package moe.kageru.kagebot.persistence
|
package moe.kageru.kagebot.persistence
|
||||||
|
|
||||||
|
import arrow.core.k
|
||||||
import org.mapdb.DBMaker
|
import org.mapdb.DBMaker
|
||||||
import org.mapdb.Serializer
|
import org.mapdb.Serializer
|
||||||
|
|
||||||
|
@ -21,7 +22,7 @@ object Dao {
|
||||||
|
|
||||||
fun close() = db.close()
|
fun close() = db.close()
|
||||||
|
|
||||||
fun getAllTimeouts() = prisoners.keys
|
fun getAllTimeouts() = prisoners.keys.k()
|
||||||
|
|
||||||
fun deleteTimeout(releaseTime: Long): LongArray {
|
fun deleteTimeout(releaseTime: Long): LongArray {
|
||||||
val timeout = prisoners[releaseTime]!!
|
val timeout = prisoners[releaseTime]!!
|
||||||
|
|
|
@ -15,6 +15,7 @@ import moe.kageru.kagebot.TestUtil.prepareTestEnvironment
|
||||||
import moe.kageru.kagebot.TestUtil.testMessageSuccess
|
import moe.kageru.kagebot.TestUtil.testMessageSuccess
|
||||||
import moe.kageru.kagebot.TestUtil.withCommands
|
import moe.kageru.kagebot.TestUtil.withCommands
|
||||||
import moe.kageru.kagebot.Util
|
import moe.kageru.kagebot.Util
|
||||||
|
import moe.kageru.kagebot.Util.unwrap
|
||||||
import moe.kageru.kagebot.config.Config
|
import moe.kageru.kagebot.config.Config
|
||||||
import moe.kageru.kagebot.persistence.Dao
|
import moe.kageru.kagebot.persistence.Dao
|
||||||
import org.javacord.api.entity.message.embed.EmbedBuilder
|
import org.javacord.api.entity.message.embed.EmbedBuilder
|
||||||
|
@ -275,7 +276,7 @@ class CommandTest : StringSpec({
|
||||||
}
|
}
|
||||||
every { Config.server.getMemberById(1) } returns Optional.of(user)
|
every { Config.server.getMemberById(1) } returns Optional.of(user)
|
||||||
mockMessage("!assign").process()
|
mockMessage("!assign").process()
|
||||||
roles shouldBe mutableListOf(Util.findRole("testrole"))
|
roles shouldBe mutableListOf(Util.findRole("testrole").unwrap())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"should create VC" {
|
"should create VC" {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user