From effebf1e07c3e2950ed58a858461a888d738e992 Mon Sep 17 00:00:00 2001 From: Nicholas Rose <35816886+nicholasgrose@users.noreply.github.com> Date: Sun, 6 Aug 2023 00:36:20 -0500 Subject: [PATCH] Improve bot initialization and error handling (#91) --- README.md | 4 +- build.gradle.kts | 2 +- gradle.properties | 6 +- .../kotlin/com/rose/gateway/GatewayPlugin.kt | 7 +- .../com/rose/gateway/config/PluginConfig.kt | 4 +- .../config/markers/CommonExtensionConfig.kt | 14 +- .../rose/gateway/config/schema/BotConfig.kt | 6 +- .../bot/{BotContext.kt => BotState.kt} | 26 ++- .../rose/gateway/discord/bot/DiscordBot.kt | 211 ++++++------------ .../discord/bot/DiscordBotController.kt | 98 ++++++++ .../discord/bot/checks/MessageCheck.kt | 6 +- .../gateway/discord/bot/client/ClientInfo.kt | 6 +- .../bot/extensions/chat/GameChatEvent.kt | 5 +- .../bot/message/DiscordMessageSender.kt | 6 +- .../discord/bot/presence/BotPresence.kt | 6 +- .../rose/gateway/minecraft/CommandRegistry.kt | 2 +- .../TextChannelMentionTokenProcessor.kt | 6 +- .../VoiceChannelMentionTokenProcessor.kt | 6 +- .../processing/tokens/result/MentionResult.kt | 8 +- .../minecraft/commands/runners/BotCommands.kt | 59 ++--- .../minecraft/component/DiscordComponent.kt | 2 +- .../gateway/shared/koin/InitializeKoin.kt | 4 +- src/main/resources/plugin.yml | 2 +- 23 files changed, 279 insertions(+), 217 deletions(-) rename src/main/kotlin/com/rose/gateway/discord/bot/{BotContext.kt => BotState.kt} (71%) create mode 100644 src/main/kotlin/com/rose/gateway/discord/bot/DiscordBotController.kt diff --git a/README.md b/README.md index 6136d249..c434b83e 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Gateway -A [Paper](https://papermc.io/) server plugin that adds [Discord](https://discord.com/) --to-[Minecraft](https://www.minecraft.net/en-us) interactions and vice versa. +A [Paper](https://papermc.io/) server plugin that adds +[Discord](https://discord.com/)-to-[Minecraft](https://www.minecraft.net/en-us) interactions and vice versa. It has a number of extensions that can be enabled or disabled in its configuration, so you don't need to use them all. ## Available extensions: diff --git a/build.gradle.kts b/build.gradle.kts index e176c5f6..1c3c138d 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -10,7 +10,7 @@ plugins { // https://github.com/jlleitschuh/ktlint-gradle id("org.jlleitschuh.gradle.ktlint") version "11.5.0" // https://detekt.dev/ - id("io.gitlab.arturbosch.detekt") version "1.23.0" + id("io.gitlab.arturbosch.detekt") version "1.23.1" } val version: String by project diff --git a/gradle.properties b/gradle.properties index 376fd649..8242f963 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ # Project info -version=1.6.0 +version=1.6.1 group=com.rose.gateway # Plugin configuration # https://github.com/pinterest/ktlint @@ -11,13 +11,13 @@ paperApiRevision=R0.1 # https://github.com/sksamuel/hoplite hopliteVersion=2.7.4 # https://github.com/charleskorn/kaml -kamlVersion=0.54.0 +kamlVersion=0.55.0 # https://kordex.kotlindiscord.com/ kordexVersion=1.5.8-SNAPSHOT # https://github.com/utybo/Lixy lixyVersion=master-SNAPSHOT # https://ktor.io/ -ktorVersion=2.3.2 +ktorVersion=2.3.3 # Compilation options for Kotlin jvmVersion=17 kotlinLanguageVersion=1.9 diff --git a/src/main/kotlin/com/rose/gateway/GatewayPlugin.kt b/src/main/kotlin/com/rose/gateway/GatewayPlugin.kt index 5a98ab77..d5d8388a 100644 --- a/src/main/kotlin/com/rose/gateway/GatewayPlugin.kt +++ b/src/main/kotlin/com/rose/gateway/GatewayPlugin.kt @@ -1,11 +1,12 @@ package com.rose.gateway -import com.rose.gateway.discord.bot.DiscordBot +import com.rose.gateway.discord.bot.DiscordBotController import com.rose.gateway.minecraft.CommandRegistry import com.rose.gateway.minecraft.EventListeners import com.rose.gateway.minecraft.logging.Logger import com.rose.gateway.shared.concurrency.PluginCoroutineScope import com.rose.gateway.shared.koin.initializeKoin +import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import kotlinx.datetime.Clock import org.bukkit.plugin.java.JavaPlugin @@ -32,11 +33,11 @@ class GatewayPlugin : JavaPlugin(), KoinComponent { */ val loader = classLoader - private val bot: DiscordBot by inject() + private val bot: DiscordBotController by inject() private val coroutineScope: PluginCoroutineScope by inject() override fun onEnable() { - runBlocking { + coroutineScope.launch { bot.start() } diff --git a/src/main/kotlin/com/rose/gateway/config/PluginConfig.kt b/src/main/kotlin/com/rose/gateway/config/PluginConfig.kt index 404e6cca..300d316a 100644 --- a/src/main/kotlin/com/rose/gateway/config/PluginConfig.kt +++ b/src/main/kotlin/com/rose/gateway/config/PluginConfig.kt @@ -1,7 +1,7 @@ package com.rose.gateway.config import com.rose.gateway.config.schema.Config -import com.rose.gateway.discord.bot.DiscordBot +import com.rose.gateway.discord.bot.DiscordBotController import com.rose.gateway.shared.concurrency.PluginCoroutineScope import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking @@ -15,7 +15,7 @@ import kotlin.reflect.KType * @constructor Creates plugin config */ class PluginConfig : KoinComponent { - private val bot: DiscordBot by inject() + private val bot: DiscordBotController by inject() private val stringMap: ConfigStringMap by inject() private val pluginCoroutineScope: PluginCoroutineScope by inject() diff --git a/src/main/kotlin/com/rose/gateway/config/markers/CommonExtensionConfig.kt b/src/main/kotlin/com/rose/gateway/config/markers/CommonExtensionConfig.kt index 850cb84c..19adb10b 100644 --- a/src/main/kotlin/com/rose/gateway/config/markers/CommonExtensionConfig.kt +++ b/src/main/kotlin/com/rose/gateway/config/markers/CommonExtensionConfig.kt @@ -1,7 +1,8 @@ package com.rose.gateway.config.markers -import com.rose.gateway.discord.bot.DiscordBot -import kotlinx.coroutines.runBlocking +import com.rose.gateway.discord.bot.DiscordBotController +import com.rose.gateway.shared.concurrency.PluginCoroutineScope +import kotlinx.coroutines.launch import kotlinx.serialization.Transient import org.koin.core.component.KoinComponent import org.koin.core.component.inject @@ -18,7 +19,8 @@ open class CommonExtensionConfig( enabled: Boolean, @Transient val extensionName: String = "None", ) : KoinComponent, ConfigObject { - private val bot: DiscordBot by inject() + private val bot: DiscordBotController by inject() + private val pluginsScope: PluginCoroutineScope by inject() /** * Whether the extension is enabled @@ -36,11 +38,11 @@ open class CommonExtensionConfig( * @param enabled The extensions new status */ private fun modifyExtensionLoadedStatus(enabled: Boolean) { - runBlocking { + pluginsScope.launch { if (enabled) { - bot.bot?.loadExtension(extensionName) + bot.discordBot.kordexBot.await()?.loadExtension(extensionName) } else { - bot.bot?.unloadExtension(extensionName) + bot.discordBot.kordexBot.await()?.unloadExtension(extensionName) } } } diff --git a/src/main/kotlin/com/rose/gateway/config/schema/BotConfig.kt b/src/main/kotlin/com/rose/gateway/config/schema/BotConfig.kt index 933e455d..b7eef488 100644 --- a/src/main/kotlin/com/rose/gateway/config/schema/BotConfig.kt +++ b/src/main/kotlin/com/rose/gateway/config/schema/BotConfig.kt @@ -2,7 +2,7 @@ package com.rose.gateway.config.schema import com.rose.gateway.config.markers.ConfigItem import com.rose.gateway.config.markers.ConfigObject -import com.rose.gateway.discord.bot.DiscordBot +import com.rose.gateway.discord.bot.DiscordBotController import com.rose.gateway.shared.concurrency.PluginCoroutineScope import com.rose.gateway.shared.serialization.SurrogateBasedSerializer import com.rose.gateway.shared.serialization.SurrogateConverter @@ -30,7 +30,7 @@ class BotConfig( @ConfigItem val extensions: ExtensionsConfig, ) : KoinComponent, ConfigObject { private val pluginCoroutineScope: PluginCoroutineScope by inject() - private val bot: DiscordBot by inject() + private val bot: DiscordBotController by inject() /** * The Discord bot's bot token @@ -53,7 +53,7 @@ class BotConfig( var botChannels = botChannels set(value) { field = value - runBlocking { bot.context.fillBotChannels() } + runBlocking { bot.state.fillBotChannels() } } } diff --git a/src/main/kotlin/com/rose/gateway/discord/bot/BotContext.kt b/src/main/kotlin/com/rose/gateway/discord/bot/BotState.kt similarity index 71% rename from src/main/kotlin/com/rose/gateway/discord/bot/BotContext.kt rename to src/main/kotlin/com/rose/gateway/discord/bot/BotState.kt index f47265f6..0b57b88d 100644 --- a/src/main/kotlin/com/rose/gateway/discord/bot/BotContext.kt +++ b/src/main/kotlin/com/rose/gateway/discord/bot/BotState.kt @@ -3,8 +3,11 @@ package com.rose.gateway.discord.bot import com.rose.gateway.config.PluginConfig import com.rose.gateway.config.access.botChannels import com.rose.gateway.discord.bot.client.ClientInfo +import com.rose.gateway.shared.concurrency.PluginCoroutineScope import dev.kord.core.entity.Guild import dev.kord.core.entity.channel.TextChannel +import kotlinx.coroutines.Job +import kotlinx.coroutines.launch import org.koin.core.component.KoinComponent import org.koin.core.component.inject @@ -13,9 +16,20 @@ import org.koin.core.component.inject * * @constructor Creates an empty bot context */ -class BotContext : KoinComponent { - private val bot: DiscordBot by inject() +class BotState : KoinComponent { + private val bot: DiscordBotController by inject() private val config: PluginConfig by inject() + private val pluginScope: PluginCoroutineScope by inject() + + /** + * The bot's status + */ + var status = BotStatus.NOT_STARTED + + /** + * The job running the bot + */ + var botJob: Job? = null /** * The text channels the bot operates in @@ -27,6 +41,12 @@ class BotContext : KoinComponent { */ val botGuilds = mutableSetOf() + init { + pluginScope.launch { + fillBotChannels() + } + } + /** * Fills the valid bot channel set and the valid bot guild set */ @@ -36,7 +56,7 @@ class BotContext : KoinComponent { botChannels.clear() botGuilds.clear() - bot.kordClient()?.guilds?.collect { guild -> + bot.discordBot.kordClient()?.guilds?.collect { guild -> guild.channels.collect { channel -> if (ClientInfo.hasChannelPermissions( channel, diff --git a/src/main/kotlin/com/rose/gateway/discord/bot/DiscordBot.kt b/src/main/kotlin/com/rose/gateway/discord/bot/DiscordBot.kt index 8987a2b9..b19edf80 100644 --- a/src/main/kotlin/com/rose/gateway/discord/bot/DiscordBot.kt +++ b/src/main/kotlin/com/rose/gateway/discord/bot/DiscordBot.kt @@ -10,12 +10,12 @@ import com.rose.gateway.minecraft.logging.Logger import com.rose.gateway.shared.concurrency.PluginCoroutineScope import dev.kord.core.Kord import dev.kord.core.exception.KordInitializationException -import kotlinx.coroutines.Job -import kotlinx.coroutines.launch -import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.Deferred +import kotlinx.coroutines.async import org.koin.core.component.KoinComponent import org.koin.core.component.inject import java.io.IOException +import java.nio.file.Files import java.nio.file.Path /** @@ -29,137 +29,43 @@ class DiscordBot : KoinComponent { private val pluginScope: PluginCoroutineScope by inject() /** - * The bot's context + * The current instance of the KordEx [ExtensibleBot]] */ - val context = BotContext() - - /** - * The bot's status - */ - var botStatus = BotStatus.NOT_STARTED - - /** - * The current instance of the bot - */ - var bot = runBlocking { - safelyBuildBot() + val kordexBot: Deferred = pluginScope.async { + safelyBuildBot().getOrNull() } - private var botJob: Job? = null - /** * Builds a bot with error handling that returns null if building is impossible or fails * * @return The build bot or null, if building failed */ - private suspend fun safelyBuildBot(): ExtensibleBot? = try { + suspend fun safelyBuildBot(): Result = tryKordOperation("Constructing Discord Bot") { if (config.notLoaded()) { Logger.warning("Bot construction failed because no configuration is loaded.") - botStatus = BotStatus.STOPPED because "No valid configuration is loaded." - - null + Result.failure(Exception("No valid configuration is loaded.")) } else { Logger.info("Building Discord bot...") - buildBot(config.botToken()) + Result.success(buildBot(config.botToken())) } - } catch (e: KordInitializationException) { - Logger.warning("Bot construction failed (${e.localizedMessage})") - - botStatus = BotStatus.STOPPED because e.localizedMessage - - null } /** - * Builds the Discord bot on standby + * Executes a suspend function for Kord while handling any exceptions that may occur. * - * @param token The token the bot will be started with - * @return The build bot - */ - private suspend fun buildBot(token: String): ExtensibleBot = ExtensibleBot(token) { - hooks { - kordShutdownHook = false - - afterKoinSetup { - loadModule { - single { plugin } - single { config } - } - } - } - presence { - since = plugin.startTime - playing(BotPresence.presenceForPlayerCount()) - } - applicationCommands { - enabled = true - } - extensions { - extensions.addAll( - DiscordBotConstants.BOT_EXTENSIONS.map { extension -> extension.extensionConstructor() }, - ) - } - plugins { - // The default path of "plugins/" is problematic on a Minecraft server, so we'll remove it and - // redirect the plugin search to "plugins/Gateway/plugins/". - pluginPaths.clear() - pluginPath(Path.of(plugin.dataFolder.path, "plugins")) - } - } - - /** - * Gives the Kord client used by the Discord bot + * @param operationName The name of the operation being performed. + * @param operation The suspend function to be executed. + * @return The result of the Kord operation, or null if an exception occurred. * - * @return The Kord client or null, if no Discord bot exists - */ - fun kordClient(): Kord? = bot?.getKoin()?.get() - - /** - * Starts the Discord bot - */ - suspend fun start() { - Logger.info("Starting Discord bot...") - - if (bot == null) { - Logger.warning("Could not start because no valid bot exists. Check bot status for error.") - - return - } - - botStatus = BotStatus.STARTING - - unloadDisabledExtensions() - context.fillBotChannels() - launchConcurrentBot() - - Logger.info("Discord bot ready!") - } - - /** - * Unloads disabled bot extensions - */ - private suspend fun unloadDisabledExtensions() { - for (extension in DiscordBotConstants.BOT_EXTENSIONS) { - if (!extension.isEnabled()) bot!!.unloadExtension(extension.extensionName()) - } - } - - /** - * Launches the bot in a new parallel task + * @suppress("TooGenericExceptionCaught") */ @Suppress("TooGenericExceptionCaught") - private suspend fun launchConcurrentBot() { - botJob = try { - botStatus = BotStatus.RUNNING - - pluginScope.launch { - bot?.start() - - botStatus = BotStatus.STOPPED - } - } catch (error: Exception) { + suspend fun tryKordOperation(operationName: String, operation: suspend () -> Result): Result = + try { + operation() + } catch (error: java.lang.Exception) { val errorMessage = error.message val newLine = System.lineSeparator() @@ -169,47 +75,76 @@ class DiscordBot : KoinComponent { else -> "Unknown error occurred:$newLine$errorMessage" } - botStatus = BotStatus.STOPPED because failureMessage - Logger.error("Could not start Discord bot. Check status for info.") + Logger.error("$operationName:$newLine$failureMessage") - null + Result.failure(error) } - } /** - * Stops the Discord bot such that it can be restarted + * Builds the Discord bot on standby + * + * @param token The token the bot will be started with + * @return The build bot */ - suspend fun stop() { - botStatus = BotStatus.STOPPING + private suspend fun buildBot(token: String): ExtensibleBot { + val bot = ExtensibleBot(token) { + hooks { + kordShutdownHook = false + + afterKoinSetup { + loadModule { + single { plugin } + single { config } + } + } + } + presence { + since = plugin.startTime + playing(BotPresence.presenceForPlayerCount()) + } + applicationCommands { + enabled = true + } + extensions { + extensions.addAll( + DiscordBotConstants.BOT_EXTENSIONS.map { extension -> extension.extensionConstructor() }, + ) + } + plugins { + // The default path of "plugins/" is problematic on a Minecraft server, so we'll remove it and + // redirect the plugin search to "plugins/Gateway/plugins/". + val correctPluginPath = Path.of(plugin.dataFolder.path, "plugins") + Files.createDirectories(correctPluginPath) + pluginPaths.clear() + pluginPath(correctPluginPath) + } + } + + unloadDisabledExtensions() - bot?.stop() - botJob?.join() + return bot } /** - * Restart the Discord bot without rebuilding it + * Unloads disabled bot extensions */ - suspend fun restart() { - stop() - start() + private suspend fun unloadDisabledExtensions() { + for (extension in DiscordBotConstants.BOT_EXTENSIONS) { + if (!extension.isEnabled()) kordexBot.await()?.unloadExtension(extension.extensionName()) + } } /** - * Fully shutdown the Discord bot. It cannot be restarted unless rebuilt + * Gives the Kord client used by the Discord bot + * + * @return The Kord client or null, if no Discord bot exists */ - suspend fun close() { - botStatus = BotStatus.STOPPING - - bot?.close() - botJob?.join() - } + suspend fun kordClient(): Kord? = kordexBot.await()?.getKoin()?.get() /** - * Fully rebuilds the Discord bot and starts it + * Checks if the bot has yet been built + * + * @return Whether the bot was built */ - suspend fun rebuild() { - close() - bot = safelyBuildBot() - start() - } + suspend fun isBuilt(): Boolean = kordexBot.await() != null } diff --git a/src/main/kotlin/com/rose/gateway/discord/bot/DiscordBotController.kt b/src/main/kotlin/com/rose/gateway/discord/bot/DiscordBotController.kt new file mode 100644 index 00000000..34f80341 --- /dev/null +++ b/src/main/kotlin/com/rose/gateway/discord/bot/DiscordBotController.kt @@ -0,0 +1,98 @@ +package com.rose.gateway.discord.bot + +import com.rose.gateway.minecraft.logging.Logger +import com.rose.gateway.shared.concurrency.PluginCoroutineScope +import kotlinx.coroutines.launch +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject + +/** + * Manages the functionality and lifecycle of the Discord bot + * + * @constructor Create the Discord bot controller + */ +class DiscordBotController : KoinComponent { + private val pluginScope: PluginCoroutineScope by inject() + + /** + * Represents the current state of the bot + */ + var state = BotState() + + /** + * Represents a bot for Discord + */ + var discordBot = DiscordBot() + + /** + * Starts the Discord bot + */ + suspend fun start() { + Logger.info("Starting Discord bot...") + + state.status = BotStatus.STARTING + launchConcurrentBot() + + Logger.info("Discord bot ready!") + } + + /** + * Launches the bot in a new parallel task + */ + private suspend fun launchConcurrentBot() { + state.botJob = pluginScope.launch { + val runResult = discordBot.tryKordOperation("Running Discord Bot") { + val bot = discordBot.kordexBot.await() + ?: return@tryKordOperation Result.failure(Exception("No bot was built that could be started")) + + state.status = BotStatus.RUNNING + bot.start() + state.status = BotStatus.STOPPED + + Result.success(Unit) + } + + if (runResult.isFailure) { + state.status = BotStatus.STOPPED because runResult.exceptionOrNull()?.localizedMessage.toString() + } + } + } + + /** + * Stops the Discord bot such that it can be restarted + */ + suspend fun stop() { + state.status = BotStatus.STOPPING + + discordBot.kordexBot.await()?.stop() + state.botJob?.join() + } + + /** + * Restart the Discord bot without rebuilding it + */ + suspend fun restart() { + stop() + start() + } + + /** + * Fully shutdown the Discord bot. It cannot be restarted unless rebuilt + */ + suspend fun close() { + state.status = BotStatus.STOPPING + + discordBot.kordexBot.await()?.close() + state.botJob?.join() + } + + /** + * Fully rebuilds the Discord bot and starts it + */ + suspend fun rebuild() { + close() + discordBot = DiscordBot() + state = BotState() + start() + } +} diff --git a/src/main/kotlin/com/rose/gateway/discord/bot/checks/MessageCheck.kt b/src/main/kotlin/com/rose/gateway/discord/bot/checks/MessageCheck.kt index 0c8c9b83..99454299 100644 --- a/src/main/kotlin/com/rose/gateway/discord/bot/checks/MessageCheck.kt +++ b/src/main/kotlin/com/rose/gateway/discord/bot/checks/MessageCheck.kt @@ -3,7 +3,7 @@ package com.rose.gateway.discord.bot.checks import com.kotlindiscord.kord.extensions.checks.channelFor import com.kotlindiscord.kord.extensions.checks.messageFor import com.kotlindiscord.kord.extensions.checks.types.Check -import com.rose.gateway.discord.bot.DiscordBot +import com.rose.gateway.discord.bot.DiscordBotController import dev.kord.core.Kord import org.koin.core.component.KoinComponent import org.koin.core.component.inject @@ -14,7 +14,7 @@ import org.koin.core.component.inject * @see Check */ object MessageCheck : KoinComponent { - private val bot: DiscordBot by inject() + private val bot: DiscordBotController by inject() /** * Check that checks that the event is in a valid bot channel @@ -23,7 +23,7 @@ object MessageCheck : KoinComponent { val channelBehaviour = channelFor(event) val channel = channelBehaviour?.asChannelOrNull() - if (bot.context.botChannels.contains(channel)) { + if (bot.state.botChannels.contains(channel)) { pass() } else { fail("Channel is not configured bot channel.") diff --git a/src/main/kotlin/com/rose/gateway/discord/bot/client/ClientInfo.kt b/src/main/kotlin/com/rose/gateway/discord/bot/client/ClientInfo.kt index 3fe4ce11..9209fab1 100644 --- a/src/main/kotlin/com/rose/gateway/discord/bot/client/ClientInfo.kt +++ b/src/main/kotlin/com/rose/gateway/discord/bot/client/ClientInfo.kt @@ -1,7 +1,7 @@ package com.rose.gateway.discord.bot.client import com.kotlindiscord.kord.extensions.utils.permissionsForMember -import com.rose.gateway.discord.bot.DiscordBot +import com.rose.gateway.discord.bot.DiscordBotController import dev.kord.common.entity.Permissions import dev.kord.core.entity.channel.GuildChannel import org.koin.core.component.KoinComponent @@ -13,7 +13,7 @@ import org.koin.core.component.inject * @constructor Create empty Client info */ object ClientInfo : KoinComponent { - private val bot: DiscordBot by inject() + private val bot: DiscordBotController by inject() /** * Determines whether the bot has the correct permissions for a channel @@ -34,6 +34,6 @@ object ClientInfo : KoinComponent { * @return The permissions the bot has in the given channel */ private suspend fun permissionsForChannel(channel: GuildChannel): Permissions { - return channel.permissionsForMember(bot.kordClient()!!.selfId) + return channel.permissionsForMember(bot.discordBot.kordClient()!!.selfId) } } diff --git a/src/main/kotlin/com/rose/gateway/discord/bot/extensions/chat/GameChatEvent.kt b/src/main/kotlin/com/rose/gateway/discord/bot/extensions/chat/GameChatEvent.kt index c294ee20..28dcfaed 100644 --- a/src/main/kotlin/com/rose/gateway/discord/bot/extensions/chat/GameChatEvent.kt +++ b/src/main/kotlin/com/rose/gateway/discord/bot/extensions/chat/GameChatEvent.kt @@ -2,6 +2,7 @@ package com.rose.gateway.discord.bot.extensions.chat import com.kotlindiscord.kord.extensions.events.KordExEvent import com.rose.gateway.discord.bot.DiscordBot +import com.rose.gateway.discord.bot.DiscordBotController import dev.kord.rest.builder.message.create.MessageCreateBuilder import org.koin.core.component.KoinComponent import org.koin.core.component.inject @@ -14,7 +15,7 @@ import org.koin.core.component.inject */ class GameChatEvent(val message: MessageCreateBuilder.() -> Unit) : KordExEvent { companion object : KoinComponent { - private val bot: DiscordBot by inject() + private val bot: DiscordBotController by inject() /** * Triggers a [GameChatEvent] in the [DiscordBot] @@ -23,7 +24,7 @@ class GameChatEvent(val message: MessageCreateBuilder.() -> Unit) : KordExEvent * @receiver The [DiscordBot] */ suspend fun trigger(message: MessageCreateBuilder.() -> Unit) { - bot.bot?.send(GameChatEvent(message)) + bot.discordBot.kordexBot.await()?.send(GameChatEvent(message)) } } } diff --git a/src/main/kotlin/com/rose/gateway/discord/bot/message/DiscordMessageSender.kt b/src/main/kotlin/com/rose/gateway/discord/bot/message/DiscordMessageSender.kt index 8bc04948..d4a9a8c0 100644 --- a/src/main/kotlin/com/rose/gateway/discord/bot/message/DiscordMessageSender.kt +++ b/src/main/kotlin/com/rose/gateway/discord/bot/message/DiscordMessageSender.kt @@ -1,6 +1,6 @@ package com.rose.gateway.discord.bot.message -import com.rose.gateway.discord.bot.DiscordBot +import com.rose.gateway.discord.bot.DiscordBotController import dev.kord.core.behavior.channel.createMessage import dev.kord.rest.builder.message.create.MessageCreateBuilder import org.koin.core.component.KoinComponent @@ -10,7 +10,7 @@ import org.koin.core.component.inject * Provides functions for sending messages in Discord */ object DiscordMessageSender : KoinComponent { - private val bot: DiscordBot by inject() + private val bot: DiscordBotController by inject() /** * Sends a message to all valid channels @@ -19,7 +19,7 @@ object DiscordMessageSender : KoinComponent { * @receiver The bot channels that the message is created in */ suspend fun sendGameChatMessage(message: MessageCreateBuilder.() -> Unit) { - for (channel in bot.context.botChannels) { + for (channel in bot.state.botChannels) { channel.createMessage(message) } } diff --git a/src/main/kotlin/com/rose/gateway/discord/bot/presence/BotPresence.kt b/src/main/kotlin/com/rose/gateway/discord/bot/presence/BotPresence.kt index c153ce0d..005c8129 100644 --- a/src/main/kotlin/com/rose/gateway/discord/bot/presence/BotPresence.kt +++ b/src/main/kotlin/com/rose/gateway/discord/bot/presence/BotPresence.kt @@ -1,6 +1,6 @@ package com.rose.gateway.discord.bot.presence -import com.rose.gateway.discord.bot.DiscordBot +import com.rose.gateway.discord.bot.DiscordBotController import com.rose.gateway.minecraft.server.ServerInfo import com.rose.gateway.shared.text.plurality import org.koin.core.component.KoinComponent @@ -10,13 +10,13 @@ import org.koin.core.component.inject * Methods that modify the Discord bot's presence on Discord */ object BotPresence : KoinComponent { - private val bot: DiscordBot by inject() + private val bot: DiscordBotController by inject() /** * Updates how many players are shown as currently playing in Discord */ suspend fun updatePresencePlayerCount() { - bot.kordClient()?.editPresence { + bot.discordBot.kordClient()?.editPresence { playing(presenceForPlayerCount()) } } diff --git a/src/main/kotlin/com/rose/gateway/minecraft/CommandRegistry.kt b/src/main/kotlin/com/rose/gateway/minecraft/CommandRegistry.kt index ee65c6f3..be258538 100644 --- a/src/main/kotlin/com/rose/gateway/minecraft/CommandRegistry.kt +++ b/src/main/kotlin/com/rose/gateway/minecraft/CommandRegistry.kt @@ -41,7 +41,7 @@ object CommandRegistry : KoinComponent { command("gateway") { subcommand("bot") { subcommand("rebuild") { - runner { context -> BotCommands.restart(context) } + runner { context -> BotCommands.rebuild(context) } } subcommand("restart") { diff --git a/src/main/kotlin/com/rose/gateway/minecraft/chat/processing/tokens/TextChannelMentionTokenProcessor.kt b/src/main/kotlin/com/rose/gateway/minecraft/chat/processing/tokens/TextChannelMentionTokenProcessor.kt index 0ff3728d..fcb14429 100644 --- a/src/main/kotlin/com/rose/gateway/minecraft/chat/processing/tokens/TextChannelMentionTokenProcessor.kt +++ b/src/main/kotlin/com/rose/gateway/minecraft/chat/processing/tokens/TextChannelMentionTokenProcessor.kt @@ -1,6 +1,6 @@ package com.rose.gateway.minecraft.chat.processing.tokens -import com.rose.gateway.discord.bot.DiscordBot +import com.rose.gateway.discord.bot.DiscordBotController import com.rose.gateway.minecraft.chat.processing.tokens.result.MentionResult import com.rose.gateway.minecraft.chat.processing.tokens.result.TokenProcessingResult import com.rose.gateway.shared.parsing.TokenProcessor @@ -22,7 +22,7 @@ class TextChannelMentionTokenProcessor : TokenProcessor): Boolean { - sendAndLogMessage(context.bukkit.sender, "Rebuilding the Discord bot. This may take a while...") + sendAndLogMessage( + context.bukkit.sender, + join( + "Discord bot will now rebuild. ".component(), + "Check Status".tertiaryComponent() + .showTextOnHover("Click to view status".component()) + .runCommandOnClick("/gateway bot status"), + ), + ) pluginScope.launch { bot.rebuild() - sendAndLogMessage( - context.bukkit.sender, - if (bot.botStatus == BotStatus.RUNNING) { - "Discord bot restarted." - } else { - "Discord bot failed to restart. Check bot status for more info." - }, - ) } return true @@ -52,18 +57,18 @@ object BotCommands : KoinComponent { * @return Whether the command succeeded */ fun restart(context: CommandExecuteContext): Boolean { - sendAndLogMessage(context.bukkit.sender, "Restarting the Discord bot...") + sendAndLogMessage( + context.bukkit.sender, + join( + "Discord bot will now restart. ".component(), + "Check Status".tertiaryComponent() + .showTextOnHover("Click to view status".component()) + .runCommandOnClick("/gateway bot status"), + ), + ) pluginScope.launch { bot.restart() - sendAndLogMessage( - context.bukkit.sender, - if (bot.botStatus == BotStatus.RUNNING) { - "Discord bot restarted." - } else { - "Discord bot failed to restart. Check bot status for more info." - }, - ) } return true @@ -76,24 +81,24 @@ object BotCommands : KoinComponent { * @return Whether the command succeeded */ fun stop(context: CommandExecuteContext): Boolean { - sendAndLogMessage(context.bukkit.sender, "Stopping the Discord bot...") + sendAndLogMessage(context.bukkit.sender, "Stopping the Discord bot...".component()) pluginScope.launch { bot.stop() - sendAndLogMessage(context.bukkit.sender, "Discord bot stopped.") + sendAndLogMessage(context.bukkit.sender, "Discord bot stopped.".component()) } return true } /** - * Sends a sender a message abd logs it in the server logs + * Sends a sender a message and logs it in the server logs * * @param sender The sender to send the message to * @param message The message to send and log */ - private fun sendAndLogMessage(sender: CommandSender, message: String) { - Logger.info(message) + private fun sendAndLogMessage(sender: CommandSender, message: Component) { + Logger.info(PlainTextComponentSerializer.plainText().serialize(message)) sender.sendMessage(message) } @@ -104,7 +109,7 @@ object BotCommands : KoinComponent { * @return Whether the command succeeded */ fun status(context: CommandExecuteContext): Boolean { - val status = bot.botStatus + val status = bot.state.status context.bukkit.sender.sendMessage( joinSpace( "Bot Status:".primaryComponent(), diff --git a/src/main/kotlin/com/rose/gateway/minecraft/component/DiscordComponent.kt b/src/main/kotlin/com/rose/gateway/minecraft/component/DiscordComponent.kt index 3cee16cc..ecfa8c78 100644 --- a/src/main/kotlin/com/rose/gateway/minecraft/component/DiscordComponent.kt +++ b/src/main/kotlin/com/rose/gateway/minecraft/component/DiscordComponent.kt @@ -22,6 +22,6 @@ fun atMember(user: Member, userColor: TextColor): Component { * @return The member [Component] */ fun member(user: Member): Component { - return user.displayName.secondaryComponent() + return user.effectiveName.secondaryComponent() .showTextOnHover("Username: ".component() + user.username.primaryComponent().italic()) } diff --git a/src/main/kotlin/com/rose/gateway/shared/koin/InitializeKoin.kt b/src/main/kotlin/com/rose/gateway/shared/koin/InitializeKoin.kt index 7b69bde0..39a45bf0 100644 --- a/src/main/kotlin/com/rose/gateway/shared/koin/InitializeKoin.kt +++ b/src/main/kotlin/com/rose/gateway/shared/koin/InitializeKoin.kt @@ -3,7 +3,7 @@ package com.rose.gateway.shared.koin import com.rose.gateway.GatewayPlugin import com.rose.gateway.config.ConfigStringMap import com.rose.gateway.config.PluginConfig -import com.rose.gateway.discord.bot.DiscordBot +import com.rose.gateway.discord.bot.DiscordBotController import com.rose.gateway.shared.concurrency.PluginCoroutineScope import io.ktor.client.HttpClient import io.ktor.client.engine.cio.CIO @@ -22,7 +22,7 @@ fun initializeKoin(plugin: GatewayPlugin) { single { plugin } single { PluginConfig() } single { ConfigStringMap() } - single { DiscordBot() } + single { DiscordBotController() } single { HttpClient(CIO) } single { PluginCoroutineScope() } }, diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index eb711d7e..08b99b6a 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -6,7 +6,7 @@ description: Provides a configurable set of features that link the in-game chat website: https://github.com/nicholasgrose/Gateway main: com.rose.gateway.GatewayPlugin -api-version: 1.19 +api-version: 1.20 load: POSTWORLD commands: