diff --git a/build.gradle.kts b/build.gradle.kts index 20490c9..f73e027 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -39,6 +39,7 @@ dependencies { implementation("org.javacord:javacord:3.0.4") implementation("org.mapdb:mapdb:3.0.7") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.1") + implementation("org.jetbrains.kotlin:kotlin-reflect:1.3.50") testImplementation("io.kotlintest:kotlintest-runner-junit5:3.4.1") testImplementation("io.mockk:mockk:1.9.3") diff --git a/src/main/kotlin/moe/kageru/kagebot/Kagebot.kt b/src/main/kotlin/moe/kageru/kagebot/Kagebot.kt index dae5479..7b1ced0 100644 --- a/src/main/kotlin/moe/kageru/kagebot/Kagebot.kt +++ b/src/main/kotlin/moe/kageru/kagebot/Kagebot.kt @@ -34,10 +34,10 @@ object Kagebot { } } - private fun getSecret() = File("secret").readText().trim() + private val secret by lazy { File("secret").readText().trim() } fun init() { - Globals.api = DiscordApiBuilder().setToken(getSecret()).login().join() + val api = DiscordApiBuilder().setToken(secret).login().join() try { ConfigParser.initialLoad(RawConfig.read()) } catch (e: IllegalArgumentException) { @@ -47,11 +47,12 @@ object Kagebot { Runtime.getRuntime().addShutdownHook(Thread { Log.info("Bot has been interrupted. Shutting down.") Dao.close() - Globals.api.disconnect() + api.disconnect() }) Log.info("kagebot Mk II running") - Globals.api.addMessageCreateListener { checked { it.process() } } - Config.features.eventFeatures().forEach { it.register(Globals.api) } + api.addMessageCreateListener { checked { it.process() } } + Config.features.eventFeatures().forEach { it.register(api) } CronD.startAll() + Globals.api = api } } diff --git a/src/main/kotlin/moe/kageru/kagebot/Util.kt b/src/main/kotlin/moe/kageru/kagebot/Util.kt index 7d53cf3..814d8b6 100644 --- a/src/main/kotlin/moe/kageru/kagebot/Util.kt +++ b/src/main/kotlin/moe/kageru/kagebot/Util.kt @@ -22,12 +22,8 @@ object Util { * Mimics the behavior of [Optional.ifPresent], but returns null if the optional is empty, * allowing easier fallback behavior via Kotlin’s ?: operator. */ - internal inline fun Optional.ifNotEmpty(op: (T) -> R): R? { - if (this.isPresent) { - return op(this.get()) - } - return null - } + internal inline fun Optional.ifNotEmpty(op: (T) -> R): R? = + if (this.isPresent) op(this.get()) else null fun hasOneOf(messageAuthor: MessageAuthor, roles: Set): Boolean { return messageAuthor.asUser().ifNotEmpty { user -> @@ -43,13 +39,16 @@ object Util { return when { idOrName.isEntityId() -> server.getRoleById(idOrName).ifNotEmpty { it } ?: throw IllegalArgumentException("Role $idOrName not found.") - else -> server.getRolesByNameIgnoreCase(idOrName).let { - when (it.size) { - 0 -> throw IllegalArgumentException("Role $idOrName not found.") - 1 -> it[0] - else -> throw IllegalArgumentException("More than one role found with name $idOrName. Please specify the role ID instead") - } - } + else -> server.getRolesByNameIgnoreCase(idOrName).getOnlyElementOrError(idOrName) + } + } + + private inline fun List.getOnlyElementOrError(identifier: String): T { + val className = T::class.simpleName!! + 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") } } @@ -73,14 +72,14 @@ object Util { try { join() } catch (e: CompletionException) { - Log.warn( + // we don’t care about this error, but I at least want to log it for debugging + Log.info( """Error during CompletableFuture: |$e |${e.localizedMessage} |${e.stackTrace.joinToString("\n\t")} """.trimMargin() ) - // we don’t care about this error, but I don’t want to spam stdout } return isCompletedExceptionally } @@ -92,22 +91,12 @@ object Util { ?: throw IllegalArgumentException("Channel ID $idOrName not found.") else -> if (idOrName.startsWith('@')) { Globals.api.getCachedUserByDiscriminatedName(idOrName.removePrefix("@")).ifNotEmpty { user -> - val channelFuture = user.openPrivateChannel() - val channel = channelFuture.join() - if (channelFuture.isCompletedExceptionally) { + user.openPrivateChannel().joinOr { throw IllegalArgumentException("Could not open private channel with user $idOrName for redirection.") } - channel - } - ?: throw IllegalArgumentException("Can’t find user $idOrName for redirection.") + } ?: throw IllegalArgumentException("Can’t find user $idOrName for redirection.") } else { - server.getTextChannelsByName(idOrName).let { - when (it.size) { - 0 -> throw IllegalArgumentException("Channel $idOrName not found.") - 1 -> it[0] - else -> throw IllegalArgumentException("More than one channel found with name $idOrName. Please specify the channel ID instead") - } - } + server.getTextChannelsByName(idOrName).getOnlyElementOrError(idOrName) } } } @@ -132,11 +121,7 @@ object Util { } } - fun userFromMessage(message: MessageCreateEvent): User? { - return message.messageAuthor.id.let { id -> - Config.server.getMemberById(id).orElse(null) - } - } + fun MessageCreateEvent.getUser(): User? = Config.server.getMemberById(messageAuthor.id).toNullable() /** * Convert a list of elements to pairs, retaining order. @@ -148,4 +133,12 @@ object Util { Pair(next(), next()) } } + + private inline fun CompletableFuture.joinOr(op: () -> Nothing): T { + val value = join() + if (isCompletedExceptionally) { + op() + } + return value + } } diff --git a/src/main/kotlin/moe/kageru/kagebot/command/RoleAssignment.kt b/src/main/kotlin/moe/kageru/kagebot/command/RoleAssignment.kt index 36b8402..3120287 100644 --- a/src/main/kotlin/moe/kageru/kagebot/command/RoleAssignment.kt +++ b/src/main/kotlin/moe/kageru/kagebot/command/RoleAssignment.kt @@ -2,6 +2,7 @@ package moe.kageru.kagebot.command import moe.kageru.kagebot.Log import moe.kageru.kagebot.Util +import moe.kageru.kagebot.Util.getUser import moe.kageru.kagebot.config.RawAssignment import org.javacord.api.event.message.MessageCreateEvent @@ -10,8 +11,7 @@ internal class RoleAssignment(rawAssignment: RawAssignment) { Util.findRole(idOrName) } ?: throw IllegalArgumentException("Can’t find role “${rawAssignment.role}”") - fun assign(message: MessageCreateEvent) { - Util.userFromMessage(message)?.addRole(role, "Requested via command.") + fun assign(message: MessageCreateEvent) = + message.getUser()?.addRole(role, "Requested via command.") ?: Log.warn("Could not find user ${message.messageAuthor.name} for role assign") - } }