From 39083d824880841787adb6f1d60629e657175ed8 Mon Sep 17 00:00:00 2001 From: kageru Date: Thu, 14 Nov 2019 15:10:30 +0100 Subject: [PATCH] Indent with 2 spaces instead of 4 --- .editorconfig | 8 + src/main/kotlin/moe/kageru/kagebot/Globals.kt | 4 +- src/main/kotlin/moe/kageru/kagebot/Kagebot.kt | 64 +-- src/main/kotlin/moe/kageru/kagebot/Log.kt | 36 +- .../kotlin/moe/kageru/kagebot/MessageUtil.kt | 80 +-- src/main/kotlin/moe/kageru/kagebot/Util.kt | 133 ++--- .../moe/kageru/kagebot/command/Command.kt | 66 +-- .../kageru/kagebot/command/MessageActions.kt | 42 +- .../kageru/kagebot/command/MessageRedirect.kt | 44 +- .../moe/kageru/kagebot/command/Permissions.kt | 30 +- .../kageru/kagebot/command/RoleAssignment.kt | 10 +- .../moe/kageru/kagebot/config/Config.kt | 24 +- .../moe/kageru/kagebot/config/ConfigParser.kt | 40 +- .../moe/kageru/kagebot/config/ConfigSpecs.kt | 16 +- .../kotlin/moe/kageru/kagebot/cron/CronD.kt | 18 +- .../kagebot/extensions/ArrowExtensions.kt | 8 +- .../kagebot/extensions/JavacordExtensions.kt | 2 +- .../kageru/kagebot/features/DebugFeature.kt | 80 +-- .../kageru/kagebot/features/FeatureTypes.kt | 4 +- .../moe/kageru/kagebot/features/Features.kt | 38 +- .../kagebot/features/GetConfigFeature.kt | 6 +- .../kageru/kagebot/features/HelpFeature.kt | 12 +- .../kagebot/features/SetConfigFeature.kt | 36 +- .../kageru/kagebot/features/TempVCFeature.kt | 74 +-- .../kageru/kagebot/features/TimeoutFeature.kt | 96 ++-- .../kageru/kagebot/features/WelcomeFeature.kt | 60 +- .../moe/kageru/kagebot/persistence/Dao.kt | 54 +- .../kotlin/moe/kageru/kagebot/ConfigTest.kt | 44 +- .../kotlin/moe/kageru/kagebot/TestUtil.kt | 218 ++++---- .../moe/kageru/kagebot/command/CommandTest.kt | 524 +++++++++--------- .../kagebot/features/ConfigFeatureTest.kt | 23 +- .../kagebot/features/DebugFeatureTest.kt | 36 +- .../kagebot/features/HelpFeatureTest.kt | 80 +-- .../kagebot/features/TimeoutFeatureTest.kt | 92 +-- .../kagebot/features/WelcomeFeatureTest.kt | 66 +-- 35 files changed, 1090 insertions(+), 1078 deletions(-) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..9da00de --- /dev/null +++ b/.editorconfig @@ -0,0 +1,8 @@ +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +trim_trailing_whitespace = true +insert_final_newline = true diff --git a/src/main/kotlin/moe/kageru/kagebot/Globals.kt b/src/main/kotlin/moe/kageru/kagebot/Globals.kt index 747e25b..a49631d 100644 --- a/src/main/kotlin/moe/kageru/kagebot/Globals.kt +++ b/src/main/kotlin/moe/kageru/kagebot/Globals.kt @@ -5,6 +5,6 @@ import org.javacord.api.DiscordApi import java.util.concurrent.atomic.AtomicInteger object Globals { - lateinit var api: DiscordApi - val commandCounter: AtomicInteger = AtomicInteger(Dao.getCommandCounter()) + lateinit var api: DiscordApi + val commandCounter: AtomicInteger = AtomicInteger(Dao.getCommandCounter()) } diff --git a/src/main/kotlin/moe/kageru/kagebot/Kagebot.kt b/src/main/kotlin/moe/kageru/kagebot/Kagebot.kt index e55be30..5bcdf44 100644 --- a/src/main/kotlin/moe/kageru/kagebot/Kagebot.kt +++ b/src/main/kotlin/moe/kageru/kagebot/Kagebot.kt @@ -12,44 +12,44 @@ import java.io.File import kotlin.system.exitProcess fun main() { - Kagebot.init() + Kagebot.init() } object Kagebot { - fun MessageCreateEvent.process() { - if (messageAuthor.isBotUser) { - handleOwn() - return - } - Config.commands - .find { it.matches(readableMessageContent) && it.isAllowed(this) } - .map { it.execute(this) } + fun MessageCreateEvent.process() { + if (messageAuthor.isBotUser) { + handleOwn() + return } + Config.commands + .find { it.matches(readableMessageContent) && it.isAllowed(this) } + .map { it.execute(this) } + } - private fun MessageCreateEvent.handleOwn() { - if (messageAuthor.isYourself) { - val loggedMessage = readableMessageContent.ifBlank { "[embed]" } - Log.info(" $loggedMessage") - } + private fun MessageCreateEvent.handleOwn() { + if (messageAuthor.isYourself) { + val loggedMessage = readableMessageContent.ifBlank { "[embed]" } + Log.info(" $loggedMessage") } + } - fun init() { - val secret = File("secret").readText().trim() - val api = DiscordApiBuilder().setToken(secret).login().join() - Globals.api = api - ConfigParser.initialLoad(ConfigParser.DEFAULT_CONFIG_PATH).mapLeft { e -> - println("Config parsing error:\n$e,\n${e.message},\n${e.stackTrace.joinToString("\n")}") - exitProcess(1) - } - Runtime.getRuntime().addShutdownHook(Thread { - Log.info("Bot has been interrupted. Shutting down.") - Dao.setCommandCounter(Globals.commandCounter.get()) - Dao.close() - api.disconnect() - }) - Log.info("kagebot Mk II running") - api.addMessageCreateListener { checked { it.process() } } - Config.features.eventFeatures().forEach { it.register(api) } - CronD.startAll() + fun init() { + val secret = File("secret").readText().trim() + val api = DiscordApiBuilder().setToken(secret).login().join() + Globals.api = api + ConfigParser.initialLoad(ConfigParser.DEFAULT_CONFIG_PATH).mapLeft { e -> + println("Config parsing error:\n$e,\n${e.message},\n${e.stackTrace.joinToString("\n")}") + exitProcess(1) } + Runtime.getRuntime().addShutdownHook(Thread { + Log.info("Bot has been interrupted. Shutting down.") + Dao.setCommandCounter(Globals.commandCounter.get()) + Dao.close() + api.disconnect() + }) + Log.info("kagebot Mk II running") + api.addMessageCreateListener { checked { it.process() } } + Config.features.eventFeatures().forEach { it.register(api) } + CronD.startAll() + } } diff --git a/src/main/kotlin/moe/kageru/kagebot/Log.kt b/src/main/kotlin/moe/kageru/kagebot/Log.kt index 76aaa4f..44cc192 100644 --- a/src/main/kotlin/moe/kageru/kagebot/Log.kt +++ b/src/main/kotlin/moe/kageru/kagebot/Log.kt @@ -8,30 +8,30 @@ import java.util.logging.LogRecord import java.util.logging.Logger object Log { - private val log: Logger by lazy { - Logger.getGlobal().apply { - addHandler( - FileHandler("kagebot.log", true).apply { - formatter = LogFormatter() - } - ) + private val log: Logger by lazy { + Logger.getGlobal().apply { + addHandler( + FileHandler("kagebot.log", true).apply { + formatter = LogFormatter() } + ) } + } - fun info(message: String) { - log.info(message) - } + fun info(message: String) { + log.info(message) + } - fun warn(message: String) { - log.warning(message) - } + fun warn(message: String) { + log.warning(message) + } } private class LogFormatter : Formatter() { - private val timeFormatter: DateTimeFormatter = - DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").withZone(ZoneId.systemDefault()) + private val timeFormatter: DateTimeFormatter = + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").withZone(ZoneId.systemDefault()) - override fun format(record: LogRecord): String { - return "[${record.level}] ${timeFormatter.format(record.instant)}: ${record.message}\n" - } + override fun format(record: LogRecord): String { + return "[${record.level}] ${timeFormatter.format(record.instant)}: ${record.message}\n" + } } diff --git a/src/main/kotlin/moe/kageru/kagebot/MessageUtil.kt b/src/main/kotlin/moe/kageru/kagebot/MessageUtil.kt index 943b1d6..a93f09b 100644 --- a/src/main/kotlin/moe/kageru/kagebot/MessageUtil.kt +++ b/src/main/kotlin/moe/kageru/kagebot/MessageUtil.kt @@ -9,53 +9,53 @@ import org.javacord.api.entity.message.embed.EmbedBuilder import java.util.concurrent.CompletableFuture object MessageUtil { - fun MessageAuthor.mention() = "<@$id>" + fun MessageAuthor.mention() = "<@$id>" - fun withEmbed(op: EmbedBuilder.() -> Unit): EmbedBuilder { - return EmbedBuilder().apply { - Config.server.icon.ifPresent { setThumbnail(it) } - setColor(SystemSpec.color) - op() - } + fun withEmbed(op: EmbedBuilder.() -> Unit): EmbedBuilder { + return EmbedBuilder().apply { + Config.server.icon.ifPresent { setThumbnail(it) } + setColor(SystemSpec.color) + op() } + } - fun Messageable.sendEmbed(op: EmbedBuilder.() -> Unit) { - val embed = withEmbed { - setTimestampToNow() - op() - } - sendMessage(embed) + fun Messageable.sendEmbed(op: EmbedBuilder.() -> Unit) { + val embed = withEmbed { + setTimestampToNow() + op() } + sendMessage(embed) + } - /** - * Send and embed and add the current time to it. - * The time is not set in [withEmbed] because of https://git.kageru.moe/kageru/discord-kagebot/issues/13. - */ - fun sendEmbed(target: Messageable, embed: EmbedBuilder): CompletableFuture { - return target.sendMessage(embed.setTimestampToNow()) - } + /** + * Send and embed and add the current time to it. + * The time is not set in [withEmbed] because of https://git.kageru.moe/kageru/discord-kagebot/issues/13. + */ + fun sendEmbed(target: Messageable, embed: EmbedBuilder): CompletableFuture { + return target.sendMessage(embed.setTimestampToNow()) + } - /** - * The reason we use a list here (rather than a map) is that maps would not retain the order specified in the config. - * I tried LinkedHashMaps, but those don’t seem to work either. - */ - fun listToEmbed(contents: List): EmbedBuilder { - check(contents.size % 2 != 1) { "Embed must have even number of content strings (title/content pairs)" } - return withEmbed { - contents.toPairs().forEach { (heading, content) -> - addField(heading, content) - } - } + /** + * The reason we use a list here (rather than a map) is that maps would not retain the order specified in the config. + * I tried LinkedHashMaps, but those don’t seem to work either. + */ + fun listToEmbed(contents: List): EmbedBuilder { + check(contents.size % 2 != 1) { "Embed must have even number of content strings (title/content pairs)" } + return withEmbed { + contents.toPairs().forEach { (heading, content) -> + addField(heading, content) + } } + } - /** - * Convert a list of elements to pairs, retaining order. - * The last element is dropped if the input size is odd. - * [1, 2, 3, 4, 5] -> [[1, 2], [3, 4]] - */ - private fun Collection.toPairs(): List> = this.iterator().run { - (0 until size / 2).map { - Pair(next(), next()) - } + /** + * Convert a list of elements to pairs, retaining order. + * The last element is dropped if the input size is odd. + * [1, 2, 3, 4, 5] -> [[1, 2], [3, 4]] + */ + private fun Collection.toPairs(): List> = this.iterator().run { + (0 until size / 2).map { + Pair(next(), next()) } + } } diff --git a/src/main/kotlin/moe/kageru/kagebot/Util.kt b/src/main/kotlin/moe/kageru/kagebot/Util.kt index c1a91bf..acba393 100644 --- a/src/main/kotlin/moe/kageru/kagebot/Util.kt +++ b/src/main/kotlin/moe/kageru/kagebot/Util.kt @@ -1,8 +1,11 @@ package moe.kageru.kagebot -import arrow.core.* +import arrow.core.Either +import arrow.core.ListK +import arrow.core.Option import arrow.core.extensions.either.monad.flatMap import arrow.core.extensions.list.foldable.find +import arrow.core.firstOrNone import moe.kageru.kagebot.config.Config.server import moe.kageru.kagebot.extensions.* import org.javacord.api.entity.channel.TextChannel @@ -16,80 +19,80 @@ import java.util.concurrent.CompletableFuture import java.util.concurrent.CompletionException object Util { - inline fun T.applyIf(condition: Boolean, op: (T) -> T): T { - return if (condition) op(this) else this + inline fun T.applyIf(condition: Boolean, op: (T) -> T): T { + return if (condition) op(this) else this + } + + fun hasOneOf(messageAuthor: MessageAuthor, roles: Set): Boolean { + return messageAuthor.asUser().asOption().flatMap { user -> + user.roles().find { it in roles } + }.nonEmpty() + } + + private val channelIdRegex = Regex("\\d{18}") + private fun String.isEntityId() = channelIdRegex.matches(this) + + fun findRole(idOrName: String): Either { + return when { + idOrName.isEntityId() -> server.getRoleById(idOrName).asOption().toEither { 0 } + else -> server.rolesByName(idOrName).getOnly() + }.mapLeft { "Found $it results, expected 1" } + } + + private fun ListK.getOnly(): Either { + return when (size) { + 1 -> Either.right(first()) + else -> Either.left(size) } + } - fun hasOneOf(messageAuthor: MessageAuthor, roles: Set): Boolean { - return messageAuthor.asUser().asOption().flatMap { user -> - user.roles().find { it in roles } - }.nonEmpty() + fun findUser(idOrName: String): Option { + return when { + idOrName.isEntityId() -> server.getMemberById(idOrName).asOption() + idOrName.contains('#') -> server.getMemberByDiscriminatedNameIgnoreCase(idOrName).asOption() + else -> server.membersByName(idOrName).firstOrNone() } + } - private val channelIdRegex = Regex("\\d{18}") - private fun String.isEntityId() = channelIdRegex.matches(this) - - fun findRole(idOrName: String): Either { - return when { - idOrName.isEntityId() -> server.getRoleById(idOrName).asOption().toEither { 0 } - else -> server.rolesByName(idOrName).getOnly() - }.mapLeft { "Found $it results, expected 1" } + fun CompletableFuture.asOption(): Option { + return try { + Option.just(join()) + } catch (e: CompletionException) { + Option.empty() } + } - private fun ListK.getOnly(): Either { - return when (size) { - 1 -> Either.right(first()) - else -> Either.left(size) + fun Optional.asOption(): Option = if (this.isPresent) Option.just(this.get()) else Option.empty() + + fun findChannel(idOrName: String): Either { + return when { + idOrName.isEntityId() -> server.channelById(idOrName).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.channelsByName(idOrName).getOnly().mapLeft { "Found $it channels for $idOrName, expected 1" } } + } - fun findUser(idOrName: String): Option { - return when { - idOrName.isEntityId() -> server.getMemberById(idOrName).asOption() - idOrName.contains('#') -> server.getMemberByDiscriminatedNameIgnoreCase(idOrName).asOption() - else -> server.membersByName(idOrName).firstOrNone() - } - } - - fun CompletableFuture.asOption(): Option { - return try { - Option.just(join()) - } catch (e: CompletionException) { - Option.empty() - } - } - - fun Optional.asOption(): Option = if (this.isPresent) Option.just(this.get()) else Option.empty() - - fun findChannel(idOrName: String): Either { - return when { - idOrName.isEntityId() -> server.channelById(idOrName).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.channelsByName(idOrName).getOnly().mapLeft { "Found $it channels for $idOrName, expected 1" } - } - } - - inline fun checked(op: (() -> Unit)) { - try { - op() - } catch (e: Exception) { - Log.warn("An uncaught exception occurred.\n$e") - Log.warn(e.stackTrace.joinToString("\n")) - MessageUtil.sendEmbed( - Globals.api.owner.get(), - EmbedBuilder() - .setColor(Color.RED) - .addField("Error", "kagebot has encountered an error") - .addField( - "$e", """``` + inline fun checked(op: (() -> Unit)) { + try { + op() + } catch (e: Exception) { + Log.warn("An uncaught exception occurred.\n$e") + Log.warn(e.stackTrace.joinToString("\n")) + MessageUtil.sendEmbed( + Globals.api.owner.get(), + EmbedBuilder() + .setColor(Color.RED) + .addField("Error", "kagebot has encountered an error") + .addField( + "$e", """``` ${e.stackTrace.joinToString("\n\t")} ```""".trimIndent().run { applyIf(length > 1800) { substring(1..1800) } } - ) - ) - } + ) + ) } + } } diff --git a/src/main/kotlin/moe/kageru/kagebot/command/Command.kt b/src/main/kotlin/moe/kageru/kagebot/command/Command.kt index 4729812..dca8eea 100644 --- a/src/main/kotlin/moe/kageru/kagebot/command/Command.kt +++ b/src/main/kotlin/moe/kageru/kagebot/command/Command.kt @@ -14,47 +14,47 @@ import org.javacord.api.event.message.MessageCreateEvent private const val AUTHOR_PLACEHOLDER = "@@" class Command( - val trigger: String, - private val response: String? = null, - private val permissions: Permissions?, - @JsonProperty("action") - private val actions: MessageActions?, - embed: List?, - feature: String?, - matchType: String? + val trigger: String, + private val response: String? = null, + private val permissions: Permissions?, + @JsonProperty("action") + private val actions: MessageActions?, + embed: List?, + feature: String?, + matchType: String? ) { - val matchType: MatchType = matchType?.let { type -> - MatchType.values().find { it.name.equals(type, ignoreCase = true) } - ?: throw IllegalArgumentException("Invalid [command.matchType]: “$matchType”") - } ?: MatchType.PREFIX - val regex: Regex? = if (this.matchType == MatchType.REGEX) Regex(trigger) else null - val embed: EmbedBuilder? = embed?.let(MessageUtil::listToEmbed) - private val feature: MessageFeature? = feature?.let { Config.features.findByString(it) } + val matchType: MatchType = matchType?.let { type -> + MatchType.values().find { it.name.equals(type, ignoreCase = true) } + ?: throw IllegalArgumentException("Invalid [command.matchType]: “$matchType”") + } ?: MatchType.PREFIX + val regex: Regex? = if (this.matchType == MatchType.REGEX) Regex(trigger) else null + val embed: EmbedBuilder? = embed?.let(MessageUtil::listToEmbed) + private val feature: MessageFeature? = feature?.let { Config.features.findByString(it) } - fun matches(msg: String) = this.matchType.matches(msg, this) + fun matches(msg: String) = this.matchType.matches(msg, this) - fun isAllowed(message: MessageCreateEvent) = permissions?.isAllowed(message) ?: true + fun isAllowed(message: MessageCreateEvent) = permissions?.isAllowed(message) ?: true - fun execute(message: MessageCreateEvent) { - Log.info("Executing command ${this.trigger} triggered by user ${message.messageAuthor.discriminatedName} (ID: ${message.messageAuthor.id})") - Globals.commandCounter.incrementAndGet() - this.actions?.run(message, this) - this.response?.let { - message.channel.sendMessage(respond(message.messageAuthor, it)) - } - this.embed?.let { - MessageUtil.sendEmbed(message.channel, embed) - } - this.feature?.handle(message) + fun execute(message: MessageCreateEvent) { + Log.info("Executing command ${this.trigger} triggered by user ${message.messageAuthor.discriminatedName} (ID: ${message.messageAuthor.id})") + Globals.commandCounter.incrementAndGet() + this.actions?.run(message, this) + this.response?.let { + message.channel.sendMessage(respond(message.messageAuthor, it)) } + this.embed?.let { + MessageUtil.sendEmbed(message.channel, embed) + } + this.feature?.handle(message) + } - private fun respond(author: MessageAuthor, response: String) = - response.replace(AUTHOR_PLACEHOLDER, author.mention()) + private fun respond(author: MessageAuthor, response: String) = + response.replace(AUTHOR_PLACEHOLDER, author.mention()) } @Suppress("unused") enum class MatchType(val matches: (String, Command) -> Boolean) { - PREFIX({ message, command -> message.startsWith(command.trigger, ignoreCase = true) }), - CONTAINS({ message, command -> message.contains(command.trigger, ignoreCase = true) }), - REGEX({ message, command -> command.regex!!.matches(message) }); + PREFIX({ message, command -> message.startsWith(command.trigger, ignoreCase = true) }), + CONTAINS({ message, command -> message.contains(command.trigger, ignoreCase = true) }), + REGEX({ message, command -> command.regex!!.matches(message) }); } diff --git a/src/main/kotlin/moe/kageru/kagebot/command/MessageActions.kt b/src/main/kotlin/moe/kageru/kagebot/command/MessageActions.kt index 1627812..9d3d9cd 100644 --- a/src/main/kotlin/moe/kageru/kagebot/command/MessageActions.kt +++ b/src/main/kotlin/moe/kageru/kagebot/command/MessageActions.kt @@ -8,31 +8,31 @@ import moe.kageru.kagebot.config.LocalizationSpec import org.javacord.api.event.message.MessageCreateEvent class MessageActions( - private val delete: Boolean = false, - private val redirect: MessageRedirect?, - @JsonProperty("assign") - private val assignment: RoleAssignment? + private val delete: Boolean = false, + private val redirect: MessageRedirect?, + @JsonProperty("assign") + private val assignment: RoleAssignment? ) { - fun run(message: MessageCreateEvent, command: Command) { - if (delete) { - deleteMessage(message) - } - redirect?.execute(message, command) - assignment?.assign(message) + fun run(message: MessageCreateEvent, command: Command) { + if (delete) { + deleteMessage(message) } + redirect?.execute(message, command) + assignment?.assign(message) + } - private fun deleteMessage(message: MessageCreateEvent) { - if (message.message.canYouDelete()) { - message.deleteMessage() - message.messageAuthor.asUser().ifPresent { user -> - user.sendEmbed { - addField("__Blacklisted__", Config.localization[LocalizationSpec.messageDeleted]) - addField("Original:", "“${message.readableMessageContent}”") - } - } - } else { - Log.info("Tried to delete a message without the necessary permissions. Channel: ${message.channel.id}") + private fun deleteMessage(message: MessageCreateEvent) { + if (message.message.canYouDelete()) { + message.deleteMessage() + message.messageAuthor.asUser().ifPresent { user -> + user.sendEmbed { + addField("__Blacklisted__", Config.localization[LocalizationSpec.messageDeleted]) + addField("Original:", "“${message.readableMessageContent}”") } + } + } else { + Log.info("Tried to delete a message without the necessary permissions. Channel: ${message.channel.id}") } + } } diff --git a/src/main/kotlin/moe/kageru/kagebot/command/MessageRedirect.kt b/src/main/kotlin/moe/kageru/kagebot/command/MessageRedirect.kt index 947a484..420cd94 100644 --- a/src/main/kotlin/moe/kageru/kagebot/command/MessageRedirect.kt +++ b/src/main/kotlin/moe/kageru/kagebot/command/MessageRedirect.kt @@ -12,29 +12,29 @@ 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).unwrap() + private val targetChannel: TextChannel = Util.findChannel(target).unwrap() - fun execute(message: MessageCreateEvent, command: Command) { - val embed = MessageUtil.withEmbed { - val redirectedText = message.readableMessageContent - .applyIf(command.matchType == MatchType.PREFIX) { content -> - content.removePrefix(command.trigger).trim() - } - addField(Config.localization[LocalizationSpec.redirectedMessage], redirectedText) - Log.info("Redirected message: $redirectedText") - } - // No inlined if/else because the types are different. - // Passing the full message author will also include the avatar in the embed. - embed.apply { - if (anonymous) { - setAuthor("Anonymous") - } else { - setAuthor(message.messageAuthor) - } - } - - if (MessageUtil.sendEmbed(targetChannel, embed).asOption().isEmpty()) { - Log.warn("Could not redirect message to channel $targetChannel") + fun execute(message: MessageCreateEvent, command: Command) { + val embed = MessageUtil.withEmbed { + val redirectedText = message.readableMessageContent + .applyIf(command.matchType == MatchType.PREFIX) { content -> + content.removePrefix(command.trigger).trim() } + addField(Config.localization[LocalizationSpec.redirectedMessage], redirectedText) + Log.info("Redirected message: $redirectedText") } + // No inlined if/else because the types are different. + // Passing the full message author will also include the avatar in the embed. + embed.apply { + if (anonymous) { + setAuthor("Anonymous") + } else { + setAuthor(message.messageAuthor) + } + } + + if (MessageUtil.sendEmbed(targetChannel, embed).asOption().isEmpty()) { + Log.warn("Could not redirect message to channel $targetChannel") + } + } } diff --git a/src/main/kotlin/moe/kageru/kagebot/command/Permissions.kt b/src/main/kotlin/moe/kageru/kagebot/command/Permissions.kt index 2f2517b..c8bd7df 100644 --- a/src/main/kotlin/moe/kageru/kagebot/command/Permissions.kt +++ b/src/main/kotlin/moe/kageru/kagebot/command/Permissions.kt @@ -7,22 +7,22 @@ import org.javacord.api.entity.permission.Role import org.javacord.api.event.message.MessageCreateEvent class Permissions( - hasOneOf: List?, - hasNoneOf: List?, - private val onlyDM: Boolean = false + hasOneOf: List?, + hasNoneOf: List?, + private val onlyDM: Boolean = false ) { - private val hasOneOf: Option> = resolveRoles(hasOneOf) - private val hasNoneOf: Option> = resolveRoles(hasNoneOf) + private val hasOneOf: Option> = resolveRoles(hasOneOf) + private val hasNoneOf: Option> = resolveRoles(hasNoneOf) - private fun resolveRoles(hasOneOf: List?): Option> { - return Option.fromNullable(hasOneOf?.mapTo(mutableSetOf(), { Util.findRole(it).unwrap() })) - } + private fun resolveRoles(hasOneOf: List?): Option> { + 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) } - } + 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) } + } } diff --git a/src/main/kotlin/moe/kageru/kagebot/command/RoleAssignment.kt b/src/main/kotlin/moe/kageru/kagebot/command/RoleAssignment.kt index 9f8af30..e002e5c 100644 --- a/src/main/kotlin/moe/kageru/kagebot/command/RoleAssignment.kt +++ b/src/main/kotlin/moe/kageru/kagebot/command/RoleAssignment.kt @@ -8,10 +8,10 @@ import moe.kageru.kagebot.extensions.unwrap import org.javacord.api.event.message.MessageCreateEvent class RoleAssignment(@JsonProperty("role") role: String) { - private val role = Util.findRole(role).unwrap() + private val role = Util.findRole(role).unwrap() - fun assign(message: MessageCreateEvent) = message.getUser().fold( - { Log.warn("Could not find user ${message.messageAuthor.name} for role assign") }, - { it.addRole(role, "Requested via command.") } - ) + fun assign(message: MessageCreateEvent) = message.getUser().fold( + { Log.warn("Could not find user ${message.messageAuthor.name} for role assign") }, + { it.addRole(role, "Requested via command.") } + ) } diff --git a/src/main/kotlin/moe/kageru/kagebot/config/Config.kt b/src/main/kotlin/moe/kageru/kagebot/config/Config.kt index 34ab95f..0ab747f 100644 --- a/src/main/kotlin/moe/kageru/kagebot/config/Config.kt +++ b/src/main/kotlin/moe/kageru/kagebot/config/Config.kt @@ -9,18 +9,18 @@ import moe.kageru.kagebot.features.Features import org.javacord.api.entity.server.Server object Config { - val systemSpec = Config { addSpec(SystemSpec) }.from.toml - val localeSpec = Config { addSpec(LocalizationSpec) }.from.toml - val commandSpec = Config { addSpec(CommandSpec) }.from.toml - val featureSpec = Config { addSpec(FeatureSpec) }.from.toml + val systemSpec = Config { addSpec(SystemSpec) }.from.toml + val localeSpec = Config { addSpec(LocalizationSpec) }.from.toml + val commandSpec = Config { addSpec(CommandSpec) }.from.toml + val featureSpec = Config { addSpec(FeatureSpec) }.from.toml - lateinit var system: Config - lateinit var localization: Config - lateinit var commandConfig: Config - lateinit var featureConfig: Config - lateinit var server: Server + lateinit var system: Config + lateinit var localization: Config + lateinit var commandConfig: Config + lateinit var featureConfig: Config + lateinit var server: Server - // for easier access - val features: Features get() = featureConfig[FeatureSpec.features] - val commands: ListK get() = commandConfig[CommandSpec.command].k() + // for easier access + val features: Features get() = featureConfig[FeatureSpec.features] + val commands: ListK get() = commandConfig[CommandSpec.command].k() } diff --git a/src/main/kotlin/moe/kageru/kagebot/config/ConfigParser.kt b/src/main/kotlin/moe/kageru/kagebot/config/ConfigParser.kt index bb2c68a..ce78f22 100644 --- a/src/main/kotlin/moe/kageru/kagebot/config/ConfigParser.kt +++ b/src/main/kotlin/moe/kageru/kagebot/config/ConfigParser.kt @@ -7,28 +7,28 @@ import moe.kageru.kagebot.config.SystemSpec.serverId import java.io.File object ConfigParser { - internal const val DEFAULT_CONFIG_PATH = "config.toml" - val configFile: File = File(DEFAULT_CONFIG_PATH) + internal const val DEFAULT_CONFIG_PATH = "config.toml" + val configFile: File = File(DEFAULT_CONFIG_PATH) - fun initialLoad(file: String) = runBlocking { - Either.catch { - val configFile = getFile(file) - val config = Config.systemSpec.file(configFile) - Config.system = config - Config.server = Globals.api.getServerById(config[serverId]) - .orElseThrow { IllegalArgumentException("Invalid server configured.") } - Config.localization = Config.localeSpec.file(configFile) - Config.featureConfig = Config.featureSpec.file(configFile) - Config.commandConfig = Config.commandSpec.file(configFile) - } + fun initialLoad(file: String) = runBlocking { + Either.catch { + val configFile = getFile(file) + val config = Config.systemSpec.file(configFile) + Config.system = config + Config.server = Globals.api.getServerById(config[serverId]) + .orElseThrow { IllegalArgumentException("Invalid server configured.") } + Config.localization = Config.localeSpec.file(configFile) + Config.featureConfig = Config.featureSpec.file(configFile) + Config.commandConfig = Config.commandSpec.file(configFile) } + } - private fun getFile(path: String): File { - val file = File(path) - if (file.isFile) { - return file - } - println("Config not found, falling back to defaults...") - return File(this::class.java.classLoader.getResource(path)!!.toURI()) + private fun getFile(path: String): File { + val file = File(path) + if (file.isFile) { + return file } + println("Config not found, falling back to defaults...") + return File(this::class.java.classLoader.getResource(path)!!.toURI()) + } } diff --git a/src/main/kotlin/moe/kageru/kagebot/config/ConfigSpecs.kt b/src/main/kotlin/moe/kageru/kagebot/config/ConfigSpecs.kt index 676855b..abab2f7 100644 --- a/src/main/kotlin/moe/kageru/kagebot/config/ConfigSpecs.kt +++ b/src/main/kotlin/moe/kageru/kagebot/config/ConfigSpecs.kt @@ -7,21 +7,21 @@ import moe.kageru.kagebot.features.Features import java.awt.Color object SystemSpec : ConfigSpec() { - private val rawColor by optional("#1793d0", name = "color") - val serverId by required() - val color by kotlin.lazy { Color.decode(system[rawColor])!! } + private val rawColor by optional("#1793d0", name = "color") + val serverId by required() + val color by kotlin.lazy { Color.decode(system[rawColor])!! } } object LocalizationSpec : ConfigSpec() { - val redirectedMessage by optional("says") - val messageDeleted by optional("Your message was deleted.") - val timeout by optional("You have been timed out for @@ minutes.") + val redirectedMessage by optional("says") + val messageDeleted by optional("Your message was deleted.") + val timeout by optional("You have been timed out for @@ minutes.") } object CommandSpec : ConfigSpec(prefix = "") { - val command by optional(emptyList()) + val command by optional(emptyList()) } object FeatureSpec : ConfigSpec(prefix = "") { - val features by optional(Features(), name = "feature") + val features by optional(Features(), name = "feature") } diff --git a/src/main/kotlin/moe/kageru/kagebot/cron/CronD.kt b/src/main/kotlin/moe/kageru/kagebot/cron/CronD.kt index 507e3f5..b79ae03 100644 --- a/src/main/kotlin/moe/kageru/kagebot/cron/CronD.kt +++ b/src/main/kotlin/moe/kageru/kagebot/cron/CronD.kt @@ -6,16 +6,16 @@ import kotlinx.coroutines.launch import moe.kageru.kagebot.config.Config object CronD { - fun startAll() { - GlobalScope.launch { - minutely() - } + fun startAll() { + GlobalScope.launch { + minutely() } + } - private suspend fun minutely() { - while (true) { - Config.features.timeout?.checkAndRelease() - delay(60_000) - } + private suspend fun minutely() { + while (true) { + Config.features.timeout?.checkAndRelease() + delay(60_000) } + } } diff --git a/src/main/kotlin/moe/kageru/kagebot/extensions/ArrowExtensions.kt b/src/main/kotlin/moe/kageru/kagebot/extensions/ArrowExtensions.kt index 3e2ed52..10a22d8 100644 --- a/src/main/kotlin/moe/kageru/kagebot/extensions/ArrowExtensions.kt +++ b/src/main/kotlin/moe/kageru/kagebot/extensions/ArrowExtensions.kt @@ -7,16 +7,16 @@ import arrow.core.getOrElse import arrow.typeclasses.Functor fun Either.on(op: (R) -> Unit): Either { - this.map { op(it) } - return this + this.map { op(it) } + return this } fun Either<*, T>.unwrap(): T = getOrElse { error("Attempted to unwrap Either.left") } inline fun Tuple3.mapFirst(AP: Functor, op: (A) -> Kind) = let { (a, b, c) -> - AP.run { op(a).map { Tuple3(it, b, c) } } + AP.run { op(a).map { Tuple3(it, b, c) } } } inline fun Tuple3.mapSecond(AP: Functor, op: (B) -> Kind) = let { (a, b, c) -> - AP.run { op(b).map { Tuple3(a, it, c) } } + AP.run { op(b).map { Tuple3(a, it, c) } } } diff --git a/src/main/kotlin/moe/kageru/kagebot/extensions/JavacordExtensions.kt b/src/main/kotlin/moe/kageru/kagebot/extensions/JavacordExtensions.kt index 6771348..514911e 100644 --- a/src/main/kotlin/moe/kageru/kagebot/extensions/JavacordExtensions.kt +++ b/src/main/kotlin/moe/kageru/kagebot/extensions/JavacordExtensions.kt @@ -3,10 +3,10 @@ package moe.kageru.kagebot.extensions import arrow.core.ListK import arrow.core.Option import arrow.core.k -import org.javacord.api.entity.channel.ServerTextChannel import moe.kageru.kagebot.Util.asOption import moe.kageru.kagebot.config.Config import org.javacord.api.entity.channel.ChannelCategory +import org.javacord.api.entity.channel.ServerTextChannel import org.javacord.api.entity.permission.Role import org.javacord.api.entity.server.Server import org.javacord.api.entity.user.User diff --git a/src/main/kotlin/moe/kageru/kagebot/features/DebugFeature.kt b/src/main/kotlin/moe/kageru/kagebot/features/DebugFeature.kt index c69a967..45dcd61 100644 --- a/src/main/kotlin/moe/kageru/kagebot/features/DebugFeature.kt +++ b/src/main/kotlin/moe/kageru/kagebot/features/DebugFeature.kt @@ -11,51 +11,51 @@ import java.time.temporal.ChronoUnit class DebugFeature : MessageFeature { - override fun handle(message: MessageCreateEvent) { - if (message.messageAuthor.isBotOwner) { - MessageUtil.sendEmbed(message.channel, getPerformanceStats()) - } + override fun handle(message: MessageCreateEvent) { + if (message.messageAuthor.isBotOwner) { + MessageUtil.sendEmbed(message.channel, getPerformanceStats()) } + } - private fun getPerformanceStats(): EmbedBuilder { - val osBean = ManagementFactory.getPlatformMXBean(OperatingSystemMXBean::class.java) - val runtime = Runtime.getRuntime() - return MessageUtil.listToEmbed( - listOf( - "Bot:", getBotStats(), - "Memory:", getMemoryInfo(runtime, osBean), - "CPU:", getCpuInfo(osBean), - "System:", getOsInfo() - ) - ) - } + private fun getPerformanceStats(): EmbedBuilder { + val osBean = ManagementFactory.getPlatformMXBean(OperatingSystemMXBean::class.java) + val runtime = Runtime.getRuntime() + return MessageUtil.listToEmbed( + listOf( + "Bot:", getBotStats(), + "Memory:", getMemoryInfo(runtime, osBean), + "CPU:", getCpuInfo(osBean), + "System:", getOsInfo() + ) + ) + } - private fun getBotStats() = "kagebot has been running for ${getBotUptime()}.\n" + - "During this time, ${Globals.commandCounter.get()} commands have been executed." + private fun getBotStats() = "kagebot has been running for ${getBotUptime()}.\n" + + "During this time, ${Globals.commandCounter.get()} commands have been executed." - private fun getBotUptime(): String { - val uptime = Duration.of(ManagementFactory.getRuntimeMXBean().uptime, ChronoUnit.MILLIS) - return String.format( - "%d days, %d hours, %d minutes, %d seconds", - uptime.toDaysPart(), - uptime.toHoursPart(), - uptime.toMinutesPart(), - uptime.toSecondsPart() - ) - } + private fun getBotUptime(): String { + val uptime = Duration.of(ManagementFactory.getRuntimeMXBean().uptime, ChronoUnit.MILLIS) + return String.format( + "%d days, %d hours, %d minutes, %d seconds", + uptime.toDaysPart(), + uptime.toHoursPart(), + uptime.toMinutesPart(), + uptime.toSecondsPart() + ) + } - private fun getMemoryInfo(runtime: Runtime, osBean: OperatingSystemMXBean): String { - val mb = 1024 * 1024 - return "Memory usage: ${(runtime.totalMemory() - runtime.freeMemory()) / mb} MB.\n" + - "Total system memory: ${osBean.committedVirtualMemorySize / mb}/" + - "${osBean.totalPhysicalMemorySize / mb} MB." - } + private fun getMemoryInfo(runtime: Runtime, osBean: OperatingSystemMXBean): String { + val mb = 1024 * 1024 + return "Memory usage: ${(runtime.totalMemory() - runtime.freeMemory()) / mb} MB.\n" + + "Total system memory: ${osBean.committedVirtualMemorySize / mb}/" + + "${osBean.totalPhysicalMemorySize / mb} MB." + } - private fun getCpuInfo(osBean: OperatingSystemMXBean) = - "The bot is currently using ${String.format("%.4f", osBean.processCpuLoad * 100)}% of the CPU with " + - "${Thread.activeCount()} active threads.\n" + - "Total system load is ${String.format("%.2f", osBean.systemCpuLoad * 100)}%." + private fun getCpuInfo(osBean: OperatingSystemMXBean) = + "The bot is currently using ${String.format("%.4f", osBean.processCpuLoad * 100)}% of the CPU with " + + "${Thread.activeCount()} active threads.\n" + + "Total system load is ${String.format("%.2f", osBean.systemCpuLoad * 100)}%." - private fun getOsInfo() = "Running on ${System.getProperty("os.name")} " + - "${System.getProperty("os.version")}-${System.getProperty("os.arch")}.\n" + private fun getOsInfo() = "Running on ${System.getProperty("os.name")} " + + "${System.getProperty("os.version")}-${System.getProperty("os.arch")}.\n" } diff --git a/src/main/kotlin/moe/kageru/kagebot/features/FeatureTypes.kt b/src/main/kotlin/moe/kageru/kagebot/features/FeatureTypes.kt index a48a18a..d7f3bae 100644 --- a/src/main/kotlin/moe/kageru/kagebot/features/FeatureTypes.kt +++ b/src/main/kotlin/moe/kageru/kagebot/features/FeatureTypes.kt @@ -4,9 +4,9 @@ import org.javacord.api.DiscordApi import org.javacord.api.event.message.MessageCreateEvent interface MessageFeature { - fun handle(message: MessageCreateEvent) + fun handle(message: MessageCreateEvent) } interface EventFeature { - fun register(api: DiscordApi) + fun register(api: DiscordApi) } diff --git a/src/main/kotlin/moe/kageru/kagebot/features/Features.kt b/src/main/kotlin/moe/kageru/kagebot/features/Features.kt index b11048d..a7f703b 100644 --- a/src/main/kotlin/moe/kageru/kagebot/features/Features.kt +++ b/src/main/kotlin/moe/kageru/kagebot/features/Features.kt @@ -1,26 +1,26 @@ package moe.kageru.kagebot.features class Features( - val welcome: WelcomeFeature? = null, - val timeout: TimeoutFeature? = null, - vc: TempVCFeature = TempVCFeature(null) + val welcome: WelcomeFeature? = null, + val timeout: TimeoutFeature? = null, + vc: TempVCFeature = TempVCFeature(null) ) { - private val debug = DebugFeature() - private val help = HelpFeature() - private val getConfig = GetConfigFeature() - private val setConfig = SetConfigFeature() + private val debug = DebugFeature() + private val help = HelpFeature() + private val getConfig = GetConfigFeature() + private val setConfig = SetConfigFeature() - private val all = listOf(welcome, debug, help, getConfig, setConfig, timeout, vc) - private val featureMap = mapOf( - "help" to help, - "debug" to debug, - "welcome" to welcome, - "getConfig" to getConfig, - "setConfig" to setConfig, - "timeout" to timeout, - "vc" to vc - ) + private val all = listOf(welcome, debug, help, getConfig, setConfig, timeout, vc) + private val featureMap = mapOf( + "help" to help, + "debug" to debug, + "welcome" to welcome, + "getConfig" to getConfig, + "setConfig" to setConfig, + "timeout" to timeout, + "vc" to vc + ) - fun findByString(feature: String) = featureMap[feature] - fun eventFeatures() = all.filterIsInstance() + fun findByString(feature: String) = featureMap[feature] + fun eventFeatures() = all.filterIsInstance() } diff --git a/src/main/kotlin/moe/kageru/kagebot/features/GetConfigFeature.kt b/src/main/kotlin/moe/kageru/kagebot/features/GetConfigFeature.kt index 2621aaa..a06ce76 100644 --- a/src/main/kotlin/moe/kageru/kagebot/features/GetConfigFeature.kt +++ b/src/main/kotlin/moe/kageru/kagebot/features/GetConfigFeature.kt @@ -7,7 +7,7 @@ import org.javacord.api.event.message.MessageCreateEvent * Simple message handler to send the current config file via message attachment. */ class GetConfigFeature : MessageFeature { - override fun handle(message: MessageCreateEvent) { - message.channel.sendMessage(ConfigParser.configFile) - } + override fun handle(message: MessageCreateEvent) { + message.channel.sendMessage(ConfigParser.configFile) + } } diff --git a/src/main/kotlin/moe/kageru/kagebot/features/HelpFeature.kt b/src/main/kotlin/moe/kageru/kagebot/features/HelpFeature.kt index 5820e95..d8f4bc5 100644 --- a/src/main/kotlin/moe/kageru/kagebot/features/HelpFeature.kt +++ b/src/main/kotlin/moe/kageru/kagebot/features/HelpFeature.kt @@ -7,13 +7,13 @@ import moe.kageru.kagebot.config.Config import org.javacord.api.event.message.MessageCreateEvent class HelpFeature : MessageFeature { - override fun handle(message: MessageCreateEvent) { - message.channel.sendEmbed { - addField("Commands:", listCommands(message)) - } + override fun handle(message: MessageCreateEvent) { + message.channel.sendEmbed { + addField("Commands:", listCommands(message)) } + } } private fun listCommands(message: MessageCreateEvent) = Config.commands - .filter { it.matchType == MatchType.PREFIX && it.isAllowed(message) } - .joinToString("\n") { it.trigger } + .filter { it.matchType == MatchType.PREFIX && it.isAllowed(message) } + .joinToString("\n") { it.trigger } diff --git a/src/main/kotlin/moe/kageru/kagebot/features/SetConfigFeature.kt b/src/main/kotlin/moe/kageru/kagebot/features/SetConfigFeature.kt index 9357160..1636ad5 100644 --- a/src/main/kotlin/moe/kageru/kagebot/features/SetConfigFeature.kt +++ b/src/main/kotlin/moe/kageru/kagebot/features/SetConfigFeature.kt @@ -6,23 +6,23 @@ import moe.kageru.kagebot.config.ConfigParser import org.javacord.api.event.message.MessageCreateEvent class SetConfigFeature : MessageFeature { - @ExperimentalStdlibApi - override fun handle(message: MessageCreateEvent) { - if (message.messageAttachments.size != 1) { - message.channel.sendMessage("Error: please attach the new config to your message.") - return - } - val newConfig = message.messageAttachments[0].url.openStream().readAllBytes().decodeToString() - try { - Config.localization = Config.localeSpec.string(newConfig) - Config.featureConfig = Config.featureSpec.string(newConfig) - Config.commandConfig = Config.commandSpec.string(newConfig) - ConfigParser.configFile.writeText(newConfig) - message.channel.sendMessage("Config reloaded.") - } catch (e: Exception) { - message.channel.sendEmbed { - addField("Error", "```${e.message}```") - } - } + @ExperimentalStdlibApi + override fun handle(message: MessageCreateEvent) { + if (message.messageAttachments.size != 1) { + message.channel.sendMessage("Error: please attach the new config to your message.") + return } + val newConfig = message.messageAttachments[0].url.openStream().readAllBytes().decodeToString() + try { + Config.localization = Config.localeSpec.string(newConfig) + Config.featureConfig = Config.featureSpec.string(newConfig) + Config.commandConfig = Config.commandSpec.string(newConfig) + ConfigParser.configFile.writeText(newConfig) + message.channel.sendMessage("Config reloaded.") + } catch (e: Exception) { + message.channel.sendEmbed { + addField("Error", "```${e.message}```") + } + } + } } diff --git a/src/main/kotlin/moe/kageru/kagebot/features/TempVCFeature.kt b/src/main/kotlin/moe/kageru/kagebot/features/TempVCFeature.kt index f56f286..ae11728 100644 --- a/src/main/kotlin/moe/kageru/kagebot/features/TempVCFeature.kt +++ b/src/main/kotlin/moe/kageru/kagebot/features/TempVCFeature.kt @@ -16,46 +16,46 @@ import org.javacord.api.entity.channel.ServerVoiceChannel import org.javacord.api.event.message.MessageCreateEvent class TempVCFeature(@JsonProperty("category") category: String? = null) : EventFeature, MessageFeature { - private val category: ChannelCategory? = category?.let { Config.server.categoriesByName(it).first() } + private val category: ChannelCategory? = category?.let { Config.server.categoriesByName(it).first() } - override fun handle(message: MessageCreateEvent): Unit = with(message) { - Either.cond(' ' in readableMessageContent, - { readableMessageContent.split(' ', limit = 2).last() }, - { "Invalid syntax, expected ` `" }) - .flatMap { limit -> - limit.toIntOrNull().rightIfNotNull { "Invalid syntax, expected a number as limit, got $limit" } - }.filterOrElse({ it < 99 }, { "Error: can’t create a channel with that many users." }) - .fold({ err -> channel.sendMessage(err) }, - { limit -> - createChannel(message, limit) - channel.sendMessage("Done") - }) + override fun handle(message: MessageCreateEvent): Unit = with(message) { + Either.cond(' ' in readableMessageContent, + { readableMessageContent.split(' ', limit = 2).last() }, + { "Invalid syntax, expected ` `" }) + .flatMap { limit -> + limit.toIntOrNull().rightIfNotNull { "Invalid syntax, expected a number as limit, got $limit" } + }.filterOrElse({ it < 99 }, { "Error: can’t create a channel with that many users." }) + .fold({ err -> channel.sendMessage(err) }, + { limit -> + createChannel(message, limit) + channel.sendMessage("Done") + }) + } + + override fun register(api: DiscordApi) { + api.addServerVoiceChannelMemberLeaveListener { event -> + if (event.channel.connectedUsers.isEmpty() && Dao.isTemporaryVC(event.channel.idAsString)) { + deleteChannel(event.channel) + } } + } - override fun register(api: DiscordApi) { - api.addServerVoiceChannelMemberLeaveListener { event -> - if (event.channel.connectedUsers.isEmpty() && Dao.isTemporaryVC(event.channel.idAsString)) { - deleteChannel(event.channel) - } - } - } + private fun deleteChannel(channel: ServerVoiceChannel) = + channel.delete("Empty temporary channel").asOption().fold( + { Log.warn("Attempted to delete temporary VC without the necessary permissions") }, + { Dao.removeTemporaryVC(channel.idAsString) } + ) - private fun deleteChannel(channel: ServerVoiceChannel) = - channel.delete("Empty temporary channel").asOption().fold( - { Log.warn("Attempted to delete temporary VC without the necessary permissions") }, - { Dao.removeTemporaryVC(channel.idAsString) } - ) + private fun createChannel(message: MessageCreateEvent, limit: Int): Unit = + Config.server.createVoiceChannelBuilder().apply { + setUserlimit(limit) + setName(generateChannelName(message)) + setAuditLogReason("Created temporary VC for user ${message.messageAuthor.discriminatedName}") + setCategory(category) + }.create().asOption().fold( + { Log.warn("Attempted to create temporary VC without the necessary permissions") }, + { channel -> Dao.addTemporaryVC(channel.idAsString) }) - private fun createChannel(message: MessageCreateEvent, limit: Int): Unit = - Config.server.createVoiceChannelBuilder().apply { - setUserlimit(limit) - setName(generateChannelName(message)) - setAuditLogReason("Created temporary VC for user ${message.messageAuthor.discriminatedName}") - setCategory(category) - }.create().asOption().fold( - { Log.warn("Attempted to create temporary VC without the necessary permissions") }, - { channel -> Dao.addTemporaryVC(channel.idAsString) }) - - private fun generateChannelName(message: MessageCreateEvent): String = - "${message.messageAuthor.name}’s volatile corner" + private fun generateChannelName(message: MessageCreateEvent): String = + "${message.messageAuthor.name}’s volatile corner" } diff --git a/src/main/kotlin/moe/kageru/kagebot/features/TimeoutFeature.kt b/src/main/kotlin/moe/kageru/kagebot/features/TimeoutFeature.kt index 8b342c3..b64becf 100644 --- a/src/main/kotlin/moe/kageru/kagebot/features/TimeoutFeature.kt +++ b/src/main/kotlin/moe/kageru/kagebot/features/TimeoutFeature.kt @@ -23,58 +23,58 @@ import java.time.Duration import java.time.Instant class TimeoutFeature(@JsonProperty("role") role: String) : MessageFeature { - private val timeoutRole: Role = findRole(role).unwrap() + private val timeoutRole: Role = findRole(role).unwrap() - override fun handle(message: MessageCreateEvent) { - message.readableMessageContent.split(' ', limit = 4).let { args -> - Either.cond( - args.size >= 3, - { Tuple3(args[1], args[2], args.getOrNull(3)) }, - { "Error: expected “