diff --git a/pom.xml b/pom.xml
index 39b6bca..ebc9fe5 100644
--- a/pom.xml
+++ b/pom.xml
@@ -12,7 +12,7 @@
de.darkatra
v-rising-discord-bot
- 2.10.5
+ 2.11.0
jar
diff --git a/src/main/kotlin/de/darkatra/vrising/discord/Bot.kt b/src/main/kotlin/de/darkatra/vrising/discord/Bot.kt
index 7ea5846..842059c 100644
--- a/src/main/kotlin/de/darkatra/vrising/discord/Bot.kt
+++ b/src/main/kotlin/de/darkatra/vrising/discord/Bot.kt
@@ -8,8 +8,13 @@ import de.darkatra.vrising.discord.commands.Command
import de.darkatra.vrising.discord.migration.DatabaseMigrationService
import de.darkatra.vrising.discord.migration.Schema
import de.darkatra.vrising.discord.persistence.model.Error
-import de.darkatra.vrising.discord.persistence.model.ServerStatusMonitor
-import de.darkatra.vrising.discord.serverstatus.ServerStatusMonitorService
+import de.darkatra.vrising.discord.persistence.model.Leaderboard
+import de.darkatra.vrising.discord.persistence.model.PlayerActivityFeed
+import de.darkatra.vrising.discord.persistence.model.PvpKillFeed
+import de.darkatra.vrising.discord.persistence.model.Server
+import de.darkatra.vrising.discord.persistence.model.Status
+import de.darkatra.vrising.discord.persistence.model.StatusMonitor
+import de.darkatra.vrising.discord.serverstatus.ServerService
import dev.kord.core.Kord
import dev.kord.core.behavior.interaction.response.respond
import dev.kord.core.event.gateway.ReadyEvent
@@ -43,23 +48,31 @@ import java.util.concurrent.atomic.AtomicBoolean
@ImportRuntimeHints(BotRuntimeHints::class)
@EnableConfigurationProperties(BotProperties::class)
@RegisterReflectionForBinding(
+ // properties
BotProperties::class,
- Schema::class,
- ServerStatusMonitor::class,
+ // database
Error::class,
+ Leaderboard::class,
+ Schema::class,
+ PlayerActivityFeed::class,
+ PvpKillFeed::class,
+ Server::class,
+ Status::class,
+ StatusMonitor::class,
+ // http
Character::class,
- VBlood::class,
PlayerActivity::class,
PlayerActivity.Type::class,
PvpKill::class,
- PvpKill.Player::class
+ PvpKill.Player::class,
+ VBlood::class,
)
class Bot(
private val database: Nitrite,
private val botProperties: BotProperties,
private val commands: List,
private val databaseMigrationService: DatabaseMigrationService,
- private val serverStatusMonitorService: ServerStatusMonitorService
+ private val serverService: ServerService
) : ApplicationRunner, DisposableBean, SchedulingConfigurer {
private val logger = LoggerFactory.getLogger(javaClass)
@@ -80,8 +93,7 @@ class Bot(
val command = commands.find { command -> command.isSupported(interaction, botProperties.adminUserIds) }
if (command == null) {
interaction.deferEphemeralResponse().respond {
- content = """This command is not supported here, please refer to the documentation.
- |Be sure to use the commands in the channel where you want the status message to appear.""".trimMargin()
+ content = "This command is not supported here, please refer to the documentation."
}
return@on
}
@@ -127,7 +139,7 @@ class Bot(
{
if (isReady.get() && kord.isActive) {
runBlocking {
- serverStatusMonitorService.updateServerStatusMonitors(kord)
+ serverService.updateServers(kord)
}
}
},
@@ -142,7 +154,7 @@ class Bot(
{
if (isReady.get() && kord.isActive) {
runBlocking {
- serverStatusMonitorService.cleanupInactiveServerStatusMonitors(kord)
+ serverService.cleanupInactiveServers(kord)
}
}
},
diff --git a/src/main/kotlin/de/darkatra/vrising/discord/KordExtensions.kt b/src/main/kotlin/de/darkatra/vrising/discord/KordExtensions.kt
index 7d4d21c..fa8106f 100644
--- a/src/main/kotlin/de/darkatra/vrising/discord/KordExtensions.kt
+++ b/src/main/kotlin/de/darkatra/vrising/discord/KordExtensions.kt
@@ -1,11 +1,12 @@
package de.darkatra.vrising.discord
-import de.darkatra.vrising.discord.serverstatus.exceptions.InvalidDiscordChannelException
import dev.kord.common.entity.Snowflake
import dev.kord.core.Kord
import dev.kord.core.behavior.channel.MessageChannelBehavior
import dev.kord.core.entity.interaction.InteractionCommand
+private class InvalidDiscordChannelException(message: String, cause: Throwable? = null) : BotException(message, cause)
+
suspend fun Kord.getDiscordChannel(discordChannelId: String): Result {
val channel = try {
getChannel(Snowflake(discordChannelId))
@@ -25,3 +26,12 @@ fun InteractionCommand.getChannelIdFromStringParameter(parameterName: String): S
val match = channelPattern.find(value) ?: return value
return match.groups[1]!!.value
}
+
+suspend fun MessageChannelBehavior.tryCreateMessage(message: String): Boolean {
+ try {
+ createMessage(message)
+ return true
+ } catch (e: Exception) {
+ return false
+ }
+}
diff --git a/src/main/kotlin/de/darkatra/vrising/discord/commands/AddServerCommand.kt b/src/main/kotlin/de/darkatra/vrising/discord/commands/AddServerCommand.kt
index 6f42484..b30f0ad 100644
--- a/src/main/kotlin/de/darkatra/vrising/discord/commands/AddServerCommand.kt
+++ b/src/main/kotlin/de/darkatra/vrising/discord/commands/AddServerCommand.kt
@@ -4,31 +4,20 @@ import com.fasterxml.uuid.Generators
import de.darkatra.vrising.discord.BotProperties
import de.darkatra.vrising.discord.commands.parameters.ServerApiHostnameParameter
import de.darkatra.vrising.discord.commands.parameters.ServerHostnameParameter
-import de.darkatra.vrising.discord.commands.parameters.addDisplayPlayerGearLevelParameter
-import de.darkatra.vrising.discord.commands.parameters.addDisplayServerDescriptionParameter
-import de.darkatra.vrising.discord.commands.parameters.addEmbedEnabledParameter
-import de.darkatra.vrising.discord.commands.parameters.addPlayerActivityFeedChannelIdParameter
-import de.darkatra.vrising.discord.commands.parameters.addPvpKillFeedChannelIdParameter
import de.darkatra.vrising.discord.commands.parameters.addServerApiHostnameParameter
import de.darkatra.vrising.discord.commands.parameters.addServerApiPasswordParameter
import de.darkatra.vrising.discord.commands.parameters.addServerApiPortParameter
import de.darkatra.vrising.discord.commands.parameters.addServerApiUsernameParameter
import de.darkatra.vrising.discord.commands.parameters.addServerHostnameParameter
import de.darkatra.vrising.discord.commands.parameters.addServerQueryPortParameter
-import de.darkatra.vrising.discord.commands.parameters.getDisplayPlayerGearLevelParameter
-import de.darkatra.vrising.discord.commands.parameters.getDisplayServerDescriptionParameter
-import de.darkatra.vrising.discord.commands.parameters.getEmbedEnabledParameter
-import de.darkatra.vrising.discord.commands.parameters.getPlayerActivityFeedChannelIdParameter
-import de.darkatra.vrising.discord.commands.parameters.getPvpKillFeedChannelIdParameter
import de.darkatra.vrising.discord.commands.parameters.getServerApiHostnameParameter
import de.darkatra.vrising.discord.commands.parameters.getServerApiPasswordParameter
import de.darkatra.vrising.discord.commands.parameters.getServerApiPortParameter
import de.darkatra.vrising.discord.commands.parameters.getServerApiUsernameParameter
import de.darkatra.vrising.discord.commands.parameters.getServerHostnameParameter
import de.darkatra.vrising.discord.commands.parameters.getServerQueryPortParameter
-import de.darkatra.vrising.discord.persistence.ServerStatusMonitorRepository
-import de.darkatra.vrising.discord.persistence.model.ServerStatusMonitor
-import de.darkatra.vrising.discord.persistence.model.ServerStatusMonitorStatus
+import de.darkatra.vrising.discord.persistence.ServerRepository
+import de.darkatra.vrising.discord.persistence.model.Server
import dev.kord.core.Kord
import dev.kord.core.behavior.interaction.response.respond
import dev.kord.core.entity.interaction.ChatInputCommandInteraction
@@ -41,14 +30,14 @@ import org.springframework.stereotype.Component
@Component
@EnableConfigurationProperties(BotProperties::class)
class AddServerCommand(
- private val serverStatusMonitorRepository: ServerStatusMonitorRepository,
+ private val serverRepository: ServerRepository,
private val botProperties: BotProperties
) : Command {
private val logger = LoggerFactory.getLogger(javaClass)
private val name: String = "add-server"
- private val description: String = "Adds a server to the status monitor."
+ private val description: String = "Adds a server."
override fun getCommandName(): String = name
@@ -69,13 +58,6 @@ class AddServerCommand(
addServerApiPortParameter(required = false)
addServerApiUsernameParameter(required = false)
addServerApiPasswordParameter(required = false)
-
- addEmbedEnabledParameter(required = false)
- addDisplayServerDescriptionParameter(required = false)
- addDisplayPlayerGearLevelParameter(required = false)
-
- addPlayerActivityFeedChannelIdParameter(required = false)
- addPvpKillFeedChannelIdParameter(required = false)
}
}
@@ -96,45 +78,29 @@ class AddServerCommand(
val apiUsername = interaction.getServerApiUsernameParameter()
val apiPassword = interaction.getServerApiPasswordParameter()
- val embedEnabled = interaction.getEmbedEnabledParameter() ?: true
- val displayServerDescription = interaction.getDisplayServerDescriptionParameter() ?: true
- val displayPlayerGearLevel = interaction.getDisplayPlayerGearLevelParameter() ?: true
-
- val playerActivityChannelId = interaction.getPlayerActivityFeedChannelIdParameter()
- val pvpKillFeedChannelId = interaction.getPvpKillFeedChannelIdParameter()
-
val discordServerId = (interaction as GuildChatInputCommandInteraction).guildId
- val channelId = interaction.channelId
ServerHostnameParameter.validate(hostname, botProperties.allowLocalAddressRanges)
ServerApiHostnameParameter.validate(apiHostname, botProperties.allowLocalAddressRanges)
- val serverStatusMonitorId = Generators.timeBasedGenerator().generate()
- serverStatusMonitorRepository.addServerStatusMonitor(
- ServerStatusMonitor(
- id = serverStatusMonitorId.toString(),
+ val serverId = Generators.timeBasedGenerator().generate()
+ serverRepository.addServer(
+ Server(
+ id = serverId.toString(),
discordServerId = discordServerId.toString(),
- discordChannelId = channelId.toString(),
- playerActivityDiscordChannelId = playerActivityChannelId,
- pvpKillFeedDiscordChannelId = pvpKillFeedChannelId,
hostname = hostname,
queryPort = queryPort,
apiHostname = apiHostname,
apiPort = apiPort,
apiUsername = apiUsername,
apiPassword = apiPassword,
- status = ServerStatusMonitorStatus.ACTIVE,
- embedEnabled = embedEnabled,
- displayServerDescription = displayServerDescription,
- displayPlayerGearLevel = displayPlayerGearLevel,
)
)
- logger.info("Successfully added monitor with id '${serverStatusMonitorId}' for '${hostname}:${queryPort}' to channel '$channelId'.")
+ logger.info("Successfully added server '$serverId' for '$hostname:$queryPort' for discord server '$discordServerId'.")
interaction.deferEphemeralResponse().respond {
- content = """Added monitor with id '${serverStatusMonitorId}' for '${hostname}:${queryPort}' to channel '$channelId'.
- |It may take a minute for the status message to appear.""".trimMargin()
+ content = "Added server with id '$serverId' for '$hostname:$queryPort' for discord server '$discordServerId'."
}
}
}
diff --git a/src/main/kotlin/de/darkatra/vrising/discord/commands/ConfigurePlayerActivityFeedCommand.kt b/src/main/kotlin/de/darkatra/vrising/discord/commands/ConfigurePlayerActivityFeedCommand.kt
new file mode 100644
index 0000000..a076bb7
--- /dev/null
+++ b/src/main/kotlin/de/darkatra/vrising/discord/commands/ConfigurePlayerActivityFeedCommand.kt
@@ -0,0 +1,114 @@
+package de.darkatra.vrising.discord.commands
+
+import de.darkatra.vrising.discord.commands.parameters.ChannelIdParameter
+import de.darkatra.vrising.discord.commands.parameters.addChannelIdParameter
+import de.darkatra.vrising.discord.commands.parameters.addServerIdParameter
+import de.darkatra.vrising.discord.commands.parameters.addStatusParameter
+import de.darkatra.vrising.discord.commands.parameters.getChannelIdParameter
+import de.darkatra.vrising.discord.commands.parameters.getServerIdParameter
+import de.darkatra.vrising.discord.commands.parameters.getStatusParameter
+import de.darkatra.vrising.discord.persistence.ServerRepository
+import de.darkatra.vrising.discord.persistence.model.PlayerActivityFeed
+import de.darkatra.vrising.discord.persistence.model.Status
+import dev.kord.core.Kord
+import dev.kord.core.behavior.interaction.response.respond
+import dev.kord.core.entity.interaction.ChatInputCommandInteraction
+import dev.kord.core.entity.interaction.GlobalChatInputCommandInteraction
+import dev.kord.core.entity.interaction.GuildChatInputCommandInteraction
+import org.slf4j.LoggerFactory
+import org.springframework.stereotype.Component
+
+@Component
+class ConfigurePlayerActivityFeedCommand(
+ private val serverRepository: ServerRepository
+) : Command {
+
+ private val logger = LoggerFactory.getLogger(javaClass)
+
+ private val name: String = "configure-player-activity-feed"
+ private val description: String = "Configures the player activity feed for a given server."
+
+ override fun getCommandName(): String = name
+
+ override suspend fun register(kord: Kord) {
+
+ kord.createGlobalChatInputCommand(
+ name = name,
+ description = description
+ ) {
+
+ dmPermission = false
+ disableCommandInGuilds()
+
+ addServerIdParameter()
+
+ addChannelIdParameter(required = false)
+ addStatusParameter(required = false)
+ }
+ }
+
+ override fun isSupported(interaction: ChatInputCommandInteraction, adminUserIds: Set): Boolean {
+ if (interaction is GlobalChatInputCommandInteraction) {
+ return false
+ }
+ return super.isSupported(interaction, adminUserIds)
+ }
+
+ override suspend fun handle(interaction: ChatInputCommandInteraction) {
+
+ val serverId = interaction.getServerIdParameter()
+
+ val channelId = interaction.getChannelIdParameter()
+ val status = interaction.getStatusParameter()
+
+ val server = when (interaction) {
+ is GuildChatInputCommandInteraction -> serverRepository.getServer(serverId, interaction.guildId.toString())
+ is GlobalChatInputCommandInteraction -> serverRepository.getServer(serverId)
+ }
+
+ if (server == null) {
+ interaction.deferEphemeralResponse().respond {
+ content = "No server with id '$serverId' was found."
+ }
+ return
+ }
+
+ val playerActivityFeed = server.playerActivityFeed
+ if (playerActivityFeed == null) {
+
+ if (channelId == null) {
+ interaction.deferEphemeralResponse().respond {
+ content = "'${ChannelIdParameter.NAME}' is required when using this command for the first time."
+ }
+ return
+ }
+
+ server.playerActivityFeed = PlayerActivityFeed(
+ status = status ?: Status.ACTIVE,
+ discordChannelId = channelId
+ )
+
+ logger.info("Successfully configured the player activity feed for server '$serverId'.")
+
+ interaction.deferEphemeralResponse().respond {
+ content = "Successfully configured the player activity feed for server with id '$serverId'."
+ }
+ return
+ }
+
+ if (channelId != null) {
+ playerActivityFeed.discordChannelId = channelId
+ }
+ if (status != null) {
+ playerActivityFeed.status = status
+ }
+
+ serverRepository.updateServer(server)
+
+ logger.info("Successfully updated the player activity feed for server '$serverId'.")
+
+ interaction.deferEphemeralResponse().respond {
+ content = "Successfully updated the player activity feed for server with id '$serverId'."
+ }
+ }
+}
diff --git a/src/main/kotlin/de/darkatra/vrising/discord/commands/ConfigurePvpKillFeedCommand.kt b/src/main/kotlin/de/darkatra/vrising/discord/commands/ConfigurePvpKillFeedCommand.kt
new file mode 100644
index 0000000..862c0f8
--- /dev/null
+++ b/src/main/kotlin/de/darkatra/vrising/discord/commands/ConfigurePvpKillFeedCommand.kt
@@ -0,0 +1,114 @@
+package de.darkatra.vrising.discord.commands
+
+import de.darkatra.vrising.discord.commands.parameters.ChannelIdParameter
+import de.darkatra.vrising.discord.commands.parameters.addChannelIdParameter
+import de.darkatra.vrising.discord.commands.parameters.addServerIdParameter
+import de.darkatra.vrising.discord.commands.parameters.addStatusParameter
+import de.darkatra.vrising.discord.commands.parameters.getChannelIdParameter
+import de.darkatra.vrising.discord.commands.parameters.getServerIdParameter
+import de.darkatra.vrising.discord.commands.parameters.getStatusParameter
+import de.darkatra.vrising.discord.persistence.ServerRepository
+import de.darkatra.vrising.discord.persistence.model.PvpKillFeed
+import de.darkatra.vrising.discord.persistence.model.Status
+import dev.kord.core.Kord
+import dev.kord.core.behavior.interaction.response.respond
+import dev.kord.core.entity.interaction.ChatInputCommandInteraction
+import dev.kord.core.entity.interaction.GlobalChatInputCommandInteraction
+import dev.kord.core.entity.interaction.GuildChatInputCommandInteraction
+import org.slf4j.LoggerFactory
+import org.springframework.stereotype.Component
+
+@Component
+class ConfigurePvpKillFeedCommand(
+ private val serverRepository: ServerRepository
+) : Command {
+
+ private val logger = LoggerFactory.getLogger(javaClass)
+
+ private val name: String = "configure-pvp-kill-feed"
+ private val description: String = "Configures the pvp kill feed for a given server."
+
+ override fun getCommandName(): String = name
+
+ override suspend fun register(kord: Kord) {
+
+ kord.createGlobalChatInputCommand(
+ name = name,
+ description = description
+ ) {
+
+ dmPermission = false
+ disableCommandInGuilds()
+
+ addServerIdParameter()
+
+ addChannelIdParameter(required = false)
+ addStatusParameter(required = false)
+ }
+ }
+
+ override fun isSupported(interaction: ChatInputCommandInteraction, adminUserIds: Set): Boolean {
+ if (interaction is GlobalChatInputCommandInteraction) {
+ return false
+ }
+ return super.isSupported(interaction, adminUserIds)
+ }
+
+ override suspend fun handle(interaction: ChatInputCommandInteraction) {
+
+ val serverId = interaction.getServerIdParameter()
+
+ val channelId = interaction.getChannelIdParameter()
+ val status = interaction.getStatusParameter()
+
+ val server = when (interaction) {
+ is GuildChatInputCommandInteraction -> serverRepository.getServer(serverId, interaction.guildId.toString())
+ is GlobalChatInputCommandInteraction -> serverRepository.getServer(serverId)
+ }
+
+ if (server == null) {
+ interaction.deferEphemeralResponse().respond {
+ content = "No server with id '$serverId' was found."
+ }
+ return
+ }
+
+ val pvpKillFeed = server.pvpKillFeed
+ if (pvpKillFeed == null) {
+
+ if (channelId == null) {
+ interaction.deferEphemeralResponse().respond {
+ content = "'${ChannelIdParameter.NAME}' is required when using this command for the first time."
+ }
+ return
+ }
+
+ server.pvpKillFeed = PvpKillFeed(
+ status = status ?: Status.ACTIVE,
+ discordChannelId = channelId
+ )
+
+ logger.info("Successfully configured the pvp kill feed for server '$serverId'.")
+
+ interaction.deferEphemeralResponse().respond {
+ content = "Successfully configured the pvp kill feed for server with id '$serverId'."
+ }
+ return
+ }
+
+ if (channelId != null) {
+ pvpKillFeed.discordChannelId = channelId
+ }
+ if (status != null) {
+ pvpKillFeed.status = status
+ }
+
+ serverRepository.updateServer(server)
+
+ logger.info("Successfully updated the pvp kill feed for server '$serverId'.")
+
+ interaction.deferEphemeralResponse().respond {
+ content = "Successfully updated the pvp kill feed for server with id '$serverId'."
+ }
+ }
+}
diff --git a/src/main/kotlin/de/darkatra/vrising/discord/commands/ConfigureStatusMonitorCommand.kt b/src/main/kotlin/de/darkatra/vrising/discord/commands/ConfigureStatusMonitorCommand.kt
new file mode 100644
index 0000000..c2079fe
--- /dev/null
+++ b/src/main/kotlin/de/darkatra/vrising/discord/commands/ConfigureStatusMonitorCommand.kt
@@ -0,0 +1,134 @@
+package de.darkatra.vrising.discord.commands
+
+import de.darkatra.vrising.discord.commands.parameters.ChannelIdParameter
+import de.darkatra.vrising.discord.commands.parameters.addChannelIdParameter
+import de.darkatra.vrising.discord.commands.parameters.addDisplayPlayerGearLevelParameter
+import de.darkatra.vrising.discord.commands.parameters.addDisplayServerDescriptionParameter
+import de.darkatra.vrising.discord.commands.parameters.addServerIdParameter
+import de.darkatra.vrising.discord.commands.parameters.addStatusParameter
+import de.darkatra.vrising.discord.commands.parameters.getChannelIdParameter
+import de.darkatra.vrising.discord.commands.parameters.getDisplayPlayerGearLevelParameter
+import de.darkatra.vrising.discord.commands.parameters.getDisplayServerDescriptionParameter
+import de.darkatra.vrising.discord.commands.parameters.getServerIdParameter
+import de.darkatra.vrising.discord.commands.parameters.getStatusParameter
+import de.darkatra.vrising.discord.persistence.ServerRepository
+import de.darkatra.vrising.discord.persistence.model.Status
+import de.darkatra.vrising.discord.persistence.model.StatusMonitor
+import dev.kord.core.Kord
+import dev.kord.core.behavior.interaction.response.respond
+import dev.kord.core.entity.interaction.ChatInputCommandInteraction
+import dev.kord.core.entity.interaction.GlobalChatInputCommandInteraction
+import dev.kord.core.entity.interaction.GuildChatInputCommandInteraction
+import org.slf4j.LoggerFactory
+import org.springframework.stereotype.Component
+
+@Component
+class ConfigureStatusMonitorCommand(
+ private val serverRepository: ServerRepository
+) : Command {
+
+ private val logger = LoggerFactory.getLogger(javaClass)
+
+ private val name: String = "configure-status-monitor"
+ private val description: String = "Configures the status monitor for a given server."
+
+ override fun getCommandName(): String = name
+
+ override suspend fun register(kord: Kord) {
+
+ kord.createGlobalChatInputCommand(
+ name = name,
+ description = description
+ ) {
+
+ dmPermission = false
+ disableCommandInGuilds()
+
+ addServerIdParameter()
+
+ addChannelIdParameter(required = false)
+ addStatusParameter(required = false)
+
+ addDisplayServerDescriptionParameter(required = false)
+ addDisplayPlayerGearLevelParameter(required = false)
+ }
+ }
+
+ override fun isSupported(interaction: ChatInputCommandInteraction, adminUserIds: Set): Boolean {
+ if (interaction is GlobalChatInputCommandInteraction) {
+ return false
+ }
+ return super.isSupported(interaction, adminUserIds)
+ }
+
+ override suspend fun handle(interaction: ChatInputCommandInteraction) {
+
+ val serverId = interaction.getServerIdParameter()
+
+ val channelId = interaction.getChannelIdParameter()
+ val status = interaction.getStatusParameter()
+
+ val displayServerDescription = interaction.getDisplayServerDescriptionParameter()
+ val displayPlayerGearLevel = interaction.getDisplayPlayerGearLevelParameter()
+
+ val server = when (interaction) {
+ is GuildChatInputCommandInteraction -> serverRepository.getServer(serverId, interaction.guildId.toString())
+ is GlobalChatInputCommandInteraction -> serverRepository.getServer(serverId)
+ }
+
+ if (server == null) {
+ interaction.deferEphemeralResponse().respond {
+ content = "No server with id '$serverId' was found."
+ }
+ return
+ }
+
+ val statusMonitor = server.statusMonitor
+ if (statusMonitor == null) {
+
+ if (channelId == null) {
+ interaction.deferEphemeralResponse().respond {
+ content = "'${ChannelIdParameter.NAME}' is required when using this command for the first time."
+ }
+ return
+ }
+
+ server.statusMonitor = StatusMonitor(
+ status = status ?: Status.ACTIVE,
+ discordChannelId = channelId,
+ displayServerDescription = displayServerDescription ?: true,
+ displayPlayerGearLevel = displayPlayerGearLevel ?: true
+ )
+
+ logger.info("Successfully configured the status monitor for server '$serverId'.")
+
+ interaction.deferEphemeralResponse().respond {
+ content = """Successfully configured the status monitor for server with id '$serverId'.
+ |It may take a few minutes for the status embed to appear.""".trimMargin()
+ }
+ return
+ }
+
+ if (channelId != null) {
+ statusMonitor.discordChannelId = channelId
+ }
+ if (status != null) {
+ statusMonitor.status = status
+ }
+ if (displayServerDescription != null) {
+ statusMonitor.displayServerDescription = displayServerDescription
+ }
+ if (displayPlayerGearLevel != null) {
+ statusMonitor.displayPlayerGearLevel = displayPlayerGearLevel
+ }
+
+ serverRepository.updateServer(server)
+
+ logger.info("Successfully updated the status monitor for server '$serverId'.")
+
+ interaction.deferEphemeralResponse().respond {
+ content = """Successfully updated the status monitor for server with id '$serverId'.
+ |It may take a few minutes for the status embed to reflect the changes.""".trimMargin()
+ }
+ }
+}
diff --git a/src/main/kotlin/de/darkatra/vrising/discord/commands/ErrorHelper.kt b/src/main/kotlin/de/darkatra/vrising/discord/commands/ErrorHelper.kt
new file mode 100644
index 0000000..7ba6a87
--- /dev/null
+++ b/src/main/kotlin/de/darkatra/vrising/discord/commands/ErrorHelper.kt
@@ -0,0 +1,19 @@
+package de.darkatra.vrising.discord.commands
+
+import de.darkatra.vrising.discord.persistence.model.ErrorAware
+import dev.kord.rest.builder.message.EmbedBuilder
+import org.springframework.util.StringUtils
+
+fun EmbedBuilder.renderRecentErrors(errorAware: ErrorAware, maxCharactersPerError: Int) {
+ val recentErrors = errorAware.recentErrors
+ if (recentErrors.isNotEmpty()) {
+ recentErrors.chunked(5).forEachIndexed { i, chunk ->
+ field {
+ name = "Errors - Page ${i + 1}"
+ value = chunk.joinToString("\n") {
+ "```${StringUtils.truncate(it.message, maxCharactersPerError)}```"
+ }
+ }
+ }
+ }
+}
diff --git a/src/main/kotlin/de/darkatra/vrising/discord/commands/GetPlayerActivityFeedDetailsCommand.kt b/src/main/kotlin/de/darkatra/vrising/discord/commands/GetPlayerActivityFeedDetailsCommand.kt
new file mode 100644
index 0000000..21a4636
--- /dev/null
+++ b/src/main/kotlin/de/darkatra/vrising/discord/commands/GetPlayerActivityFeedDetailsCommand.kt
@@ -0,0 +1,89 @@
+package de.darkatra.vrising.discord.commands
+
+import de.darkatra.vrising.discord.BotProperties
+import de.darkatra.vrising.discord.commands.parameters.addServerIdParameter
+import de.darkatra.vrising.discord.persistence.ServerRepository
+import dev.kord.core.Kord
+import dev.kord.core.behavior.interaction.response.respond
+import dev.kord.core.entity.interaction.ChatInputCommandInteraction
+import dev.kord.rest.builder.message.embed
+import org.springframework.boot.context.properties.EnableConfigurationProperties
+import org.springframework.stereotype.Component
+
+@Component
+@EnableConfigurationProperties(BotProperties::class)
+class GetPlayerActivityFeedDetailsCommand(
+ private val serverRepository: ServerRepository,
+ private val botProperties: BotProperties
+) : Command {
+
+ private val name: String = "get-player-activity-feed-details"
+ private val description: String = "Gets all details of the player activity feed for the specified server."
+
+ override fun getCommandName(): String = name
+
+ override suspend fun register(kord: Kord) {
+
+ kord.createGlobalChatInputCommand(
+ name = name,
+ description = description
+ ) {
+ dmPermission = true
+ disableCommandInGuilds()
+
+ addServerIdParameter()
+ }
+ }
+
+ override suspend fun handle(interaction: ChatInputCommandInteraction) {
+
+ val server = interaction.getServer(serverRepository)
+ ?: return
+
+ val playerActivityFeed = server.playerActivityFeed
+ if (playerActivityFeed == null) {
+ interaction.deferEphemeralResponse().respond {
+ content = "No player activity feed is configured for server with id '${server.id}'."
+ }
+ return
+ }
+
+ interaction.deferEphemeralResponse().respond {
+ embed {
+ title = "Player Activity Feed Details for ${server.id}"
+
+ field {
+ name = "Status"
+ value = playerActivityFeed.status.name
+ inline = true
+ }
+
+ field {
+ name = "Discord Server Id"
+ value = server.discordServerId
+ inline = true
+ }
+
+ field {
+ name = "Discord Channel Id"
+ value = playerActivityFeed.discordChannelId
+ inline = true
+ }
+
+ field {
+ name = "Current Failed Attempts"
+ value = "${playerActivityFeed.currentFailedAttempts}"
+ inline = true
+ }
+
+ field {
+ name = "Last Updated"
+ value = ""
+ inline = true
+ }
+
+ renderRecentErrors(playerActivityFeed, botProperties.maxCharactersPerError)
+ }
+ }
+ }
+}
diff --git a/src/main/kotlin/de/darkatra/vrising/discord/commands/GetPvpKillFeedDetailsCommand.kt b/src/main/kotlin/de/darkatra/vrising/discord/commands/GetPvpKillFeedDetailsCommand.kt
new file mode 100644
index 0000000..b42c1ff
--- /dev/null
+++ b/src/main/kotlin/de/darkatra/vrising/discord/commands/GetPvpKillFeedDetailsCommand.kt
@@ -0,0 +1,89 @@
+package de.darkatra.vrising.discord.commands
+
+import de.darkatra.vrising.discord.BotProperties
+import de.darkatra.vrising.discord.commands.parameters.addServerIdParameter
+import de.darkatra.vrising.discord.persistence.ServerRepository
+import dev.kord.core.Kord
+import dev.kord.core.behavior.interaction.response.respond
+import dev.kord.core.entity.interaction.ChatInputCommandInteraction
+import dev.kord.rest.builder.message.embed
+import org.springframework.boot.context.properties.EnableConfigurationProperties
+import org.springframework.stereotype.Component
+
+@Component
+@EnableConfigurationProperties(BotProperties::class)
+class GetPvpKillFeedDetailsCommand(
+ private val serverRepository: ServerRepository,
+ private val botProperties: BotProperties
+) : Command {
+
+ private val name: String = "get-pvp-kill-feed-details"
+ private val description: String = "Gets all details of the pvp kill feed for the specified server."
+
+ override fun getCommandName(): String = name
+
+ override suspend fun register(kord: Kord) {
+
+ kord.createGlobalChatInputCommand(
+ name = name,
+ description = description
+ ) {
+ dmPermission = true
+ disableCommandInGuilds()
+
+ addServerIdParameter()
+ }
+ }
+
+ override suspend fun handle(interaction: ChatInputCommandInteraction) {
+
+ val server = interaction.getServer(serverRepository)
+ ?: return
+
+ val pvpKillFeed = server.pvpKillFeed
+ if (pvpKillFeed == null) {
+ interaction.deferEphemeralResponse().respond {
+ content = "No pvp kill feed is configured for server with id '${server.id}'."
+ }
+ return
+ }
+
+ interaction.deferEphemeralResponse().respond {
+ embed {
+ title = "Pvp Kill Feed Details for ${server.id}"
+
+ field {
+ name = "Status"
+ value = pvpKillFeed.status.name
+ inline = true
+ }
+
+ field {
+ name = "Discord Server Id"
+ value = server.discordServerId
+ inline = true
+ }
+
+ field {
+ name = "Discord Channel Id"
+ value = pvpKillFeed.discordChannelId
+ inline = true
+ }
+
+ field {
+ name = "Current Failed Attempts"
+ value = "${pvpKillFeed.currentFailedAttempts}"
+ inline = true
+ }
+
+ field {
+ name = "Last Updated"
+ value = ""
+ inline = true
+ }
+
+ renderRecentErrors(pvpKillFeed, botProperties.maxCharactersPerError)
+ }
+ }
+ }
+}
diff --git a/src/main/kotlin/de/darkatra/vrising/discord/commands/GetServerDetailsCommand.kt b/src/main/kotlin/de/darkatra/vrising/discord/commands/GetServerDetailsCommand.kt
index 13f15c7..3daa6e5 100644
--- a/src/main/kotlin/de/darkatra/vrising/discord/commands/GetServerDetailsCommand.kt
+++ b/src/main/kotlin/de/darkatra/vrising/discord/commands/GetServerDetailsCommand.kt
@@ -1,24 +1,18 @@
package de.darkatra.vrising.discord.commands
-import de.darkatra.vrising.discord.BotProperties
-import de.darkatra.vrising.discord.commands.parameters.addServerStatusMonitorIdParameter
-import de.darkatra.vrising.discord.commands.parameters.getServerStatusMonitorIdParameter
-import de.darkatra.vrising.discord.persistence.ServerStatusMonitorRepository
+import de.darkatra.vrising.discord.commands.parameters.addServerIdParameter
+import de.darkatra.vrising.discord.persistence.ServerRepository
import dev.kord.core.Kord
import dev.kord.core.behavior.interaction.response.respond
import dev.kord.core.entity.interaction.ChatInputCommandInteraction
-import dev.kord.core.entity.interaction.GlobalChatInputCommandInteraction
-import dev.kord.core.entity.interaction.GuildChatInputCommandInteraction
import dev.kord.rest.builder.message.embed
-import org.springframework.boot.context.properties.EnableConfigurationProperties
import org.springframework.stereotype.Component
-import org.springframework.util.StringUtils
+import java.util.Objects
+import java.util.stream.Stream
@Component
-@EnableConfigurationProperties(BotProperties::class)
class GetServerDetailsCommand(
- private val serverStatusMonitorRepository: ServerStatusMonitorRepository,
- private val botProperties: BotProperties
+ private val serverRepository: ServerRepository
) : Command {
private val name: String = "get-server-details"
@@ -35,52 +29,41 @@ class GetServerDetailsCommand(
dmPermission = true
disableCommandInGuilds()
- addServerStatusMonitorIdParameter()
+ addServerIdParameter()
}
}
override suspend fun handle(interaction: ChatInputCommandInteraction) {
- val serverStatusMonitorId = interaction.getServerStatusMonitorIdParameter()
-
- val serverStatusMonitor = when (interaction) {
- is GuildChatInputCommandInteraction -> serverStatusMonitorRepository.getServerStatusMonitor(serverStatusMonitorId, interaction.guildId.toString())
- is GlobalChatInputCommandInteraction -> serverStatusMonitorRepository.getServerStatusMonitor(serverStatusMonitorId)
- }
-
- if (serverStatusMonitor == null) {
- interaction.deferEphemeralResponse().respond {
- content = "No server with id '$serverStatusMonitorId' was found."
- }
- return
- }
+ val server = interaction.getServer(serverRepository)
+ ?: return
interaction.deferEphemeralResponse().respond {
embed {
- title = "Details for ${serverStatusMonitor.id}"
+ title = "Details for ${server.id}"
field {
name = "Hostname"
- value = serverStatusMonitor.hostname
+ value = server.hostname
inline = true
}
field {
name = "Query Port"
- value = "${serverStatusMonitor.queryPort}"
+ value = "${server.queryPort}"
inline = true
}
field {
- name = "Status"
- value = serverStatusMonitor.status.name
+ name = "Discord Server Id"
+ value = server.discordServerId
inline = true
}
field {
name = "Api Hostname"
- value = when (serverStatusMonitor.apiHostname != null) {
- true -> "${serverStatusMonitor.apiHostname}"
+ value = when (server.apiHostname != null) {
+ true -> "${server.apiHostname}"
false -> "-"
}
inline = true
@@ -88,89 +71,42 @@ class GetServerDetailsCommand(
field {
name = "Api Port"
- value = when (serverStatusMonitor.apiPort != null) {
- true -> "${serverStatusMonitor.apiPort}"
+ value = when (server.apiPort != null) {
+ true -> "${server.apiPort}"
false -> "-"
}
inline = true
}
field {
- name = "Embed Enabled"
- value = "${serverStatusMonitor.embedEnabled}"
- inline = true
- }
-
- field {
- name = "Display Server Description"
- value = "${serverStatusMonitor.displayServerDescription}"
- inline = true
- }
-
- field {
- name = "Display Player Gear Level"
- value = "${serverStatusMonitor.displayPlayerGearLevel}"
- inline = true
- }
-
- field {
- name = "Discord Server Id"
- value = serverStatusMonitor.discordServerId
- inline = true
- }
-
- field {
- name = "Discord Channel Id"
- value = serverStatusMonitor.discordChannelId
- inline = true
- }
-
- field {
- name = "Player Activity Feed Channel Id"
- value = serverStatusMonitor.playerActivityDiscordChannelId ?: "-"
+ name = "Last Update Attempt"
+ value = ""
inline = true
}
field {
- name = "Pvp Kill Feed Channel Id"
- value = serverStatusMonitor.pvpKillFeedDiscordChannelId ?: "-"
+ name = "Status Monitor Status"
+ value = server.statusMonitor?.status?.name ?: "-"
inline = true
}
field {
- name = "Current Embed Message Id"
- value = serverStatusMonitor.currentEmbedMessageId ?: "-"
+ name = "Player Activity Feed Status"
+ value = server.playerActivityFeed?.status?.name ?: "-"
inline = true
}
field {
- name = "Current Failed Attempts"
- value = "${serverStatusMonitor.currentFailedAttempts}"
+ name = "Pvp Kill Feed Status"
+ value = server.pvpKillFeed?.status?.name ?: "-"
inline = true
}
field {
- name = "Current Failed Api Attempts"
- value = "${serverStatusMonitor.currentFailedApiAttempts}"
+ name = "Number of Leaderboards"
+ value = "${Stream.of(server.pvpKillFeed).filter(Objects::nonNull).count()}"
inline = true
}
-
- field {
- name = "Last Update Attempt"
- value = ""
- inline = true
- }
-
- if (serverStatusMonitor.recentErrors.isNotEmpty()) {
- serverStatusMonitor.recentErrors.chunked(5).forEachIndexed { i, chunk ->
- field {
- name = "Most recent Errors $i"
- value = chunk.joinToString("\n") {
- "```${StringUtils.truncate(it.message, botProperties.maxCharactersPerError)}```"
- }
- }
- }
- }
}
}
}
diff --git a/src/main/kotlin/de/darkatra/vrising/discord/commands/GetStatusMonitorDetailsCommand.kt b/src/main/kotlin/de/darkatra/vrising/discord/commands/GetStatusMonitorDetailsCommand.kt
new file mode 100644
index 0000000..c6b04ed
--- /dev/null
+++ b/src/main/kotlin/de/darkatra/vrising/discord/commands/GetStatusMonitorDetailsCommand.kt
@@ -0,0 +1,113 @@
+package de.darkatra.vrising.discord.commands
+
+import de.darkatra.vrising.discord.BotProperties
+import de.darkatra.vrising.discord.commands.parameters.addServerIdParameter
+import de.darkatra.vrising.discord.persistence.ServerRepository
+import dev.kord.core.Kord
+import dev.kord.core.behavior.interaction.response.respond
+import dev.kord.core.entity.interaction.ChatInputCommandInteraction
+import dev.kord.rest.builder.message.embed
+import org.springframework.boot.context.properties.EnableConfigurationProperties
+import org.springframework.stereotype.Component
+
+@Component
+@EnableConfigurationProperties(BotProperties::class)
+class GetStatusMonitorDetailsCommand(
+ private val serverRepository: ServerRepository,
+ private val botProperties: BotProperties
+) : Command {
+
+ private val name: String = "get-status-monitor-details"
+ private val description: String = "Gets all details of the status monitor for the specified server."
+
+ override fun getCommandName(): String = name
+
+ override suspend fun register(kord: Kord) {
+
+ kord.createGlobalChatInputCommand(
+ name = name,
+ description = description
+ ) {
+ dmPermission = true
+ disableCommandInGuilds()
+
+ addServerIdParameter()
+ }
+ }
+
+ override suspend fun handle(interaction: ChatInputCommandInteraction) {
+
+ val server = interaction.getServer(serverRepository)
+ ?: return
+
+ val statusMonitor = server.statusMonitor
+ if (statusMonitor == null) {
+ interaction.deferEphemeralResponse().respond {
+ content = "No status monitor is configured for server with id '${server.id}'."
+ }
+ return
+ }
+
+ interaction.deferEphemeralResponse().respond {
+ embed {
+ title = "Status Monitor Details for ${server.id}"
+
+ field {
+ name = "Status"
+ value = statusMonitor.status.name
+ inline = true
+ }
+
+ field {
+ name = "Discord Server Id"
+ value = server.discordServerId
+ inline = true
+ }
+
+ field {
+ name = "Discord Channel Id"
+ value = statusMonitor.discordChannelId
+ inline = true
+ }
+
+ field {
+ name = "Display Server Description"
+ value = "${statusMonitor.displayServerDescription}"
+ inline = true
+ }
+
+ field {
+ name = "Display Player Gear Level"
+ value = "${statusMonitor.displayPlayerGearLevel}"
+ inline = true
+ }
+
+ field {
+ name = "Current Embed Message Id"
+ value = statusMonitor.currentEmbedMessageId ?: "-"
+ inline = true
+ }
+
+ field {
+ name = "Current Failed Attempts"
+ value = "${statusMonitor.currentFailedAttempts}"
+ inline = true
+ }
+
+ field {
+ name = "Current Failed Api Attempts"
+ value = "${statusMonitor.currentFailedApiAttempts}"
+ inline = true
+ }
+
+ field {
+ name = "Last Updated"
+ value = ""
+ inline = true
+ }
+
+ renderRecentErrors(statusMonitor, botProperties.maxCharactersPerError)
+ }
+ }
+ }
+}
diff --git a/src/main/kotlin/de/darkatra/vrising/discord/commands/ListServersCommand.kt b/src/main/kotlin/de/darkatra/vrising/discord/commands/ListServersCommand.kt
index 675ee5e..c927e89 100644
--- a/src/main/kotlin/de/darkatra/vrising/discord/commands/ListServersCommand.kt
+++ b/src/main/kotlin/de/darkatra/vrising/discord/commands/ListServersCommand.kt
@@ -4,8 +4,8 @@ import de.darkatra.vrising.discord.BotProperties
import de.darkatra.vrising.discord.commands.parameters.PageParameter
import de.darkatra.vrising.discord.commands.parameters.addPageParameter
import de.darkatra.vrising.discord.commands.parameters.getPageParameter
-import de.darkatra.vrising.discord.persistence.ServerStatusMonitorRepository
-import de.darkatra.vrising.discord.persistence.model.ServerStatusMonitor
+import de.darkatra.vrising.discord.persistence.ServerRepository
+import de.darkatra.vrising.discord.persistence.model.Server
import dev.kord.core.Kord
import dev.kord.core.behavior.interaction.response.respond
import dev.kord.core.entity.interaction.ChatInputCommandInteraction
@@ -19,11 +19,11 @@ private const val PAGE_SIZE = 10
@Component
@EnableConfigurationProperties(BotProperties::class)
class ListServersCommand(
- private val serverStatusMonitorRepository: ServerStatusMonitorRepository
+ private val serverRepository: ServerRepository
) : Command {
private val name: String = "list-servers"
- private val description: String = "Lists all server status monitors."
+ private val description: String = "Lists all servers."
override fun getCommandName(): String = name
@@ -46,8 +46,8 @@ class ListServersCommand(
PageParameter.validate(page)
val totalElements = when (interaction) {
- is GuildChatInputCommandInteraction -> serverStatusMonitorRepository.count(interaction.guildId.toString())
- is GlobalChatInputCommandInteraction -> serverStatusMonitorRepository.count()
+ is GuildChatInputCommandInteraction -> serverRepository.count(interaction.guildId.toString())
+ is GlobalChatInputCommandInteraction -> serverRepository.count()
}
val totalPages = totalElements / PAGE_SIZE + 1
@@ -58,24 +58,24 @@ class ListServersCommand(
return
}
- val serverStatusMonitors: List = when (interaction) {
- is GuildChatInputCommandInteraction -> serverStatusMonitorRepository.getServerStatusMonitors(
+ val servers: List = when (interaction) {
+ is GuildChatInputCommandInteraction -> serverRepository.getServers(
discordServerId = interaction.guildId.toString(),
offset = page * PAGE_SIZE,
limit = PAGE_SIZE
)
- is GlobalChatInputCommandInteraction -> serverStatusMonitorRepository.getServerStatusMonitors(
+ is GlobalChatInputCommandInteraction -> serverRepository.getServers(
offset = page * PAGE_SIZE,
limit = PAGE_SIZE
)
}
interaction.deferEphemeralResponse().respond {
- content = when (serverStatusMonitors.isEmpty()) {
+ content = when (servers.isEmpty()) {
true -> "No servers found."
- false -> serverStatusMonitors.joinToString(separator = "\n") { serverStatusMonitor ->
- "${serverStatusMonitor.id} - ${serverStatusMonitor.hostname}:${serverStatusMonitor.queryPort} - ${serverStatusMonitor.status.name}"
+ false -> servers.joinToString(separator = "\n") { server ->
+ "${server.id} - ${server.hostname}:${server.queryPort} - ${server.status.name}"
} + "\n*Current Page: $page, Total Pages: $totalPages*"
}
}
diff --git a/src/main/kotlin/de/darkatra/vrising/discord/commands/RemoveServerCommand.kt b/src/main/kotlin/de/darkatra/vrising/discord/commands/RemoveServerCommand.kt
index cbfa77c..7677d9b 100644
--- a/src/main/kotlin/de/darkatra/vrising/discord/commands/RemoveServerCommand.kt
+++ b/src/main/kotlin/de/darkatra/vrising/discord/commands/RemoveServerCommand.kt
@@ -1,8 +1,8 @@
package de.darkatra.vrising.discord.commands
-import de.darkatra.vrising.discord.commands.parameters.addServerStatusMonitorIdParameter
-import de.darkatra.vrising.discord.commands.parameters.getServerStatusMonitorIdParameter
-import de.darkatra.vrising.discord.persistence.ServerStatusMonitorRepository
+import de.darkatra.vrising.discord.commands.parameters.addServerIdParameter
+import de.darkatra.vrising.discord.commands.parameters.getServerIdParameter
+import de.darkatra.vrising.discord.persistence.ServerRepository
import dev.kord.core.Kord
import dev.kord.core.behavior.interaction.response.respond
import dev.kord.core.entity.interaction.ChatInputCommandInteraction
@@ -13,13 +13,13 @@ import org.springframework.stereotype.Component
@Component
class RemoveServerCommand(
- private val serverStatusMonitorRepository: ServerStatusMonitorRepository
+ private val serverRepository: ServerRepository
) : Command {
private val logger = LoggerFactory.getLogger(javaClass)
private val name: String = "remove-server"
- private val description: String = "Removes a server from the status monitor."
+ private val description: String = "Removes a server."
override fun getCommandName(): String = name
@@ -32,29 +32,27 @@ class RemoveServerCommand(
dmPermission = true
disableCommandInGuilds()
- addServerStatusMonitorIdParameter()
+ addServerIdParameter()
}
}
override suspend fun handle(interaction: ChatInputCommandInteraction) {
- val serverStatusMonitorId = interaction.getServerStatusMonitorIdParameter()
+ val serverId = interaction.getServerIdParameter()
val wasSuccessful = when (interaction) {
- is GuildChatInputCommandInteraction -> serverStatusMonitorRepository.removeServerStatusMonitor(
- serverStatusMonitorId,
- interaction.guildId.toString()
- )
-
- is GlobalChatInputCommandInteraction -> serverStatusMonitorRepository.removeServerStatusMonitor(serverStatusMonitorId)
+ is GuildChatInputCommandInteraction -> serverRepository.removeServer(serverId, interaction.guildId.toString())
+ is GlobalChatInputCommandInteraction -> serverRepository.removeServer(serverId)
}
- logger.info("Successfully removed monitor with id '${serverStatusMonitorId}'.")
+ if (wasSuccessful) {
+ logger.info("Successfully removed server with id '$serverId'.")
+ }
interaction.deferEphemeralResponse().respond {
content = when (wasSuccessful) {
- true -> "Removed monitor with id '$serverStatusMonitorId'."
- false -> "No server with id '$serverStatusMonitorId' was found."
+ true -> "Removed server with id '$serverId'."
+ false -> "No server with id '$serverId' was found."
}
}
}
diff --git a/src/main/kotlin/de/darkatra/vrising/discord/commands/ServerHelper.kt b/src/main/kotlin/de/darkatra/vrising/discord/commands/ServerHelper.kt
new file mode 100644
index 0000000..d81aae4
--- /dev/null
+++ b/src/main/kotlin/de/darkatra/vrising/discord/commands/ServerHelper.kt
@@ -0,0 +1,28 @@
+package de.darkatra.vrising.discord.commands
+
+import de.darkatra.vrising.discord.commands.parameters.getServerIdParameter
+import de.darkatra.vrising.discord.persistence.ServerRepository
+import de.darkatra.vrising.discord.persistence.model.Server
+import dev.kord.core.behavior.interaction.response.respond
+import dev.kord.core.entity.interaction.ChatInputCommandInteraction
+import dev.kord.core.entity.interaction.GlobalChatInputCommandInteraction
+import dev.kord.core.entity.interaction.GuildChatInputCommandInteraction
+
+suspend fun ChatInputCommandInteraction.getServer(serverRepository: ServerRepository): Server? {
+
+ val serverId = getServerIdParameter()
+
+ val server = when (this) {
+ is GuildChatInputCommandInteraction -> serverRepository.getServer(serverId, guildId.toString())
+ is GlobalChatInputCommandInteraction -> serverRepository.getServer(serverId)
+ }
+
+ if (server == null) {
+ deferEphemeralResponse().respond {
+ content = "No server with id '$serverId' was found."
+ }
+ return null
+ }
+
+ return server
+}
diff --git a/src/main/kotlin/de/darkatra/vrising/discord/commands/UpdateServerCommand.kt b/src/main/kotlin/de/darkatra/vrising/discord/commands/UpdateServerCommand.kt
index e26ae29..66818b1 100644
--- a/src/main/kotlin/de/darkatra/vrising/discord/commands/UpdateServerCommand.kt
+++ b/src/main/kotlin/de/darkatra/vrising/discord/commands/UpdateServerCommand.kt
@@ -3,38 +3,24 @@ package de.darkatra.vrising.discord.commands
import de.darkatra.vrising.discord.BotProperties
import de.darkatra.vrising.discord.commands.parameters.ServerApiHostnameParameter
import de.darkatra.vrising.discord.commands.parameters.ServerHostnameParameter
-import de.darkatra.vrising.discord.commands.parameters.addDisplayPlayerGearLevelParameter
-import de.darkatra.vrising.discord.commands.parameters.addDisplayServerDescriptionParameter
-import de.darkatra.vrising.discord.commands.parameters.addEmbedEnabledParameter
-import de.darkatra.vrising.discord.commands.parameters.addPlayerActivityFeedChannelIdParameter
-import de.darkatra.vrising.discord.commands.parameters.addPvpKillFeedChannelIdParameter
import de.darkatra.vrising.discord.commands.parameters.addServerApiHostnameParameter
import de.darkatra.vrising.discord.commands.parameters.addServerApiPasswordParameter
import de.darkatra.vrising.discord.commands.parameters.addServerApiPortParameter
import de.darkatra.vrising.discord.commands.parameters.addServerApiUsernameParameter
import de.darkatra.vrising.discord.commands.parameters.addServerHostnameParameter
+import de.darkatra.vrising.discord.commands.parameters.addServerIdParameter
import de.darkatra.vrising.discord.commands.parameters.addServerQueryPortParameter
-import de.darkatra.vrising.discord.commands.parameters.addServerStatusMonitorIdParameter
-import de.darkatra.vrising.discord.commands.parameters.addServerStatusMonitorStatusParameter
-import de.darkatra.vrising.discord.commands.parameters.getDisplayPlayerGearLevelParameter
-import de.darkatra.vrising.discord.commands.parameters.getDisplayServerDescriptionParameter
-import de.darkatra.vrising.discord.commands.parameters.getEmbedEnabledParameter
-import de.darkatra.vrising.discord.commands.parameters.getPlayerActivityFeedChannelIdParameter
-import de.darkatra.vrising.discord.commands.parameters.getPvpKillFeedChannelIdParameter
import de.darkatra.vrising.discord.commands.parameters.getServerApiHostnameParameter
import de.darkatra.vrising.discord.commands.parameters.getServerApiPasswordParameter
import de.darkatra.vrising.discord.commands.parameters.getServerApiPortParameter
import de.darkatra.vrising.discord.commands.parameters.getServerApiUsernameParameter
import de.darkatra.vrising.discord.commands.parameters.getServerHostnameParameter
+import de.darkatra.vrising.discord.commands.parameters.getServerIdParameter
import de.darkatra.vrising.discord.commands.parameters.getServerQueryPortParameter
-import de.darkatra.vrising.discord.commands.parameters.getServerStatusMonitorIdParameter
-import de.darkatra.vrising.discord.commands.parameters.getServerStatusMonitorStatusParameter
-import de.darkatra.vrising.discord.persistence.ServerStatusMonitorRepository
+import de.darkatra.vrising.discord.persistence.ServerRepository
import dev.kord.core.Kord
import dev.kord.core.behavior.interaction.response.respond
import dev.kord.core.entity.interaction.ChatInputCommandInteraction
-import dev.kord.core.entity.interaction.GlobalChatInputCommandInteraction
-import dev.kord.core.entity.interaction.GuildChatInputCommandInteraction
import org.slf4j.LoggerFactory
import org.springframework.boot.context.properties.EnableConfigurationProperties
import org.springframework.stereotype.Component
@@ -42,14 +28,14 @@ import org.springframework.stereotype.Component
@Component
@EnableConfigurationProperties(BotProperties::class)
class UpdateServerCommand(
- private val serverStatusMonitorRepository: ServerStatusMonitorRepository,
+ private val serverRepository: ServerRepository,
private val botProperties: BotProperties
) : Command {
private val logger = LoggerFactory.getLogger(javaClass)
private val name: String = "update-server"
- private val description: String = "Updates the given server status monitor."
+ private val description: String = "Updates the given server."
override fun getCommandName(): String = name
@@ -62,7 +48,7 @@ class UpdateServerCommand(
dmPermission = true
disableCommandInGuilds()
- addServerStatusMonitorIdParameter()
+ addServerIdParameter()
addServerHostnameParameter(required = false)
addServerQueryPortParameter(required = false)
@@ -71,21 +57,15 @@ class UpdateServerCommand(
addServerApiPortParameter(required = false)
addServerApiUsernameParameter(required = false)
addServerApiPasswordParameter(required = false)
-
- addServerStatusMonitorStatusParameter(required = false)
-
- addEmbedEnabledParameter(required = false)
- addDisplayServerDescriptionParameter(required = false)
- addDisplayPlayerGearLevelParameter(required = false)
-
- addPlayerActivityFeedChannelIdParameter(required = false)
- addPvpKillFeedChannelIdParameter(required = false)
}
}
override suspend fun handle(interaction: ChatInputCommandInteraction) {
- val serverStatusMonitorId = interaction.getServerStatusMonitorIdParameter()
+ val server = interaction.getServer(serverRepository)
+ ?: return
+
+ val serverId = interaction.getServerIdParameter()
val hostname = interaction.getServerHostnameParameter()
val queryPort = interaction.getServerQueryPortParameter()
@@ -94,73 +74,35 @@ class UpdateServerCommand(
val apiUsername = interaction.getServerApiUsernameParameter()
val apiPassword = interaction.getServerApiPasswordParameter()
- val status = interaction.getServerStatusMonitorStatusParameter()
-
- val embedEnabled = interaction.getEmbedEnabledParameter()
- val displayServerDescription = interaction.getDisplayServerDescriptionParameter()
- val displayPlayerGearLevel = interaction.getDisplayPlayerGearLevelParameter()
-
- val playerActivityFeedChannelId = interaction.getPlayerActivityFeedChannelIdParameter()
- val pvpKillFeedChannelId = interaction.getPvpKillFeedChannelIdParameter()
-
- val serverStatusMonitor = when (interaction) {
- is GuildChatInputCommandInteraction -> serverStatusMonitorRepository.getServerStatusMonitor(serverStatusMonitorId, interaction.guildId.toString())
- is GlobalChatInputCommandInteraction -> serverStatusMonitorRepository.getServerStatusMonitor(serverStatusMonitorId)
- }
-
- if (serverStatusMonitor == null) {
- interaction.deferEphemeralResponse().respond {
- content = "No server with id '$serverStatusMonitorId' was found."
- }
- return
- }
-
if (hostname != null) {
ServerHostnameParameter.validate(hostname, botProperties.allowLocalAddressRanges)
- serverStatusMonitor.hostname = hostname
+ server.hostname = hostname
}
if (queryPort != null) {
- serverStatusMonitor.queryPort = queryPort
+ server.queryPort = queryPort
}
if (apiHostname != null) {
- serverStatusMonitor.apiHostname = determineValueOfNullableStringParameter(apiHostname).also {
+ server.apiHostname = determineValueOfNullableStringParameter(apiHostname).also {
ServerApiHostnameParameter.validate(it, botProperties.allowLocalAddressRanges)
}
}
if (apiPort != null) {
- serverStatusMonitor.apiPort = if (apiPort == -1) null else apiPort
+ server.apiPort = if (apiPort == -1) null else apiPort
}
if (apiUsername != null) {
- serverStatusMonitor.apiUsername = determineValueOfNullableStringParameter(apiUsername)
+ server.apiUsername = determineValueOfNullableStringParameter(apiUsername)
}
if (apiPassword != null) {
- serverStatusMonitor.apiPassword = determineValueOfNullableStringParameter(apiPassword)
- }
- if (status != null) {
- serverStatusMonitor.status = status
- }
- if (embedEnabled != null) {
- serverStatusMonitor.embedEnabled = embedEnabled
- }
- if (displayServerDescription != null) {
- serverStatusMonitor.displayServerDescription = displayServerDescription
- }
- if (displayPlayerGearLevel != null) {
- serverStatusMonitor.displayPlayerGearLevel = displayPlayerGearLevel
- }
- if (playerActivityFeedChannelId != null) {
- serverStatusMonitor.playerActivityDiscordChannelId = determineValueOfNullableStringParameter(playerActivityFeedChannelId)
- }
- if (pvpKillFeedChannelId != null) {
- serverStatusMonitor.pvpKillFeedDiscordChannelId = determineValueOfNullableStringParameter(pvpKillFeedChannelId)
+ server.apiPassword = determineValueOfNullableStringParameter(apiPassword)
}
- serverStatusMonitorRepository.updateServerStatusMonitor(serverStatusMonitor)
+ serverRepository.updateServer(server)
- logger.info("Successfully updated monitor with id '${serverStatusMonitorId}'.")
+ logger.info("Successfully updated server '$serverId'.")
interaction.deferEphemeralResponse().respond {
- content = "Updated server status monitor with id '${serverStatusMonitorId}'. It may take some time until the status message is updated."
+ content = """Successfully updated server with id '$serverId'.
+ |Related status embeds, activity feeds, kill feeds and leaderboards may take some time to reflect the changes.""".trimMargin()
}
}
diff --git a/src/main/kotlin/de/darkatra/vrising/discord/commands/parameters/ChannelIdParameter.kt b/src/main/kotlin/de/darkatra/vrising/discord/commands/parameters/ChannelIdParameter.kt
new file mode 100644
index 0000000..0ec8b13
--- /dev/null
+++ b/src/main/kotlin/de/darkatra/vrising/discord/commands/parameters/ChannelIdParameter.kt
@@ -0,0 +1,23 @@
+package de.darkatra.vrising.discord.commands.parameters
+
+import de.darkatra.vrising.discord.getChannelIdFromStringParameter
+import dev.kord.core.entity.interaction.ChatInputCommandInteraction
+import dev.kord.rest.builder.interaction.GlobalChatInputCreateBuilder
+import dev.kord.rest.builder.interaction.string
+
+object ChannelIdParameter {
+ const val NAME = "channel-id"
+}
+
+fun GlobalChatInputCreateBuilder.addChannelIdParameter(required: Boolean = true) {
+ string(
+ name = ChannelIdParameter.NAME,
+ description = "The id of the channel to post to."
+ ) {
+ this.required = required
+ }
+}
+
+fun ChatInputCommandInteraction.getChannelIdParameter(): String? {
+ return command.getChannelIdFromStringParameter(ChannelIdParameter.NAME)
+}
diff --git a/src/main/kotlin/de/darkatra/vrising/discord/commands/parameters/EmbedEnabledParameter.kt b/src/main/kotlin/de/darkatra/vrising/discord/commands/parameters/EmbedEnabledParameter.kt
deleted file mode 100644
index 03da07d..0000000
--- a/src/main/kotlin/de/darkatra/vrising/discord/commands/parameters/EmbedEnabledParameter.kt
+++ /dev/null
@@ -1,22 +0,0 @@
-package de.darkatra.vrising.discord.commands.parameters
-
-import dev.kord.core.entity.interaction.ChatInputCommandInteraction
-import dev.kord.rest.builder.interaction.GlobalChatInputCreateBuilder
-import dev.kord.rest.builder.interaction.boolean
-
-object EmbedEnabledParameter {
- const val NAME = "embed-enabled"
-}
-
-fun GlobalChatInputCreateBuilder.addEmbedEnabledParameter(required: Boolean = true) {
- boolean(
- name = EmbedEnabledParameter.NAME,
- description = "Whether or not a discord status embed should be posted. Defaults to true."
- ) {
- this.required = required
- }
-}
-
-fun ChatInputCommandInteraction.getEmbedEnabledParameter(): Boolean? {
- return command.booleans[EmbedEnabledParameter.NAME]
-}
diff --git a/src/main/kotlin/de/darkatra/vrising/discord/commands/parameters/PlayerActivityFeedChannelIdParameter.kt b/src/main/kotlin/de/darkatra/vrising/discord/commands/parameters/PlayerActivityFeedChannelIdParameter.kt
deleted file mode 100644
index c6f7b19..0000000
--- a/src/main/kotlin/de/darkatra/vrising/discord/commands/parameters/PlayerActivityFeedChannelIdParameter.kt
+++ /dev/null
@@ -1,23 +0,0 @@
-package de.darkatra.vrising.discord.commands.parameters
-
-import de.darkatra.vrising.discord.getChannelIdFromStringParameter
-import dev.kord.core.entity.interaction.ChatInputCommandInteraction
-import dev.kord.rest.builder.interaction.GlobalChatInputCreateBuilder
-import dev.kord.rest.builder.interaction.string
-
-object PlayerActivityFeedChannelIdParameter {
- const val NAME = "player-activity-feed-channel-id"
-}
-
-fun GlobalChatInputCreateBuilder.addPlayerActivityFeedChannelIdParameter(required: Boolean = true) {
- string(
- name = PlayerActivityFeedChannelIdParameter.NAME,
- description = "The id of the channel to post the player activity feed in."
- ) {
- this.required = required
- }
-}
-
-fun ChatInputCommandInteraction.getPlayerActivityFeedChannelIdParameter(): String? {
- return command.getChannelIdFromStringParameter(PlayerActivityFeedChannelIdParameter.NAME)
-}
diff --git a/src/main/kotlin/de/darkatra/vrising/discord/commands/parameters/PvpKillFeedChannelIdParameter.kt b/src/main/kotlin/de/darkatra/vrising/discord/commands/parameters/PvpKillFeedChannelIdParameter.kt
deleted file mode 100644
index ffbf8f0..0000000
--- a/src/main/kotlin/de/darkatra/vrising/discord/commands/parameters/PvpKillFeedChannelIdParameter.kt
+++ /dev/null
@@ -1,23 +0,0 @@
-package de.darkatra.vrising.discord.commands.parameters
-
-import de.darkatra.vrising.discord.getChannelIdFromStringParameter
-import dev.kord.core.entity.interaction.ChatInputCommandInteraction
-import dev.kord.rest.builder.interaction.GlobalChatInputCreateBuilder
-import dev.kord.rest.builder.interaction.string
-
-object PvpKillFeedChannelIdParameter {
- const val NAME = "pvp-kill-feed-channel-id"
-}
-
-fun GlobalChatInputCreateBuilder.addPvpKillFeedChannelIdParameter(required: Boolean = true) {
- string(
- name = PvpKillFeedChannelIdParameter.NAME,
- description = "The id of the channel to post the pvp kill feed in."
- ) {
- this.required = required
- }
-}
-
-fun ChatInputCommandInteraction.getPvpKillFeedChannelIdParameter(): String? {
- return command.getChannelIdFromStringParameter(PvpKillFeedChannelIdParameter.NAME)
-}
diff --git a/src/main/kotlin/de/darkatra/vrising/discord/commands/parameters/ServerIdParameter.kt b/src/main/kotlin/de/darkatra/vrising/discord/commands/parameters/ServerIdParameter.kt
new file mode 100644
index 0000000..039814c
--- /dev/null
+++ b/src/main/kotlin/de/darkatra/vrising/discord/commands/parameters/ServerIdParameter.kt
@@ -0,0 +1,22 @@
+package de.darkatra.vrising.discord.commands.parameters
+
+import dev.kord.core.entity.interaction.ChatInputCommandInteraction
+import dev.kord.rest.builder.interaction.GlobalChatInputCreateBuilder
+import dev.kord.rest.builder.interaction.string
+
+object ServerIdParameter {
+ const val NAME = "server-id"
+}
+
+fun GlobalChatInputCreateBuilder.addServerIdParameter() {
+ string(
+ name = ServerIdParameter.NAME,
+ description = "The id of the server."
+ ) {
+ required = true
+ }
+}
+
+fun ChatInputCommandInteraction.getServerIdParameter(): String {
+ return command.strings[ServerIdParameter.NAME]!!
+}
diff --git a/src/main/kotlin/de/darkatra/vrising/discord/commands/parameters/ServerStatusMonitorIdParameter.kt b/src/main/kotlin/de/darkatra/vrising/discord/commands/parameters/ServerStatusMonitorIdParameter.kt
deleted file mode 100644
index e530e26..0000000
--- a/src/main/kotlin/de/darkatra/vrising/discord/commands/parameters/ServerStatusMonitorIdParameter.kt
+++ /dev/null
@@ -1,22 +0,0 @@
-package de.darkatra.vrising.discord.commands.parameters
-
-import dev.kord.core.entity.interaction.ChatInputCommandInteraction
-import dev.kord.rest.builder.interaction.GlobalChatInputCreateBuilder
-import dev.kord.rest.builder.interaction.string
-
-object ServerStatusMonitorIdParameter {
- const val NAME = "server-status-monitor-id"
-}
-
-fun GlobalChatInputCreateBuilder.addServerStatusMonitorIdParameter() {
- string(
- name = ServerStatusMonitorIdParameter.NAME,
- description = "The id of the server status monitor."
- ) {
- required = true
- }
-}
-
-fun ChatInputCommandInteraction.getServerStatusMonitorIdParameter(): String {
- return command.strings[ServerStatusMonitorIdParameter.NAME]!!
-}
diff --git a/src/main/kotlin/de/darkatra/vrising/discord/commands/parameters/ServerStatusMonitorStatusParameter.kt b/src/main/kotlin/de/darkatra/vrising/discord/commands/parameters/ServerStatusMonitorStatusParameter.kt
deleted file mode 100644
index 655facb..0000000
--- a/src/main/kotlin/de/darkatra/vrising/discord/commands/parameters/ServerStatusMonitorStatusParameter.kt
+++ /dev/null
@@ -1,26 +0,0 @@
-package de.darkatra.vrising.discord.commands.parameters
-
-import de.darkatra.vrising.discord.persistence.model.ServerStatusMonitorStatus
-import dev.kord.core.entity.interaction.ChatInputCommandInteraction
-import dev.kord.rest.builder.interaction.GlobalChatInputCreateBuilder
-import dev.kord.rest.builder.interaction.string
-
-object ServerStatusMonitorStatusParameter {
- const val NAME = "status"
-}
-
-fun GlobalChatInputCreateBuilder.addServerStatusMonitorStatusParameter(required: Boolean = true) {
- string(
- name = ServerStatusMonitorStatusParameter.NAME,
- description = "Determines if a server status monitor should be updated or not."
- ) {
- this.required = required
-
- choice("ACTIVE", "ACTIVE")
- choice("INACTIVE", "INACTIVE")
- }
-}
-
-fun ChatInputCommandInteraction.getServerStatusMonitorStatusParameter(): ServerStatusMonitorStatus? {
- return command.strings[ServerStatusMonitorStatusParameter.NAME]?.let { ServerStatusMonitorStatus.valueOf(it) }
-}
diff --git a/src/main/kotlin/de/darkatra/vrising/discord/commands/parameters/StatusParameter.kt b/src/main/kotlin/de/darkatra/vrising/discord/commands/parameters/StatusParameter.kt
new file mode 100644
index 0000000..1f7d59e
--- /dev/null
+++ b/src/main/kotlin/de/darkatra/vrising/discord/commands/parameters/StatusParameter.kt
@@ -0,0 +1,26 @@
+package de.darkatra.vrising.discord.commands.parameters
+
+import de.darkatra.vrising.discord.persistence.model.Status
+import dev.kord.core.entity.interaction.ChatInputCommandInteraction
+import dev.kord.rest.builder.interaction.GlobalChatInputCreateBuilder
+import dev.kord.rest.builder.interaction.string
+
+object StatusParameter {
+ const val NAME = "status"
+}
+
+fun GlobalChatInputCreateBuilder.addStatusParameter(required: Boolean = true) {
+ string(
+ name = StatusParameter.NAME,
+ description = "Determines if a feature is active or not."
+ ) {
+ this.required = required
+
+ choice("ACTIVE", "ACTIVE")
+ choice("INACTIVE", "INACTIVE")
+ }
+}
+
+fun ChatInputCommandInteraction.getStatusParameter(): Status? {
+ return command.strings[StatusParameter.NAME]?.let { Status.valueOf(it) }
+}
diff --git a/src/main/kotlin/de/darkatra/vrising/discord/migration/DatabaseMigration.kt b/src/main/kotlin/de/darkatra/vrising/discord/migration/DatabaseMigration.kt
index 68abb23..69d8ee3 100644
--- a/src/main/kotlin/de/darkatra/vrising/discord/migration/DatabaseMigration.kt
+++ b/src/main/kotlin/de/darkatra/vrising/discord/migration/DatabaseMigration.kt
@@ -6,6 +6,7 @@ import org.dizitart.no2.Nitrite
class DatabaseMigration(
val description: String,
val isApplicable: (currentSchemaVersion: SemanticVersion) -> Boolean,
+ val documentCollectionName: String,
val documentAction: (document: Document) -> Unit = {},
val databaseAction: (database: Nitrite) -> Unit = {}
)
diff --git a/src/main/kotlin/de/darkatra/vrising/discord/migration/DatabaseMigrationService.kt b/src/main/kotlin/de/darkatra/vrising/discord/migration/DatabaseMigrationService.kt
index 94ebe9b..19042f7 100644
--- a/src/main/kotlin/de/darkatra/vrising/discord/migration/DatabaseMigrationService.kt
+++ b/src/main/kotlin/de/darkatra/vrising/discord/migration/DatabaseMigrationService.kt
@@ -1,11 +1,9 @@
package de.darkatra.vrising.discord.migration
-import de.darkatra.vrising.discord.persistence.model.ServerStatusMonitor
-import de.darkatra.vrising.discord.persistence.model.ServerStatusMonitorStatus
+import de.darkatra.vrising.discord.persistence.model.Status
import org.dizitart.no2.Document
import org.dizitart.no2.Nitrite
import org.dizitart.no2.objects.filters.ObjectFilters
-import org.dizitart.no2.util.ObjectUtils
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Value
import org.springframework.stereotype.Service
@@ -26,19 +24,22 @@ class DatabaseMigrationService(
DatabaseMigration(
description = "Set default value for displayPlayerGearLevel property.",
isApplicable = { currentSchemaVersion -> currentSchemaVersion.major == 1 && currentSchemaVersion.minor <= 3 },
+ documentCollectionName = "de.darkatra.vrising.discord.ServerStatusMonitor",
documentAction = { document -> document["displayPlayerGearLevel"] = true }
),
DatabaseMigration(
description = "Set default value for status and displayServerDescription property.",
isApplicable = { currentSchemaVersion -> currentSchemaVersion.major == 1 && currentSchemaVersion.minor <= 4 },
+ documentCollectionName = "de.darkatra.vrising.discord.ServerStatusMonitor",
documentAction = { document ->
- document["status"] = ServerStatusMonitorStatus.ACTIVE.name
+ document["status"] = Status.ACTIVE.name
document["displayServerDescription"] = true
}
),
DatabaseMigration(
description = "Remove the displayPlayerGearLevel property due to patch 0.5.42405.",
isApplicable = { currentSchemaVersion -> currentSchemaVersion.major == 1 && currentSchemaVersion.minor <= 5 },
+ documentCollectionName = "de.darkatra.vrising.discord.ServerStatusMonitor",
documentAction = { document ->
// we can't remove the field completely due to how nitrites update function works
// setting it to false instead (this was the default value in previous versions)
@@ -48,14 +49,16 @@ class DatabaseMigrationService(
DatabaseMigration(
description = "Set default value for currentFailedAttempts property.",
isApplicable = { currentSchemaVersion -> currentSchemaVersion.major == 1 && currentSchemaVersion.minor <= 7 },
+ documentCollectionName = "de.darkatra.vrising.discord.ServerStatusMonitor",
documentAction = { document -> document["currentFailedAttempts"] = 0 }
),
DatabaseMigration(
description = "Migrate the existing ServerStatusMonitor collection to the new collection name introduced by a package change and set defaults for displayClan, displayGearLevel and displayKilledVBloods.",
isApplicable = { currentSchemaVersion -> currentSchemaVersion.major < 2 || (currentSchemaVersion.major == 2 && currentSchemaVersion.minor <= 1) },
+ documentCollectionName = "de.darkatra.vrising.discord.serverstatus.model.ServerStatusMonitor",
databaseAction = { database ->
val oldCollection = database.getCollection("de.darkatra.vrising.discord.ServerStatusMonitor")
- val newCollection = database.getCollection(ObjectUtils.findObjectStoreName(ServerStatusMonitor::class.java))
+ val newCollection = database.getCollection("de.darkatra.vrising.discord.serverstatus.model.ServerStatusMonitor")
oldCollection.find().forEach { document ->
newCollection.insert(document)
}
@@ -69,6 +72,7 @@ class DatabaseMigrationService(
DatabaseMigration(
description = "Set default value for version property.",
isApplicable = { currentSchemaVersion -> currentSchemaVersion.major < 2 || (currentSchemaVersion.major == 2 && currentSchemaVersion.minor <= 2) },
+ documentCollectionName = "de.darkatra.vrising.discord.serverstatus.model.ServerStatusMonitor",
documentAction = { document ->
document["version"] = Instant.now().toEpochMilli()
}
@@ -76,6 +80,7 @@ class DatabaseMigrationService(
DatabaseMigration(
description = "Make it possible to disable the discord embed and only use the activity or kill feed.",
isApplicable = { currentSchemaVersion -> currentSchemaVersion.major < 2 || (currentSchemaVersion.major == 2 && currentSchemaVersion.minor <= 8) },
+ documentCollectionName = "de.darkatra.vrising.discord.serverstatus.model.ServerStatusMonitor",
documentAction = { document ->
document["embedEnabled"] = true
}
@@ -83,6 +88,7 @@ class DatabaseMigrationService(
DatabaseMigration(
description = "Serialize error timestamp as long (epochSecond).",
isApplicable = { currentSchemaVersion -> currentSchemaVersion.major < 2 || (currentSchemaVersion.major == 2 && currentSchemaVersion.minor <= 9) },
+ documentCollectionName = "de.darkatra.vrising.discord.serverstatus.model.ServerStatusMonitor",
documentAction = { document ->
val recentErrors = document["recentErrors"]
if (recentErrors is List<*>) {
@@ -99,14 +105,75 @@ class DatabaseMigrationService(
DatabaseMigration(
description = "Migrate the existing ServerStatusMonitor collection to the new collection name introduced by a package change.",
isApplicable = { currentSchemaVersion -> currentSchemaVersion.major < 2 || (currentSchemaVersion.major == 2 && currentSchemaVersion.minor <= 10 && currentSchemaVersion.patch <= 1) },
+ documentCollectionName = "de.darkatra.vrising.discord.persistence.model.ServerStatusMonitor",
databaseAction = { database ->
val oldCollection = database.getCollection("de.darkatra.vrising.discord.serverstatus.model.ServerStatusMonitor")
- val newCollection = database.getCollection(ObjectUtils.findObjectStoreName(ServerStatusMonitor::class.java))
+ val newCollection = database.getCollection("de.darkatra.vrising.discord.persistence.model.ServerStatusMonitor")
oldCollection.find().forEach { document ->
newCollection.insert(document)
}
oldCollection.remove(ObjectFilters.ALL)
}
+ ),
+ DatabaseMigration(
+ description = "Each feature now has its own nested database object. Will not migrate previous errors to the new format.",
+ isApplicable = { currentSchemaVersion -> currentSchemaVersion.major < 2 || (currentSchemaVersion.major == 2 && currentSchemaVersion.minor <= 10) },
+ documentCollectionName = "de.darkatra.vrising.discord.persistence.model.Server",
+ databaseAction = { database ->
+ val oldCollection = database.getCollection("de.darkatra.vrising.discord.persistence.model.ServerStatusMonitor")
+ val newCollection = database.getCollection("de.darkatra.vrising.discord.persistence.model.Server")
+ oldCollection.find().forEach { document ->
+
+ val server = Document().apply {
+ put("id", document["id"])
+ put("version", document["version"])
+ put("discordServerId", document["discordServerId"])
+ put("hostname", document["hostname"])
+ put("queryPort", document["queryPort"])
+ put("apiHostname", document["apiHostname"])
+ put("apiPort", document["apiPort"])
+ put("apiUsername", document["apiUsername"])
+ put("apiPassword", document["apiPassword"])
+
+ if (document["playerActivityDiscordChannelId"] != null) {
+ put(
+ "playerActivityFeed",
+ mapOf(
+ "status" to Status.ACTIVE,
+ "discordChannelId" to document["playerActivityDiscordChannelId"]
+ )
+ )
+ }
+
+ if (document["pvpKillFeedDiscordChannelId"] != null) {
+ put(
+ "pvpKillFeed",
+ mapOf(
+ "status" to Status.ACTIVE,
+ "discordChannelId" to document["pvpKillFeedDiscordChannelId"]
+ )
+ )
+ }
+
+ if (document["embedEnabled"] == true) {
+ put(
+ "statusMonitor",
+ mapOf(
+ "status" to document["status"],
+ "discordChannelId" to document["discordChannelId"],
+ "displayServerDescription" to document["displayServerDescription"],
+ "displayPlayerGearLevel" to document["displayPlayerGearLevel"],
+ "currentEmbedMessageId" to document["currentEmbedMessageId"],
+ "currentFailedAttempts" to document["currentFailedAttempts"],
+ "currentFailedApiAttempts" to document["currentFailedApiAttempts"],
+ )
+ )
+ }
+ }
+ newCollection.insert(server)
+ }
+ oldCollection.remove(ObjectFilters.ALL)
+ }
)
)
@@ -130,13 +197,14 @@ class DatabaseMigrationService(
}
// perform migration that affect the whole database
- migrationsToPerform.forEach { migration -> migration.databaseAction(database) }
+ migrationsToPerform.forEach { migration ->
+ migration.databaseAction(database)
- // perform migration that affect documents in the ServerStatusMonitor collection
- val collection = database.getCollection(ObjectUtils.findObjectStoreName(ServerStatusMonitor::class.java))
- collection.find().forEach { document ->
- migrationsToPerform.forEach { migration -> migration.documentAction(document) }
- collection.update(document)
+ val collection = database.getCollection(migration.documentCollectionName)
+ collection.find().forEach { document ->
+ migration.documentAction(document)
+ collection.update(document)
+ }
}
repository.insert(Schema("V$currentAppVersion"))
diff --git a/src/main/kotlin/de/darkatra/vrising/discord/persistence/OutdatedServerException.kt b/src/main/kotlin/de/darkatra/vrising/discord/persistence/OutdatedServerException.kt
new file mode 100644
index 0000000..3f2b128
--- /dev/null
+++ b/src/main/kotlin/de/darkatra/vrising/discord/persistence/OutdatedServerException.kt
@@ -0,0 +1,5 @@
+package de.darkatra.vrising.discord.persistence
+
+import de.darkatra.vrising.discord.BotException
+
+class OutdatedServerException(message: String) : BotException(message)
diff --git a/src/main/kotlin/de/darkatra/vrising/discord/persistence/ServerStatusMonitorRepository.kt b/src/main/kotlin/de/darkatra/vrising/discord/persistence/ServerRepository.kt
similarity index 55%
rename from src/main/kotlin/de/darkatra/vrising/discord/persistence/ServerStatusMonitorRepository.kt
rename to src/main/kotlin/de/darkatra/vrising/discord/persistence/ServerRepository.kt
index 62be134..6b1f13e 100644
--- a/src/main/kotlin/de/darkatra/vrising/discord/persistence/ServerStatusMonitorRepository.kt
+++ b/src/main/kotlin/de/darkatra/vrising/discord/persistence/ServerRepository.kt
@@ -1,9 +1,7 @@
package de.darkatra.vrising.discord.persistence
-import de.darkatra.vrising.discord.persistence.model.ServerStatusMonitor
-import de.darkatra.vrising.discord.persistence.model.ServerStatusMonitorStatus
+import de.darkatra.vrising.discord.persistence.model.Server
import de.darkatra.vrising.discord.plus
-import de.darkatra.vrising.discord.serverstatus.exceptions.OutdatedServerStatusMonitorException
import org.dizitart.kno2.filters.and
import org.dizitart.no2.FindOptions
import org.dizitart.no2.Nitrite
@@ -13,38 +11,39 @@ import org.springframework.stereotype.Service
import java.time.Instant
@Service
-class ServerStatusMonitorRepository(
+class ServerRepository(
database: Nitrite,
) {
- private var repository = database.getRepository(ServerStatusMonitor::class.java)
+ private val repository = database.getRepository(Server::class.java)
- fun addServerStatusMonitor(serverStatusMonitor: ServerStatusMonitor) {
+ fun addServer(server: Server) {
- if (repository.find(ObjectFilters.eq("id", serverStatusMonitor.id)).any()) {
- throw IllegalStateException("Monitor with id '${serverStatusMonitor.id}' already exists.")
+ if (repository.find(ObjectFilters.eq("id", server.id)).any()) {
+ throw IllegalStateException("Server with id '${server.id}' already exists.")
}
- repository.insert(updateVersion(serverStatusMonitor))
+ repository.insert(updateVersion(server))
}
- fun updateServerStatusMonitor(serverStatusMonitor: ServerStatusMonitor) {
+ fun updateServer(server: Server) {
@Suppress("DEPRECATION") // this is the internal usage the warning is referring to
- val newVersion = serverStatusMonitor.version
+ val newVersion = server.version
@Suppress("DEPRECATION") // this is the internal usage the warning is referring to
- val databaseVersion = (repository.find(ObjectFilters.eq("id", serverStatusMonitor.id)).firstOrNull()
- ?: throw OutdatedServerStatusMonitorException("Monitor with id '${serverStatusMonitor.id}' not found."))
+ val databaseVersion = (repository.find(ObjectFilters.eq("id", server.id)).firstOrNull()
+ ?: throw OutdatedServerException("Server with id '${server.id}' not found."))
.version!!
if (newVersion == null || databaseVersion > newVersion) {
- throw OutdatedServerStatusMonitorException("Monitor with id '${serverStatusMonitor.id}' was already updated by another thread.")
+ throw OutdatedServerException("Server with id '${server.id}' was already updated by another thread.")
}
- repository.update(updateVersion(serverStatusMonitor))
+ repository.update(updateVersion(server))
}
- fun removeServerStatusMonitor(id: String, discordServerId: String? = null): Boolean {
+ fun removeServer(id: String, discordServerId: String? = null): Boolean {
+
var objectFilter: ObjectFilter = ObjectFilters.eq("id", id)
if (discordServerId != null) {
@@ -54,7 +53,7 @@ class ServerStatusMonitorRepository(
return repository.remove(objectFilter).affectedCount > 0
}
- fun getServerStatusMonitor(id: String, discordServerId: String? = null): ServerStatusMonitor? {
+ fun getServer(id: String, discordServerId: String? = null): Server? {
var objectFilter: ObjectFilter = ObjectFilters.eq("id", id)
@@ -62,23 +61,17 @@ class ServerStatusMonitorRepository(
objectFilter += ObjectFilters.eq("discordServerId", discordServerId)
}
- return repository.find(objectFilter).firstOrNull()
+ return repository.find(objectFilter).firstOrNull().also { server ->
+ server?.linkServerAwareFields()
+ }
}
- fun getServerStatusMonitors(
- discordServerId: String? = null,
- status: ServerStatusMonitorStatus? = null,
- offset: Int? = null,
- limit: Int? = null
- ): List {
+ fun getServers(discordServerId: String? = null, offset: Int? = null, limit: Int? = null): List {
val objectFilter = buildList {
if (discordServerId != null) {
add(ObjectFilters.eq("discordServerId", discordServerId))
}
- if (status != null) {
- add(ObjectFilters.eq("status", status))
- }
}.reduceOrNull { acc: ObjectFilter, objectFilter: ObjectFilter -> acc.and(objectFilter) }
if (offset != null && limit != null) {
@@ -97,12 +90,12 @@ class ServerStatusMonitorRepository(
return when {
objectFilter != null -> repository.find(objectFilter).toList()
else -> repository.find().toList()
+ }.onEach { server ->
+ server.linkServerAwareFields()
}
}
- fun count(
- discordServerId: String? = null,
- ): Int {
+ fun count(discordServerId: String? = null): Int {
return when {
discordServerId != null -> repository.find(ObjectFilters.eq("discordServerId", discordServerId)).size()
@@ -110,8 +103,9 @@ class ServerStatusMonitorRepository(
}
}
- private fun updateVersion(serverStatusMonitor: ServerStatusMonitor): ServerStatusMonitor {
- return serverStatusMonitor.apply {
+ private fun updateVersion(server: Server): Server {
+
+ return server.apply {
@Suppress("DEPRECATION") // this is the internal usage the warning is referring to
version = Instant.now().toEpochMilli()
}
diff --git a/src/main/kotlin/de/darkatra/vrising/discord/persistence/model/ErrorAware.kt b/src/main/kotlin/de/darkatra/vrising/discord/persistence/model/ErrorAware.kt
new file mode 100644
index 0000000..9d29f3f
--- /dev/null
+++ b/src/main/kotlin/de/darkatra/vrising/discord/persistence/model/ErrorAware.kt
@@ -0,0 +1,22 @@
+package de.darkatra.vrising.discord.persistence.model
+
+import java.time.Instant
+
+interface ErrorAware {
+
+ var recentErrors: List
+
+ fun addError(throwable: Throwable, maxErrorsToKeep: Int) {
+ recentErrors = recentErrors
+ .takeLast((maxErrorsToKeep - 1).coerceAtLeast(0))
+ .toMutableList()
+ .apply {
+ add(
+ Error(
+ message = "${throwable::class.simpleName}: ${throwable.message}",
+ timestamp = Instant.now().epochSecond
+ )
+ )
+ }
+ }
+}
diff --git a/src/main/kotlin/de/darkatra/vrising/discord/persistence/model/Leaderboard.kt b/src/main/kotlin/de/darkatra/vrising/discord/persistence/model/Leaderboard.kt
new file mode 100644
index 0000000..f12fa39
--- /dev/null
+++ b/src/main/kotlin/de/darkatra/vrising/discord/persistence/model/Leaderboard.kt
@@ -0,0 +1,20 @@
+package de.darkatra.vrising.discord.persistence.model
+
+data class Leaderboard(
+ override var status: Status,
+ @Transient
+ private var server: Server? = null,
+
+ // TODO: define the type of the leaderboard and think about other properties that should be configurable
+
+ override var recentErrors: List = emptyList()
+) : ErrorAware, ServerAware, StatusAware {
+
+ override fun getServer(): Server {
+ return server!!
+ }
+
+ override fun setServer(server: Server) {
+ this.server = server
+ }
+}
diff --git a/src/main/kotlin/de/darkatra/vrising/discord/persistence/model/PlayerActivityFeed.kt b/src/main/kotlin/de/darkatra/vrising/discord/persistence/model/PlayerActivityFeed.kt
new file mode 100644
index 0000000..979ced2
--- /dev/null
+++ b/src/main/kotlin/de/darkatra/vrising/discord/persistence/model/PlayerActivityFeed.kt
@@ -0,0 +1,31 @@
+package de.darkatra.vrising.discord.persistence.model
+
+import java.time.Instant
+
+data class PlayerActivityFeed(
+ override var status: Status,
+ @Transient
+ private var server: Server? = null,
+
+ var discordChannelId: String,
+ var lastUpdated: Long? = null,
+
+ var currentFailedAttempts: Int = 0,
+
+ override var recentErrors: List = emptyList()
+) : ErrorAware, ServerAware, StatusAware {
+
+ fun getLastUpdated(): Instant {
+ val lastUpdated = lastUpdated ?: return getServer().lastUpdated
+ return Instant.ofEpochMilli(lastUpdated)
+ }
+
+ override fun getServer(): Server {
+ return server!!
+ }
+
+ override fun setServer(server: Server) {
+ this.server = server
+ }
+}
+
diff --git a/src/main/kotlin/de/darkatra/vrising/discord/persistence/model/PvpKillFeed.kt b/src/main/kotlin/de/darkatra/vrising/discord/persistence/model/PvpKillFeed.kt
new file mode 100644
index 0000000..1717ce3
--- /dev/null
+++ b/src/main/kotlin/de/darkatra/vrising/discord/persistence/model/PvpKillFeed.kt
@@ -0,0 +1,30 @@
+package de.darkatra.vrising.discord.persistence.model
+
+import java.time.Instant
+
+data class PvpKillFeed(
+ override var status: Status,
+ @Transient
+ private var server: Server? = null,
+
+ var discordChannelId: String,
+ var lastUpdated: Long? = null,
+
+ var currentFailedAttempts: Int = 0,
+
+ override var recentErrors: List = emptyList()
+) : ErrorAware, ServerAware, StatusAware {
+
+ fun getLastUpdated(): Instant {
+ val lastUpdated = lastUpdated ?: return getServer().lastUpdated
+ return Instant.ofEpochMilli(lastUpdated)
+ }
+
+ override fun getServer(): Server {
+ return server!!
+ }
+
+ override fun setServer(server: Server) {
+ this.server = server
+ }
+}
diff --git a/src/main/kotlin/de/darkatra/vrising/discord/persistence/model/Server.kt b/src/main/kotlin/de/darkatra/vrising/discord/persistence/model/Server.kt
new file mode 100644
index 0000000..8b1508b
--- /dev/null
+++ b/src/main/kotlin/de/darkatra/vrising/discord/persistence/model/Server.kt
@@ -0,0 +1,80 @@
+package de.darkatra.vrising.discord.persistence.model
+
+import org.dizitart.no2.IndexType
+import org.dizitart.no2.objects.Id
+import org.dizitart.no2.objects.Index
+import org.dizitart.no2.objects.Indices
+import org.springframework.http.client.ClientHttpRequestInterceptor
+import org.springframework.http.client.support.BasicAuthenticationInterceptor
+import java.time.Instant
+
+@Indices(
+ value = [
+ Index(value = "discordServerId", type = IndexType.NonUnique),
+ ]
+)
+data class Server(
+ @Id
+ val id: String,
+ @Deprecated("This field is updated automatically by the ServerRepository, manually update with caution")
+ var version: Long? = null,
+
+ var discordServerId: String,
+
+ var hostname: String,
+ var queryPort: Int,
+
+ var apiHostname: String? = null,
+ var apiPort: Int? = null,
+ var apiUsername: String? = null,
+ var apiPassword: String? = null,
+
+ var playerActivityFeed: PlayerActivityFeed? = null,
+ var pvpKillFeed: PvpKillFeed? = null,
+ var statusMonitor: StatusMonitor? = null,
+
+ // TODO: decide on the way to store leaderboards
+ // option 1: store all leaderboards as a List and create commands that allow CRUD operations on that list
+ // this allows users to create more than one leaderboard per "type" (which might not be good - idk)
+ // option 2: store leaderboards in specific fields, such as `pvpLeaderboard` or `soulShardLeaderboard`
+ // this prevents users from having more than one leaderboard per type and probably also simplifies the commands
+ // a little bit by not introducing another id
+ var pvpLeaderboard: Leaderboard? = null
+) : StatusAware {
+
+ val apiEnabled: Boolean
+ get() = apiHostname != null && apiPort != null
+
+ @Suppress("DEPRECATION") // this is the internal usage the warning is referring to
+ val lastUpdated: Instant
+ get() = Instant.ofEpochMilli(version!!)
+
+ override val status: Status
+ get() {
+ return when (playerActivityFeed?.status == Status.ACTIVE
+ || statusMonitor?.status == Status.ACTIVE
+ || pvpKillFeed?.status == Status.ACTIVE
+ || pvpLeaderboard?.status == Status.ACTIVE) {
+ true -> Status.ACTIVE
+ false -> Status.INACTIVE
+ }
+ }
+
+ fun linkServerAwareFields() {
+ playerActivityFeed?.setServer(this)
+ pvpKillFeed?.setServer(this)
+ statusMonitor?.setServer(this)
+ pvpLeaderboard?.setServer(this)
+ }
+
+ fun getApiInterceptors(): List {
+
+ val apiUsername = apiUsername
+ val apiPassword = apiPassword
+
+ return when (apiUsername != null && apiPassword != null) {
+ true -> listOf(BasicAuthenticationInterceptor(apiUsername, apiPassword))
+ false -> emptyList()
+ }
+ }
+}
diff --git a/src/main/kotlin/de/darkatra/vrising/discord/persistence/model/ServerAware.kt b/src/main/kotlin/de/darkatra/vrising/discord/persistence/model/ServerAware.kt
new file mode 100644
index 0000000..84b5aa0
--- /dev/null
+++ b/src/main/kotlin/de/darkatra/vrising/discord/persistence/model/ServerAware.kt
@@ -0,0 +1,6 @@
+package de.darkatra.vrising.discord.persistence.model
+
+interface ServerAware {
+ fun getServer(): Server
+ fun setServer(server: Server)
+}
diff --git a/src/main/kotlin/de/darkatra/vrising/discord/persistence/model/ServerStatusMonitor.kt b/src/main/kotlin/de/darkatra/vrising/discord/persistence/model/ServerStatusMonitor.kt
deleted file mode 100644
index 425ed59..0000000
--- a/src/main/kotlin/de/darkatra/vrising/discord/persistence/model/ServerStatusMonitor.kt
+++ /dev/null
@@ -1,67 +0,0 @@
-package de.darkatra.vrising.discord.persistence.model
-
-import org.dizitart.no2.IndexType
-import org.dizitart.no2.objects.Id
-import org.dizitart.no2.objects.Index
-import org.dizitart.no2.objects.Indices
-import java.time.Instant
-
-@Indices(
- value = [
- Index(value = "discordServerId", type = IndexType.NonUnique),
- Index(value = "status", type = IndexType.NonUnique)
- ]
-)
-data class ServerStatusMonitor(
- @Id
- val id: String,
- @Deprecated("This field is updated automatically by the ServerStatusMonitorRepository, manually update with caution")
- var version: Long? = null,
-
- var discordServerId: String,
- var discordChannelId: String,
- var playerActivityDiscordChannelId: String? = null,
- var pvpKillFeedDiscordChannelId: String? = null,
-
- var hostname: String,
- var queryPort: Int,
-
- var apiHostname: String? = null,
- var apiPort: Int? = null,
- var apiUsername: String? = null,
- var apiPassword: String? = null,
-
- var status: ServerStatusMonitorStatus,
-
- var embedEnabled: Boolean = true,
- var displayServerDescription: Boolean,
- var displayPlayerGearLevel: Boolean,
-
- var currentEmbedMessageId: String? = null,
- var currentFailedAttempts: Int = 0,
- var currentFailedApiAttempts: Int = 0,
-
- var recentErrors: List = emptyList()
-) {
-
- val apiEnabled: Boolean
- get() = apiHostname != null && apiPort != null
-
- @Suppress("DEPRECATION") // this is the internal usage the warning is referring to
- val lastUpdated: Instant
- get() = Instant.ofEpochMilli(version!!)
-
- fun addError(throwable: Throwable, maxErrorsToKeep: Int) {
- recentErrors = recentErrors
- .takeLast((maxErrorsToKeep - 1).coerceAtLeast(0))
- .toMutableList()
- .apply {
- add(
- Error(
- message = "${throwable::class.simpleName}: ${throwable.message}",
- timestamp = Instant.now().epochSecond
- )
- )
- }
- }
-}
diff --git a/src/main/kotlin/de/darkatra/vrising/discord/persistence/model/ServerStatusMonitorStatus.kt b/src/main/kotlin/de/darkatra/vrising/discord/persistence/model/Status.kt
similarity index 67%
rename from src/main/kotlin/de/darkatra/vrising/discord/persistence/model/ServerStatusMonitorStatus.kt
rename to src/main/kotlin/de/darkatra/vrising/discord/persistence/model/Status.kt
index 56e1a06..10e16c5 100644
--- a/src/main/kotlin/de/darkatra/vrising/discord/persistence/model/ServerStatusMonitorStatus.kt
+++ b/src/main/kotlin/de/darkatra/vrising/discord/persistence/model/Status.kt
@@ -1,6 +1,6 @@
package de.darkatra.vrising.discord.persistence.model
-enum class ServerStatusMonitorStatus {
+enum class Status {
INACTIVE,
ACTIVE
}
diff --git a/src/main/kotlin/de/darkatra/vrising/discord/persistence/model/StatusAware.kt b/src/main/kotlin/de/darkatra/vrising/discord/persistence/model/StatusAware.kt
new file mode 100644
index 0000000..05a45e7
--- /dev/null
+++ b/src/main/kotlin/de/darkatra/vrising/discord/persistence/model/StatusAware.kt
@@ -0,0 +1,17 @@
+package de.darkatra.vrising.discord.persistence.model
+
+interface StatusAware {
+ val status: Status
+}
+
+fun Iterable.filterActive(): List {
+ return filter {
+ it.status == Status.ACTIVE
+ }
+}
+
+fun Iterable.filterInactive(): List {
+ return filter {
+ it.status == Status.INACTIVE
+ }
+}
diff --git a/src/main/kotlin/de/darkatra/vrising/discord/persistence/model/StatusMonitor.kt b/src/main/kotlin/de/darkatra/vrising/discord/persistence/model/StatusMonitor.kt
new file mode 100644
index 0000000..b9f5604
--- /dev/null
+++ b/src/main/kotlin/de/darkatra/vrising/discord/persistence/model/StatusMonitor.kt
@@ -0,0 +1,27 @@
+package de.darkatra.vrising.discord.persistence.model
+
+data class StatusMonitor(
+ override var status: Status,
+ @Transient
+ private var server: Server? = null,
+
+ var discordChannelId: String,
+
+ var displayServerDescription: Boolean,
+ var displayPlayerGearLevel: Boolean,
+
+ var currentEmbedMessageId: String? = null,
+ var currentFailedAttempts: Int = 0,
+ var currentFailedApiAttempts: Int = 0,
+
+ override var recentErrors: List = emptyList()
+) : ErrorAware, ServerAware, StatusAware {
+
+ override fun getServer(): Server {
+ return server!!
+ }
+
+ override fun setServer(server: Server) {
+ this.server = server
+ }
+}
diff --git a/src/main/kotlin/de/darkatra/vrising/discord/serverstatus/PlayerActivityFeedService.kt b/src/main/kotlin/de/darkatra/vrising/discord/serverstatus/PlayerActivityFeedService.kt
new file mode 100644
index 0000000..5cdf2ad
--- /dev/null
+++ b/src/main/kotlin/de/darkatra/vrising/discord/serverstatus/PlayerActivityFeedService.kt
@@ -0,0 +1,83 @@
+package de.darkatra.vrising.discord.serverstatus
+
+import de.darkatra.vrising.discord.BotProperties
+import de.darkatra.vrising.discord.clients.botcompanion.BotCompanionClient
+import de.darkatra.vrising.discord.clients.botcompanion.model.PlayerActivity
+import de.darkatra.vrising.discord.getDiscordChannel
+import de.darkatra.vrising.discord.persistence.model.PlayerActivityFeed
+import de.darkatra.vrising.discord.persistence.model.Status
+import de.darkatra.vrising.discord.tryCreateMessage
+import dev.kord.core.Kord
+import org.slf4j.LoggerFactory
+import org.springframework.stereotype.Service
+import java.time.Instant
+
+@Service
+class PlayerActivityFeedService(
+ private val botProperties: BotProperties,
+ private val botCompanionClient: BotCompanionClient
+) {
+
+ private val logger = LoggerFactory.getLogger(javaClass)
+
+ suspend fun updatePlayerActivityFeed(kord: Kord, playerActivityFeed: PlayerActivityFeed) {
+
+ if (!playerActivityFeed.getServer().apiEnabled) {
+ logger.debug("Skipping player activity feed update for server '${playerActivityFeed.getServer().id}' because apiEnabled is false.")
+ return
+ }
+
+ val playerActivityChannel = kord.getDiscordChannel(playerActivityFeed.discordChannelId).getOrElse {
+ logger.debug("Disabling player activity feed for server '${playerActivityFeed.getServer().id}' because the channel '${playerActivityFeed.discordChannelId}' does not seem to exist.")
+ playerActivityFeed.status = Status.INACTIVE
+ return
+ }
+
+ val playerActivities = botCompanionClient.getPlayerActivities(
+ playerActivityFeed.getServer().apiHostname!!,
+ playerActivityFeed.getServer().apiPort!!,
+ playerActivityFeed.getServer().getApiInterceptors()
+ ).getOrElse { e ->
+
+ logger.error("Exception updating the player activity feed for server '${playerActivityFeed.getServer().id}'", e)
+ playerActivityFeed.currentFailedAttempts += 1
+
+ if (botProperties.maxRecentErrors > 0) {
+ playerActivityFeed.addError(e, botProperties.maxRecentErrors)
+ }
+
+ if (botProperties.maxFailedApiAttempts != 0 && playerActivityFeed.currentFailedAttempts >= botProperties.maxFailedApiAttempts) {
+ logger.warn("Disabling the player activity feed for server '${playerActivityFeed.getServer().id}' because it exceeded the max failed api attempts.")
+ playerActivityFeed.status = Status.INACTIVE
+
+ // FIXME: mention the correct command to re-enable the player activity feed
+ playerActivityChannel.tryCreateMessage(
+ """Disabled the player activity feed for server '${playerActivityFeed.getServer().id}' because
+ |the bot companion did not respond successfully after ${botProperties.maxFailedApiAttempts} attempts.
+ |Please make sure the server-api-hostname and server-api-port are correct.
+ |You can re-enable the functionality using the update-server command.""".trimMargin()
+ )
+ }
+ return
+ }
+
+ playerActivityFeed.currentFailedAttempts = 0
+
+ playerActivities
+ .filter { playerActivity -> playerActivity.occurred.isAfter(playerActivityFeed.getLastUpdated()) }
+ .sortedWith(Comparator.comparing(PlayerActivity::occurred))
+ .forEach { playerActivity ->
+ val action = when (playerActivity.type) {
+ PlayerActivity.Type.CONNECTED -> "joined"
+ PlayerActivity.Type.DISCONNECTED -> "left"
+ }
+ playerActivityChannel.tryCreateMessage(
+ ": ${playerActivity.playerName} $action the server."
+ )
+ }
+
+ playerActivityFeed.lastUpdated = Instant.now().toEpochMilli()
+
+ logger.debug("Successfully updated the player activity feed for server '${playerActivityFeed.getServer().id}'.")
+ }
+}
diff --git a/src/main/kotlin/de/darkatra/vrising/discord/serverstatus/PvpKillFeedService.kt b/src/main/kotlin/de/darkatra/vrising/discord/serverstatus/PvpKillFeedService.kt
new file mode 100644
index 0000000..7267516
--- /dev/null
+++ b/src/main/kotlin/de/darkatra/vrising/discord/serverstatus/PvpKillFeedService.kt
@@ -0,0 +1,79 @@
+package de.darkatra.vrising.discord.serverstatus
+
+import de.darkatra.vrising.discord.BotProperties
+import de.darkatra.vrising.discord.clients.botcompanion.BotCompanionClient
+import de.darkatra.vrising.discord.clients.botcompanion.model.PvpKill
+import de.darkatra.vrising.discord.getDiscordChannel
+import de.darkatra.vrising.discord.persistence.model.PvpKillFeed
+import de.darkatra.vrising.discord.persistence.model.Status
+import de.darkatra.vrising.discord.tryCreateMessage
+import dev.kord.core.Kord
+import org.slf4j.LoggerFactory
+import org.springframework.stereotype.Service
+import java.time.Instant
+
+@Service
+class PvpKillFeedService(
+ private val botProperties: BotProperties,
+ private val botCompanionClient: BotCompanionClient
+) {
+
+ private val logger = LoggerFactory.getLogger(javaClass)
+
+ suspend fun updatePvpKillFeed(kord: Kord, pvpKillFeed: PvpKillFeed) {
+
+ if (!pvpKillFeed.getServer().apiEnabled) {
+ logger.debug("Skipping pvp kill feed update for server '${pvpKillFeed.getServer().id}' because apiEnabled is false.")
+ return
+ }
+
+ val pvpKillFeedChannel = kord.getDiscordChannel(pvpKillFeed.discordChannelId).getOrElse {
+ logger.debug("Disabling pvp kill feed for server '${pvpKillFeed.getServer().id}' because the channel '${pvpKillFeed.discordChannelId}' does not seem to exist.")
+ pvpKillFeed.status = Status.INACTIVE
+ return
+ }
+
+ val pvpKills = botCompanionClient.getPvpKills(
+ pvpKillFeed.getServer().apiHostname!!,
+ pvpKillFeed.getServer().apiPort!!,
+ pvpKillFeed.getServer().getApiInterceptors()
+ ).getOrElse { e ->
+
+ logger.error("Exception updating the pvp kill feed for server ${pvpKillFeed.getServer().id}", e)
+ pvpKillFeed.currentFailedAttempts += 1
+
+ if (botProperties.maxRecentErrors > 0) {
+ pvpKillFeed.addError(e, botProperties.maxRecentErrors)
+ }
+
+ if (botProperties.maxFailedApiAttempts != 0 && pvpKillFeed.currentFailedAttempts >= botProperties.maxFailedApiAttempts) {
+ logger.warn("Disabling the pvp kill feed for server '${pvpKillFeed.getServer().id}' because it exceeded the max failed api attempts.")
+ pvpKillFeed.status = Status.INACTIVE
+
+ // FIXME: mention the correct command to re-enable the player activity feed
+ pvpKillFeedChannel.tryCreateMessage(
+ """Disabled the pvp kill feed for server '${pvpKillFeed.getServer().id}' because
+ |the bot companion did not respond successfully after ${botProperties.maxFailedApiAttempts} attempts.
+ |Please make sure the server-api-hostname and server-api-port are correct.
+ |You can re-enable the functionality using the update-server command.""".trimMargin()
+ )
+ }
+ return
+ }
+
+ pvpKillFeed.currentFailedAttempts = 0
+
+ pvpKills
+ .filter { pvpKill -> pvpKill.occurred.isAfter(pvpKillFeed.getLastUpdated()) }
+ .sortedWith(Comparator.comparing(PvpKill::occurred))
+ .forEach { pvpKill ->
+ pvpKillFeedChannel.tryCreateMessage(
+ ": ${pvpKill.killer.name} (${pvpKill.killer.gearLevel}) killed ${pvpKill.victim.name} (${pvpKill.victim.gearLevel})."
+ )
+ }
+
+ pvpKillFeed.lastUpdated = Instant.now().toEpochMilli()
+
+ logger.debug("Successfully updated the pvp kill feed for server '${pvpKillFeed.getServer().id}'.")
+ }
+}
diff --git a/src/main/kotlin/de/darkatra/vrising/discord/serverstatus/ServerService.kt b/src/main/kotlin/de/darkatra/vrising/discord/serverstatus/ServerService.kt
new file mode 100644
index 0000000..8e8ccb2
--- /dev/null
+++ b/src/main/kotlin/de/darkatra/vrising/discord/serverstatus/ServerService.kt
@@ -0,0 +1,97 @@
+package de.darkatra.vrising.discord.serverstatus
+
+import de.darkatra.vrising.discord.persistence.OutdatedServerException
+import de.darkatra.vrising.discord.persistence.ServerRepository
+import de.darkatra.vrising.discord.persistence.model.Server
+import de.darkatra.vrising.discord.persistence.model.Status
+import de.darkatra.vrising.discord.persistence.model.filterActive
+import de.darkatra.vrising.discord.persistence.model.filterInactive
+import dev.kord.core.Kord
+import org.slf4j.LoggerFactory
+import org.slf4j.MDC
+import org.springframework.stereotype.Service
+import java.time.Instant
+import java.time.temporal.ChronoUnit
+
+@Service
+class ServerService(
+ private val serverRepository: ServerRepository,
+ private val statusMonitorService: StatusMonitorService,
+ private val playerActivityFeedService: PlayerActivityFeedService,
+ private val pvpKillFeedService: PvpKillFeedService
+) {
+
+ private val logger = LoggerFactory.getLogger(javaClass)
+
+ suspend fun updateServers(kord: Kord) {
+
+ val activeServers = serverRepository.getServers().filterActive()
+ activeServers.forEach { server ->
+
+ MDC.put("server-id", server.id)
+
+ updateStatusMonitor(kord, server)
+ updatePlayerActivityFeed(kord, server)
+ updatePvpKillFeed(kord, server)
+
+ try {
+ serverRepository.updateServer(server)
+ } catch (e: OutdatedServerException) {
+ logger.debug("Server was updated or deleted by another thread. Will ignore this exception and proceed with the next server.", e)
+ }
+
+ MDC.clear()
+ }
+ }
+
+ suspend fun cleanupInactiveServers(kord: Kord) {
+
+ val inactiveServers = serverRepository.getServers().filterInactive()
+ if (inactiveServers.isEmpty()) {
+ logger.info("No inactive servers to clean up.")
+ return
+ }
+
+ val sevenDaysAgo = Instant.now().minus(7, ChronoUnit.DAYS)
+ inactiveServers.forEach { server ->
+ if (server.lastUpdated.isBefore(sevenDaysAgo)) {
+ serverRepository.removeServer(server.id)
+ }
+ }
+
+ logger.info("Successfully removed ${inactiveServers.count()} servers with no active feature.")
+ }
+
+ private suspend fun updateStatusMonitor(kord: Kord, server: Server) {
+
+ val statusMonitor = server.statusMonitor
+ if (statusMonitor == null || statusMonitor.status == Status.INACTIVE) {
+ logger.debug("No active status monitor to update for server '${server.id}'.")
+ return
+ }
+
+ statusMonitorService.updateStatusMonitor(kord, statusMonitor)
+ }
+
+ private suspend fun updatePlayerActivityFeed(kord: Kord, server: Server) {
+
+ val playerActivityFeed = server.playerActivityFeed
+ if (playerActivityFeed == null || playerActivityFeed.status == Status.INACTIVE) {
+ logger.debug("No active player activity feed to update for server '${server.id}'.")
+ return
+ }
+
+ playerActivityFeedService.updatePlayerActivityFeed(kord, playerActivityFeed)
+ }
+
+ private suspend fun updatePvpKillFeed(kord: Kord, server: Server) {
+
+ val pvpKillFeed = server.pvpKillFeed
+ if (pvpKillFeed == null || pvpKillFeed.status == Status.INACTIVE) {
+ logger.debug("No active pvp kill feed to update for server '${server.id}'.")
+ return
+ }
+
+ pvpKillFeedService.updatePvpKillFeed(kord, pvpKillFeed)
+ }
+}
diff --git a/src/main/kotlin/de/darkatra/vrising/discord/serverstatus/ServerStatusMonitorService.kt b/src/main/kotlin/de/darkatra/vrising/discord/serverstatus/ServerStatusMonitorService.kt
deleted file mode 100644
index 0f0207c..0000000
--- a/src/main/kotlin/de/darkatra/vrising/discord/serverstatus/ServerStatusMonitorService.kt
+++ /dev/null
@@ -1,324 +0,0 @@
-package de.darkatra.vrising.discord.serverstatus
-
-import de.darkatra.vrising.discord.BotProperties
-import de.darkatra.vrising.discord.clients.botcompanion.BotCompanionClient
-import de.darkatra.vrising.discord.clients.botcompanion.model.PlayerActivity
-import de.darkatra.vrising.discord.clients.botcompanion.model.PvpKill
-import de.darkatra.vrising.discord.clients.serverquery.ServerQueryClient
-import de.darkatra.vrising.discord.getDiscordChannel
-import de.darkatra.vrising.discord.persistence.ServerStatusMonitorRepository
-import de.darkatra.vrising.discord.persistence.model.ServerStatusMonitor
-import de.darkatra.vrising.discord.persistence.model.ServerStatusMonitorStatus
-import de.darkatra.vrising.discord.serverstatus.exceptions.OutdatedServerStatusMonitorException
-import de.darkatra.vrising.discord.serverstatus.model.ServerInfo
-import dev.kord.common.entity.Snowflake
-import dev.kord.core.Kord
-import dev.kord.core.behavior.channel.createEmbed
-import dev.kord.core.behavior.edit
-import dev.kord.core.exception.EntityNotFoundException
-import dev.kord.rest.builder.message.EmbedBuilder
-import dev.kord.rest.builder.message.embed
-import org.slf4j.LoggerFactory
-import org.slf4j.MDC
-import org.springframework.http.client.ClientHttpRequestInterceptor
-import org.springframework.http.client.support.BasicAuthenticationInterceptor
-import org.springframework.stereotype.Service
-import java.time.Instant
-import java.time.temporal.ChronoUnit
-
-@Service
-class ServerStatusMonitorService(
- private val serverStatusMonitorRepository: ServerStatusMonitorRepository,
- private val serverQueryClient: ServerQueryClient,
- private val botCompanionClient: BotCompanionClient,
- private val botProperties: BotProperties
-) {
-
- private val logger = LoggerFactory.getLogger(javaClass)
-
- suspend fun updateServerStatusMonitors(kord: Kord) {
-
- serverStatusMonitorRepository.getServerStatusMonitors(status = ServerStatusMonitorStatus.ACTIVE).forEach { serverStatusMonitor ->
-
- MDC.put("server-status-monitor-id", serverStatusMonitor.id)
-
- updateServerStatusMonitor(kord, serverStatusMonitor)
- updatePlayerActivityFeed(kord, serverStatusMonitor)
- updatePvpKillFeed(kord, serverStatusMonitor)
- try {
- serverStatusMonitorRepository.updateServerStatusMonitor(serverStatusMonitor)
- } catch (e: OutdatedServerStatusMonitorException) {
- logger.debug("Server status monitor was updated or deleted by another thread. Will ignore this exception and proceed as usual.", e)
- }
-
- MDC.clear()
- }
- }
-
- suspend fun cleanupInactiveServerStatusMonitors(kord: Kord) {
-
- val sevenDaysAgo = Instant.now().minus(7, ChronoUnit.DAYS)
- val inactiveServerStatusMonitors = serverStatusMonitorRepository.getServerStatusMonitors(status = ServerStatusMonitorStatus.INACTIVE)
- inactiveServerStatusMonitors.forEach { serverStatusMonitor ->
- if (serverStatusMonitor.lastUpdated.isBefore(sevenDaysAgo)) {
- serverStatusMonitorRepository.removeServerStatusMonitor(serverStatusMonitor.id)
- }
- }
-
- logger.info("Successfully removed ${inactiveServerStatusMonitors.count()} inactive server status monitors.")
- }
-
- private suspend fun updateServerStatusMonitor(kord: Kord, serverStatusMonitor: ServerStatusMonitor) {
-
- if (!serverStatusMonitor.embedEnabled) {
- logger.debug("Skipping server monitor '${serverStatusMonitor.id}' because embedEnabled is false.")
- return
- }
-
- val channel = kord.getDiscordChannel(serverStatusMonitor.discordChannelId).getOrElse {
- logger.debug("Disabling server monitor '${serverStatusMonitor.id}' because the channel '${serverStatusMonitor.discordChannelId}' does not seem to exist.")
- serverStatusMonitor.status = ServerStatusMonitorStatus.INACTIVE
- return
- }
-
- val serverInfo = serverQueryClient.getServerStatus(
- serverStatusMonitor.hostname,
- serverStatusMonitor.queryPort
- ).map { serverStatus ->
- ServerInfo.of(serverStatus)
- }.getOrElse { e ->
-
- logger.error("Exception fetching the status of '${serverStatusMonitor.id}'.", e)
- serverStatusMonitor.currentFailedAttempts += 1
-
- if (botProperties.maxRecentErrors > 0) {
- serverStatusMonitor.addError(e, botProperties.maxRecentErrors)
- }
-
- try {
-
- if (serverStatusMonitor.currentEmbedMessageId == null && serverStatusMonitor.currentFailedAttempts == 1) {
- channel.createMessage(
- """The status check for your status monitor '${serverStatusMonitor.id}' failed.
- |Please check the detailed error message using the get-server-details command.""".trimMargin()
- )
- }
-
- if (botProperties.maxFailedAttempts != 0 && serverStatusMonitor.currentFailedAttempts >= botProperties.maxFailedAttempts) {
- logger.warn("Disabling server monitor '${serverStatusMonitor.id}' because it exceeded the max failed attempts.")
- serverStatusMonitor.status = ServerStatusMonitorStatus.INACTIVE
-
- channel.createMessage(
- """Disabled server status monitor '${serverStatusMonitor.id}' because the server did not
- |respond successfully after ${botProperties.maxFailedAttempts} attempts.
- |Please make sure the server is running and is accessible from the internet to use this bot.
- |You can re-enable the server status monitor using the update-server command.""".trimMargin()
- )
- }
- } catch (e: Exception) {
- logger.warn("Could not post status message for monitor '${serverStatusMonitor.id}'.", e)
- }
- return
- }
-
- if (serverStatusMonitor.apiEnabled && serverStatusMonitor.displayPlayerGearLevel) {
-
- val characters = botCompanionClient.getCharacters(
- serverStatusMonitor.apiHostname!!,
- serverStatusMonitor.apiPort!!,
- getInterceptors(serverStatusMonitor)
- ).getOrElse { e ->
-
- logger.warn("Could not resolve characters for server monitor '${serverStatusMonitor.id}'. Player Gear level will not be displayed.", e)
- serverStatusMonitor.currentFailedApiAttempts += 1
-
- if (botProperties.maxRecentErrors > 0) {
- serverStatusMonitor.addError(e, botProperties.maxRecentErrors)
- }
-
- try {
-
- if (botProperties.maxFailedApiAttempts != 0 && serverStatusMonitor.currentFailedApiAttempts >= botProperties.maxFailedApiAttempts) {
- logger.warn("Disabling displayPlayerGearLevel for server monitor '${serverStatusMonitor.id}' because it exceeded the max failed api attempts.")
- serverStatusMonitor.displayPlayerGearLevel = false
-
- channel.createMessage(
- """Disabled displayPlayerGearLevel for server status monitor '${serverStatusMonitor.id}' because
- |the bot companion did not respond successfully after ${botProperties.maxFailedApiAttempts} attempts.
- |Please make sure the server-api-hostname and server-api-port are correct.
- |You can re-enable the functionality using the update-server command.""".trimMargin()
- )
- }
- } catch (e: Exception) {
- logger.warn("Could not post status message for monitor '${serverStatusMonitor.id}'", e)
- }
- return
- }
-
- serverInfo.enrichCompanionData(characters)
- serverStatusMonitor.currentFailedApiAttempts = 0
- }
-
- val embedCustomizer: (embedBuilder: EmbedBuilder) -> Unit = { embedBuilder ->
- ServerStatusEmbed.buildEmbed(
- serverInfo,
- serverStatusMonitor.apiEnabled,
- serverStatusMonitor.displayServerDescription,
- serverStatusMonitor.displayPlayerGearLevel,
- embedBuilder
- )
- }
-
- val currentEmbedMessageId = serverStatusMonitor.currentEmbedMessageId
- if (currentEmbedMessageId != null) {
- try {
- channel.getMessage(Snowflake(currentEmbedMessageId))
- .edit { embed(embedCustomizer) }
-
- serverStatusMonitor.currentFailedAttempts = 0
-
- logger.debug("Successfully updated the status of server monitor '${serverStatusMonitor.id}'.")
- return
- } catch (e: EntityNotFoundException) {
- serverStatusMonitor.currentEmbedMessageId = null
- }
- }
-
- serverStatusMonitor.currentEmbedMessageId = channel.createEmbed(embedCustomizer).id.toString()
- serverStatusMonitor.currentFailedAttempts = 0
-
- logger.debug("Successfully updated the status and persisted the embedId of server monitor '${serverStatusMonitor.id}'.")
- }
-
- private suspend fun updatePlayerActivityFeed(kord: Kord, serverStatusMonitor: ServerStatusMonitor) {
-
- if (!serverStatusMonitor.apiEnabled) {
- logger.debug("Skipping player activity feed update for server monitor '${serverStatusMonitor.id}' because apiEnabled is false.")
- return
- }
-
- val playerActivityDiscordChannelId = serverStatusMonitor.playerActivityDiscordChannelId ?: return
- val playerActivityChannel = kord.getDiscordChannel(playerActivityDiscordChannelId).getOrElse {
- logger.debug("Disabling player activity feed for server monitor '${serverStatusMonitor.id}' because the channel '${playerActivityDiscordChannelId}' does not seem to exist.")
- serverStatusMonitor.playerActivityDiscordChannelId = null
- return
- }
-
- val playerActivities = botCompanionClient.getPlayerActivities(
- serverStatusMonitor.apiHostname!!,
- serverStatusMonitor.apiPort!!,
- getInterceptors(serverStatusMonitor)
- ).getOrElse { e ->
-
- logger.error("Exception updating the player activity feed of '${serverStatusMonitor.id}'", e)
- serverStatusMonitor.currentFailedApiAttempts += 1
-
- if (botProperties.maxRecentErrors > 0) {
- serverStatusMonitor.addError(e, botProperties.maxRecentErrors)
- }
-
- try {
- if (botProperties.maxFailedApiAttempts != 0 && serverStatusMonitor.currentFailedApiAttempts >= botProperties.maxFailedApiAttempts) {
- logger.warn("Disabling the player activity feed for server monitor '${serverStatusMonitor.id}' because it exceeded the max failed api attempts.")
- serverStatusMonitor.playerActivityDiscordChannelId = null
-
- playerActivityChannel.createMessage(
- """Disabled the player activity feed for server status monitor '${serverStatusMonitor.id}' because
- |the bot companion did not respond successfully after ${botProperties.maxFailedApiAttempts} attempts.
- |Please make sure the server-api-hostname and server-api-port are correct.
- |You can re-enable the functionality using the update-server command.""".trimMargin()
- )
- }
- } catch (e: Exception) {
- logger.warn("Could not post status message for monitor '${serverStatusMonitor.id}'", e)
- }
- return
- }
-
- serverStatusMonitor.currentFailedApiAttempts = 0
-
- playerActivities
- .filter { playerActivity -> playerActivity.occurred.isAfter(serverStatusMonitor.lastUpdated) }
- .sortedWith(Comparator.comparing(PlayerActivity::occurred))
- .forEach { playerActivity ->
- val action = when (playerActivity.type) {
- PlayerActivity.Type.CONNECTED -> "joined"
- PlayerActivity.Type.DISCONNECTED -> "left"
- }
- playerActivityChannel.createMessage(
- ": ${playerActivity.playerName} $action the server."
- )
- }
-
- logger.debug("Successfully updated the player activity feed of server monitor '${serverStatusMonitor.id}'.")
- }
-
- private suspend fun updatePvpKillFeed(kord: Kord, serverStatusMonitor: ServerStatusMonitor) {
-
- if (!serverStatusMonitor.apiEnabled) {
- logger.debug("Skipping pvp kill feed update for server monitor '${serverStatusMonitor.id}' because apiEnabled is false.")
- return
- }
-
- val pvpKillFeedDiscordChannelId = serverStatusMonitor.pvpKillFeedDiscordChannelId ?: return
- val pvpKillFeedChannel = kord.getDiscordChannel(pvpKillFeedDiscordChannelId).getOrElse {
- logger.debug("Disabling pvp kill feed for server monitor '${serverStatusMonitor.id}' because the channel '${pvpKillFeedDiscordChannelId}' does not seem to exist.")
- serverStatusMonitor.pvpKillFeedDiscordChannelId = null
- return
- }
-
- val pvpKills = botCompanionClient.getPvpKills(
- serverStatusMonitor.apiHostname!!,
- serverStatusMonitor.apiPort!!,
- getInterceptors(serverStatusMonitor)
- ).getOrElse { e ->
-
- logger.error("Exception updating the pvp kill feed of ${serverStatusMonitor.id}", e)
- serverStatusMonitor.currentFailedApiAttempts += 1
-
- if (botProperties.maxRecentErrors > 0) {
- serverStatusMonitor.addError(e, botProperties.maxRecentErrors)
- }
-
- try {
- if (botProperties.maxFailedApiAttempts != 0 && serverStatusMonitor.currentFailedApiAttempts >= botProperties.maxFailedApiAttempts) {
- logger.warn("Disabling the pvp kill feed for server monitor '${serverStatusMonitor.id}' because it exceeded the max failed api attempts.")
- serverStatusMonitor.pvpKillFeedDiscordChannelId = null
-
- pvpKillFeedChannel.createMessage(
- """Disabled the pvp kill feed for server status monitor '${serverStatusMonitor.id}' because
- |the bot companion did not respond successfully after ${botProperties.maxFailedApiAttempts} attempts.
- |Please make sure the server-api-hostname and server-api-port are correct.
- |You can re-enable the functionality using the update-server command.""".trimMargin()
- )
- }
- } catch (e: Exception) {
- logger.warn("Could not post status message for monitor '${serverStatusMonitor.id}'", e)
- }
- return
- }
-
- serverStatusMonitor.currentFailedApiAttempts = 0
-
- pvpKills
- .filter { pvpKill -> pvpKill.occurred.isAfter(serverStatusMonitor.lastUpdated) }
- .sortedWith(Comparator.comparing(PvpKill::occurred))
- .forEach { pvpKill ->
- pvpKillFeedChannel.createMessage(
- ": ${pvpKill.killer.name} (${pvpKill.killer.gearLevel}) killed ${pvpKill.victim.name} (${pvpKill.victim.gearLevel})."
- )
- }
-
- logger.debug("Successfully updated the pvp kill feed of server monitor '${serverStatusMonitor.id}'.")
- }
-
- private fun getInterceptors(serverStatusMonitor: ServerStatusMonitor): List {
-
- val (_, _, _, _, _, _, _, _, _, _, apiUsername, apiPassword, _, _, _, _, _, _) = serverStatusMonitor
-
- return when (apiUsername != null && apiPassword != null) {
- true -> listOf(BasicAuthenticationInterceptor(apiUsername, apiPassword))
- false -> emptyList()
- }
- }
-}
diff --git a/src/main/kotlin/de/darkatra/vrising/discord/serverstatus/StatusMonitorService.kt b/src/main/kotlin/de/darkatra/vrising/discord/serverstatus/StatusMonitorService.kt
new file mode 100644
index 0000000..9a32058
--- /dev/null
+++ b/src/main/kotlin/de/darkatra/vrising/discord/serverstatus/StatusMonitorService.kt
@@ -0,0 +1,140 @@
+package de.darkatra.vrising.discord.serverstatus
+
+import de.darkatra.vrising.discord.BotProperties
+import de.darkatra.vrising.discord.clients.botcompanion.BotCompanionClient
+import de.darkatra.vrising.discord.clients.serverquery.ServerQueryClient
+import de.darkatra.vrising.discord.getDiscordChannel
+import de.darkatra.vrising.discord.persistence.model.Status
+import de.darkatra.vrising.discord.persistence.model.StatusMonitor
+import de.darkatra.vrising.discord.serverstatus.model.ServerInfo
+import de.darkatra.vrising.discord.tryCreateMessage
+import dev.kord.common.entity.Snowflake
+import dev.kord.core.Kord
+import dev.kord.core.behavior.channel.createEmbed
+import dev.kord.core.behavior.edit
+import dev.kord.rest.builder.message.EmbedBuilder
+import dev.kord.rest.builder.message.embed
+import org.slf4j.LoggerFactory
+import org.springframework.stereotype.Service
+
+@Service
+class StatusMonitorService(
+ private val botProperties: BotProperties,
+ private val serverQueryClient: ServerQueryClient,
+ private val botCompanionClient: BotCompanionClient
+) {
+
+ private val logger = LoggerFactory.getLogger(javaClass)
+
+ suspend fun updateStatusMonitor(kord: Kord, statusMonitor: StatusMonitor) {
+
+ val channel = kord.getDiscordChannel(statusMonitor.discordChannelId).getOrElse {
+ logger.debug("Disabling server monitor for server '${statusMonitor.getServer().id}' because the channel '${statusMonitor.discordChannelId}' does not seem to exist.")
+ statusMonitor.status = Status.INACTIVE
+ return
+ }
+
+ val serverInfo = serverQueryClient.getServerStatus(
+ statusMonitor.getServer().hostname,
+ statusMonitor.getServer().queryPort
+ ).map { serverStatus ->
+ ServerInfo.of(serverStatus)
+ }.getOrElse { e ->
+
+ logger.error("Exception updating the status monitor for server '${statusMonitor.getServer().id}'.", e)
+ statusMonitor.currentFailedAttempts += 1
+
+ if (botProperties.maxRecentErrors > 0) {
+ statusMonitor.addError(e, botProperties.maxRecentErrors)
+ }
+
+
+ if (statusMonitor.currentEmbedMessageId == null && statusMonitor.currentFailedAttempts == 1) {
+ // FIXME: mention the correct command to retrieve the error messages for the status monitor
+ channel.tryCreateMessage(
+ """Failed to update the status monitor for server '${statusMonitor.getServer().id}'.
+ |Please check the detailed error message using the get-server-details command.""".trimMargin()
+ )
+ }
+
+ if (botProperties.maxFailedAttempts != 0 && statusMonitor.currentFailedAttempts >= botProperties.maxFailedAttempts) {
+ logger.warn("Disabling server monitor for server '${statusMonitor.getServer().id}' because it exceeded the max failed attempts.")
+ statusMonitor.status = Status.INACTIVE
+
+ // FIXME: mention the correct command to re-enable the status monitor
+ channel.tryCreateMessage(
+ """Disabled status monitor for server '${statusMonitor.getServer().id}' because the server did not
+ |respond successfully after ${botProperties.maxFailedAttempts} attempts.
+ |Please make sure the server is running and is accessible from the internet to use this bot.
+ |You can re-enable the server status monitor using the update-server command.""".trimMargin()
+ )
+ }
+ return
+ }
+
+ if (statusMonitor.getServer().apiEnabled && statusMonitor.displayPlayerGearLevel) {
+
+ val characters = botCompanionClient.getCharacters(
+ statusMonitor.getServer().apiHostname!!,
+ statusMonitor.getServer().apiPort!!,
+ statusMonitor.getServer().getApiInterceptors()
+ ).getOrElse { e ->
+
+ logger.warn("Could not resolve characters for status monitor for server '${statusMonitor.getServer().id}'.", e)
+ statusMonitor.currentFailedApiAttempts += 1
+
+ if (botProperties.maxRecentErrors > 0) {
+ statusMonitor.addError(e, botProperties.maxRecentErrors)
+ }
+
+
+ if (botProperties.maxFailedApiAttempts != 0 && statusMonitor.currentFailedApiAttempts >= botProperties.maxFailedApiAttempts) {
+ logger.warn("Disabling displayPlayerGearLevel for status monitor of server '${statusMonitor.getServer().id}' because it exceeded the max failed api attempts.")
+ statusMonitor.displayPlayerGearLevel = false
+
+ // FIXME: mention the correct command to re-enable the status monitor
+ channel.tryCreateMessage(
+ """The status monitor for server '${statusMonitor.getServer().id}' will no longer display the players gear level because
+ |the bot companion did not respond successfully after ${botProperties.maxFailedApiAttempts} attempts.
+ |Please make sure the server-api-hostname and server-api-port are correct.
+ |You can re-enable the functionality using the update-server command.""".trimMargin()
+ )
+ }
+ return
+ }
+
+ serverInfo.enrichCompanionData(characters)
+ statusMonitor.currentFailedApiAttempts = 0
+ }
+
+ val embedCustomizer: (embedBuilder: EmbedBuilder) -> Unit = { embedBuilder ->
+ ServerStatusEmbed.buildEmbed(
+ serverInfo,
+ statusMonitor.getServer().apiEnabled,
+ statusMonitor.displayServerDescription,
+ statusMonitor.displayPlayerGearLevel,
+ embedBuilder
+ )
+ }
+
+ val currentEmbedMessageId = statusMonitor.currentEmbedMessageId
+ if (currentEmbedMessageId != null) {
+ try {
+ channel.getMessage(Snowflake(currentEmbedMessageId))
+ .edit { embed(embedCustomizer) }
+
+ statusMonitor.currentFailedAttempts = 0
+
+ logger.debug("Successfully updated the status monitor for server '${statusMonitor.getServer().id}'.")
+ return
+ } catch (e: Exception) {
+ statusMonitor.currentEmbedMessageId = null
+ }
+ }
+
+ statusMonitor.currentEmbedMessageId = channel.createEmbed(embedCustomizer).id.toString()
+ statusMonitor.currentFailedAttempts = 0
+
+ logger.debug("Successfully updated the status and persisted the embedId for server monitor of server '${statusMonitor.getServer().id}'.")
+ }
+}
diff --git a/src/main/kotlin/de/darkatra/vrising/discord/serverstatus/exceptions/InvalidDiscordChannelException.kt b/src/main/kotlin/de/darkatra/vrising/discord/serverstatus/exceptions/InvalidDiscordChannelException.kt
deleted file mode 100644
index 03cfd99..0000000
--- a/src/main/kotlin/de/darkatra/vrising/discord/serverstatus/exceptions/InvalidDiscordChannelException.kt
+++ /dev/null
@@ -1,5 +0,0 @@
-package de.darkatra.vrising.discord.serverstatus.exceptions
-
-import de.darkatra.vrising.discord.BotException
-
-class InvalidDiscordChannelException(message: String, cause: Throwable? = null) : BotException(message, cause)
diff --git a/src/main/kotlin/de/darkatra/vrising/discord/serverstatus/exceptions/OutdatedServerStatusMonitorException.kt b/src/main/kotlin/de/darkatra/vrising/discord/serverstatus/exceptions/OutdatedServerStatusMonitorException.kt
deleted file mode 100644
index ab53976..0000000
--- a/src/main/kotlin/de/darkatra/vrising/discord/serverstatus/exceptions/OutdatedServerStatusMonitorException.kt
+++ /dev/null
@@ -1,5 +0,0 @@
-package de.darkatra.vrising.discord.serverstatus.exceptions
-
-import de.darkatra.vrising.discord.BotException
-
-class OutdatedServerStatusMonitorException(message: String) : BotException(message)
diff --git a/src/test/kotlin/de/darkatra/vrising/discord/DatabaseConfigurationTestUtils.kt b/src/test/kotlin/de/darkatra/vrising/discord/DatabaseConfigurationTestUtils.kt
index 9aa609e..c912393 100644
--- a/src/test/kotlin/de/darkatra/vrising/discord/DatabaseConfigurationTestUtils.kt
+++ b/src/test/kotlin/de/darkatra/vrising/discord/DatabaseConfigurationTestUtils.kt
@@ -7,14 +7,24 @@ import java.io.File
object DatabaseConfigurationTestUtils {
+ val DATABASE_FILE_V1_2_x = File(DatabaseConfigurationTestUtils::class.java.getResource("/persistence/v1.2.db")!!.toURI())
+ val DATABASE_FILE_V2_10_5 = File(DatabaseConfigurationTestUtils::class.java.getResource("/persistence/v2.10.5.db")!!.toURI())
private val logger = LoggerFactory.getLogger(javaClass)
- fun getTestDatabase(): Nitrite {
+ fun getTestDatabase(fromTemplate: File? = null): Nitrite {
+
+ val databaseFile = File.createTempFile("v-rising-bot", ".db").also {
+ logger.info("Test Db location: " + it.absolutePath)
+ }
+
+ if (fromTemplate != null) {
+ logger.info("Loading template from '${fromTemplate.absolutePath}'.")
+ fromTemplate.copyTo(databaseFile, overwrite = true)
+ }
+
return Nitrite.builder()
.compressed()
- .filePath(File.createTempFile("v-rising-bot", ".db").also {
- logger.info("Test Db location: " + it.absolutePath)
- })
+ .filePath(databaseFile)
.openOrCreate()
}
diff --git a/src/test/kotlin/de/darkatra/vrising/discord/RuntimeHintsTest.kt b/src/test/kotlin/de/darkatra/vrising/discord/RuntimeHintsTest.kt
index 9b607da..100e1c2 100644
--- a/src/test/kotlin/de/darkatra/vrising/discord/RuntimeHintsTest.kt
+++ b/src/test/kotlin/de/darkatra/vrising/discord/RuntimeHintsTest.kt
@@ -1,7 +1,17 @@
package de.darkatra.vrising.discord
+import de.darkatra.vrising.discord.clients.botcompanion.model.Character
+import de.darkatra.vrising.discord.clients.botcompanion.model.PlayerActivity
+import de.darkatra.vrising.discord.clients.botcompanion.model.PvpKill
+import de.darkatra.vrising.discord.clients.botcompanion.model.VBlood
import de.darkatra.vrising.discord.migration.Schema
-import de.darkatra.vrising.discord.persistence.model.ServerStatusMonitor
+import de.darkatra.vrising.discord.persistence.model.Error
+import de.darkatra.vrising.discord.persistence.model.Leaderboard
+import de.darkatra.vrising.discord.persistence.model.PlayerActivityFeed
+import de.darkatra.vrising.discord.persistence.model.PvpKillFeed
+import de.darkatra.vrising.discord.persistence.model.Server
+import de.darkatra.vrising.discord.persistence.model.Status
+import de.darkatra.vrising.discord.persistence.model.StatusMonitor
import org.junit.jupiter.api.Test
import org.springframework.aot.hint.annotation.RegisterReflectionForBinding
import org.springframework.boot.test.context.SpringBootTest
@@ -18,7 +28,27 @@ class RuntimeHintsTest {
// workaround to generate runtime hints for unit tests
}
+ // should use the same hints and reflection bindings as in Bot.kt
@ImportRuntimeHints(BotRuntimeHints::class)
- @RegisterReflectionForBinding(BotProperties::class, Schema::class, ServerStatusMonitor::class)
+ @RegisterReflectionForBinding(
+ // properties
+ BotProperties::class,
+ // database
+ Error::class,
+ Leaderboard::class,
+ Schema::class,
+ PlayerActivityFeed::class,
+ PvpKillFeed::class,
+ Server::class,
+ Status::class,
+ StatusMonitor::class,
+ // http
+ Character::class,
+ PlayerActivity::class,
+ PlayerActivity.Type::class,
+ PvpKill::class,
+ PvpKill.Player::class,
+ VBlood::class,
+ )
class TestConfiguration
}
diff --git a/src/test/kotlin/de/darkatra/vrising/discord/migration/DatabaseMigrationServiceTest.kt b/src/test/kotlin/de/darkatra/vrising/discord/migration/DatabaseMigrationServiceTest.kt
index 29c6f76..607ee83 100644
--- a/src/test/kotlin/de/darkatra/vrising/discord/migration/DatabaseMigrationServiceTest.kt
+++ b/src/test/kotlin/de/darkatra/vrising/discord/migration/DatabaseMigrationServiceTest.kt
@@ -1,101 +1,207 @@
package de.darkatra.vrising.discord.migration
import de.darkatra.vrising.discord.DatabaseConfigurationTestUtils
-import de.darkatra.vrising.discord.persistence.model.ServerStatusMonitor
+import de.darkatra.vrising.discord.persistence.model.Server
+import de.darkatra.vrising.discord.persistence.model.Status
import org.assertj.core.api.Assertions.assertThat
import org.dizitart.no2.Document
-import org.dizitart.no2.Nitrite
-import org.dizitart.no2.util.ObjectUtils
-import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
-import org.junit.jupiter.api.condition.DisabledInNativeImage
-@DisabledInNativeImage
class DatabaseMigrationServiceTest {
- private lateinit var database: Nitrite
-
- @BeforeEach
- fun setUp() {
- database = DatabaseConfigurationTestUtils.getTestDatabase()
- }
-
@Test
fun `should perform database migration when no schema was found`() {
- val databaseMigrationService = DatabaseMigrationService(
- database = database,
- appVersionFromPom = "1.5.0"
- )
+ DatabaseConfigurationTestUtils.getTestDatabase().use { database ->
- assertThat(databaseMigrationService.migrateToLatestVersion()).isTrue()
+ val databaseMigrationService = DatabaseMigrationService(
+ database = database,
+ appVersionFromPom = "1.5.0"
+ )
- val repository = database.getRepository(Schema::class.java)
+ assertThat(databaseMigrationService.migrateToLatestVersion()).isTrue()
- val schemas = repository.find().toList()
- assertThat(schemas).hasSize(1)
- assertThat(schemas).first().extracting(Schema::appVersion).isEqualTo("V1.5.0")
+ val repository = database.getRepository(Schema::class.java)
+
+ val schemas = repository.find().toList()
+ assertThat(schemas).hasSize(1)
+ assertThat(schemas).first().extracting(Schema::appVersion).isEqualTo("V1.5.0")
+ }
}
@Test
fun `should not perform database migration when schema matches the current version`() {
- val repository = database.getRepository(Schema::class.java)
- repository.insert(Schema(appVersion = "V1.4.0"))
- repository.insert(Schema(appVersion = "V1.5.0"))
- repository.insert(Schema(appVersion = "V1.6.0"))
- repository.insert(Schema(appVersion = "V1.8.0"))
- repository.insert(Schema(appVersion = "V2.2.0"))
- repository.insert(Schema(appVersion = "V2.3.0"))
- repository.insert(Schema(appVersion = "V2.9.0"))
- repository.insert(Schema(appVersion = "V2.10.0"))
- repository.insert(Schema(appVersion = "V2.10.2"))
-
- val databaseMigrationService = DatabaseMigrationService(
- database = database,
- appVersionFromPom = "2.10.2"
- )
-
- assertThat(databaseMigrationService.migrateToLatestVersion()).isFalse()
-
- val schemas = repository.find().toList()
- assertThat(schemas).hasSize(9)
+ DatabaseConfigurationTestUtils.getTestDatabase().use { database ->
+
+ val repository = database.getRepository(Schema::class.java)
+ repository.insert(Schema(appVersion = "V1.4.0"))
+ repository.insert(Schema(appVersion = "V1.5.0"))
+ repository.insert(Schema(appVersion = "V1.6.0"))
+ repository.insert(Schema(appVersion = "V1.8.0"))
+ repository.insert(Schema(appVersion = "V2.2.0"))
+ repository.insert(Schema(appVersion = "V2.3.0"))
+ repository.insert(Schema(appVersion = "V2.9.0"))
+ repository.insert(Schema(appVersion = "V2.10.0"))
+ repository.insert(Schema(appVersion = "V2.10.2"))
+ repository.insert(Schema(appVersion = "V2.11.0"))
+
+ val databaseMigrationService = DatabaseMigrationService(
+ database = database,
+ appVersionFromPom = "2.11.0"
+ )
+
+ assertThat(databaseMigrationService.migrateToLatestVersion()).isFalse()
+
+ val schemas = repository.find().toList()
+ assertThat(schemas).hasSize(10)
+ }
}
@Test
fun `should migrate existing ServerStatusMonitor documents to new collection and cleanup obsolete data`() {
- val repository = database.getRepository(Schema::class.java)
- repository.insert(Schema(appVersion = "V2.1.0"))
+ DatabaseConfigurationTestUtils.getTestDatabase().use { database ->
+
+ val repository = database.getRepository(Schema::class.java)
+ repository.insert(Schema(appVersion = "V2.1.0"))
- val databaseMigrationService = DatabaseMigrationService(
- database = database,
- appVersionFromPom = "2.9.0"
- )
+ val databaseMigrationService = DatabaseMigrationService(
+ database = database,
+ appVersionFromPom = "2.9.0"
+ )
- val oldCollection = database.getCollection("de.darkatra.vrising.discord.ServerStatusMonitor")
- val newCollection = database.getCollection(ObjectUtils.findObjectStoreName(ServerStatusMonitor::class.java))
+ val oldCollection = database.getCollection("de.darkatra.vrising.discord.ServerStatusMonitor")
+ val newCollection = database.getCollection("de.darkatra.vrising.discord.persistence.model.Server")
- oldCollection.insert(
- arrayOf(
+ oldCollection.insert(
Document.createDocument("hostName", "test-hostname")
)
- )
- assertThat(oldCollection.size()).isEqualTo(1)
- assertThat(newCollection.size()).isEqualTo(0)
+ assertThat(oldCollection.size()).isEqualTo(1)
+ assertThat(newCollection.size()).isEqualTo(0)
- assertThat(databaseMigrationService.migrateToLatestVersion()).isTrue()
+ assertThat(databaseMigrationService.migrateToLatestVersion()).isTrue()
- assertThat(oldCollection.size()).isEqualTo(0)
- assertThat(newCollection.size()).isEqualTo(1)
+ assertThat(oldCollection.size()).isEqualTo(0)
+ assertThat(newCollection.size()).isEqualTo(1)
+
+ val migratedDocument = newCollection.find().first()
+ assertThat(migratedDocument["hostname"]).isEqualTo("test-hostname")
+
+ val schemas = repository.find().toList()
+ assertThat(schemas).hasSize(2)
+ }
+ }
- val migratedDocument = newCollection.find().first()
- assertThat(migratedDocument["hostname"]).isEqualTo(migratedDocument["hostName"])
- assertThat(migratedDocument["displayPlayerGearLevel"]).isEqualTo(true)
- assertThat(migratedDocument["embedEnabled"]).isEqualTo(true)
+ @Test
+ fun `should migrate schema from 1_2_x to 2_11_0`() {
+
+ DatabaseConfigurationTestUtils.getTestDatabase(DatabaseConfigurationTestUtils.DATABASE_FILE_V1_2_x).use { database ->
+
+ val databaseMigrationService = DatabaseMigrationService(
+ database = database,
+ appVersionFromPom = "2.11.0"
+ )
+
+ val oldCollection = database.getCollection("de.darkatra.vrising.discord.ServerStatusMonitor")
+ val serverRepository = database.getRepository(Server::class.java)
+
+ assertThat(oldCollection.size()).isEqualTo(1)
+ assertThat(serverRepository.size()).isEqualTo(0)
+
+ val oldDocument = oldCollection.find().first()
+
+ assertThat(databaseMigrationService.migrateToLatestVersion()).isTrue()
+
+ assertThat(oldCollection.size()).isEqualTo(0)
+ assertThat(serverRepository.size()).isEqualTo(1)
+
+ val server = serverRepository.find().first()
+ assertThat(server.id).isEqualTo(oldDocument["id"])
+ assertThat(server.version).isNotNull()
+ assertThat(server.discordServerId).isEqualTo(oldDocument["discordServerId"])
+ assertThat(server.hostname).isEqualTo(oldDocument["hostName"])
+ assertThat(server.queryPort).isEqualTo(oldDocument["queryPort"])
+ assertThat(server.apiHostname).isEqualTo(oldDocument["apiHostname"])
+ assertThat(server.apiPort).isEqualTo(oldDocument["apiPort"])
+ assertThat(server.apiUsername).isEqualTo(oldDocument["apiUsername"])
+ assertThat(server.apiPassword).isEqualTo(oldDocument["apiPassword"])
+ assertThat(server.pvpLeaderboard).isNull()
+ assertThat(server.playerActivityFeed).isNull()
+ assertThat(server.pvpKillFeed).isNull()
+ assertThat(server.statusMonitor).isNotNull()
+ assertThat(server.statusMonitor!!.status).isNotNull()
+ assertThat(server.statusMonitor!!.status).isEqualTo(Status.ACTIVE)
+ assertThat(server.statusMonitor!!.discordChannelId).isEqualTo(oldDocument["discordChannelId"])
+ assertThat(server.statusMonitor!!.displayServerDescription).isTrue()
+ assertThat(server.statusMonitor!!.displayPlayerGearLevel).isTrue()
+ assertThat(server.statusMonitor!!.currentEmbedMessageId).isEqualTo(oldDocument["currentEmbedMessageId"])
+ assertThat(server.statusMonitor!!.currentFailedAttempts).isEqualTo(0)
+ assertThat(server.statusMonitor!!.currentFailedApiAttempts).isEqualTo(0)
+ assertThat(server.statusMonitor!!.recentErrors).isEmpty()
+ }
+ }
+
+ @Test
+ fun `should migrate schema from 2_10_5 to 2_11_0`() {
+
+ DatabaseConfigurationTestUtils.getTestDatabase(DatabaseConfigurationTestUtils.DATABASE_FILE_V2_10_5).use { database ->
+
+ val repository = database.getRepository(Schema::class.java)
+ repository.insert(Schema(appVersion = "V2.10.5"))
+
+ val databaseMigrationService = DatabaseMigrationService(
+ database = database,
+ appVersionFromPom = "2.11.0"
+ )
- val schemas = repository.find().toList()
- assertThat(schemas).hasSize(2)
+ val oldCollection = database.getCollection("de.darkatra.vrising.discord.persistence.model.ServerStatusMonitor")
+ val serverRepository = database.getRepository(Server::class.java)
+
+ assertThat(oldCollection.size()).isEqualTo(1)
+ assertThat(serverRepository.size()).isEqualTo(0)
+
+ val oldDocument = oldCollection.find().first()
+
+ assertThat(databaseMigrationService.migrateToLatestVersion()).isTrue()
+
+ assertThat(oldCollection.size()).isEqualTo(0)
+ assertThat(serverRepository.size()).isEqualTo(1)
+
+ val server = serverRepository.find().first()
+ assertThat(server.id).isEqualTo(oldDocument["id"])
+ assertThat(server.version).isEqualTo(oldDocument["version"])
+ assertThat(server.discordServerId).isEqualTo(oldDocument["discordServerId"])
+ assertThat(server.hostname).isEqualTo(oldDocument["hostname"])
+ assertThat(server.queryPort).isEqualTo(oldDocument["queryPort"])
+ assertThat(server.apiHostname).isEqualTo(oldDocument["apiHostname"])
+ assertThat(server.apiPort).isEqualTo(oldDocument["apiPort"])
+ assertThat(server.apiUsername).isEqualTo(oldDocument["apiUsername"])
+ assertThat(server.apiPassword).isEqualTo(oldDocument["apiPassword"])
+ assertThat(server.pvpLeaderboard).isNull()
+ assertThat(server.playerActivityFeed).isNotNull()
+ assertThat(server.playerActivityFeed!!.status).isEqualTo(Status.ACTIVE)
+ assertThat(server.playerActivityFeed!!.discordChannelId).isEqualTo(oldDocument["playerActivityDiscordChannelId"])
+ assertThat(server.playerActivityFeed!!.lastUpdated).isNull()
+ assertThat(server.playerActivityFeed!!.currentFailedAttempts).isEqualTo(0)
+ assertThat(server.playerActivityFeed!!.recentErrors).isEmpty()
+ assertThat(server.pvpKillFeed).isNotNull()
+ assertThat(server.pvpKillFeed!!.status).isEqualTo(Status.ACTIVE)
+ assertThat(server.pvpKillFeed!!.discordChannelId).isEqualTo(oldDocument["pvpKillFeedDiscordChannelId"])
+ assertThat(server.pvpKillFeed!!.lastUpdated).isNull()
+ assertThat(server.pvpKillFeed!!.currentFailedAttempts).isEqualTo(0)
+ assertThat(server.pvpKillFeed!!.recentErrors).isEmpty()
+ assertThat(server.statusMonitor).isNotNull()
+ assertThat(server.statusMonitor!!.status).isNotNull()
+ assertThat(server.statusMonitor!!.status).isEqualTo(Status.ACTIVE)
+ assertThat(server.statusMonitor!!.discordChannelId).isEqualTo(oldDocument["discordChannelId"])
+ assertThat(server.statusMonitor!!.displayServerDescription).isEqualTo(oldDocument["displayServerDescription"])
+ assertThat(server.statusMonitor!!.displayPlayerGearLevel).isEqualTo(oldDocument["displayPlayerGearLevel"])
+ assertThat(server.statusMonitor!!.currentEmbedMessageId).isEqualTo(oldDocument["currentEmbedMessageId"])
+ assertThat(server.statusMonitor!!.currentFailedAttempts).isEqualTo(oldDocument["currentFailedAttempts"])
+ assertThat(server.statusMonitor!!.currentFailedApiAttempts).isEqualTo(oldDocument["currentFailedApiAttempts"])
+ assertThat(server.statusMonitor!!.recentErrors).isEmpty()
+ }
}
}
diff --git a/src/test/kotlin/de/darkatra/vrising/discord/persistence/ServerRepositoryTest.kt b/src/test/kotlin/de/darkatra/vrising/discord/persistence/ServerRepositoryTest.kt
new file mode 100644
index 0000000..693d290
--- /dev/null
+++ b/src/test/kotlin/de/darkatra/vrising/discord/persistence/ServerRepositoryTest.kt
@@ -0,0 +1,73 @@
+package de.darkatra.vrising.discord.persistence
+
+import de.darkatra.vrising.discord.DatabaseConfigurationTestUtils
+import de.darkatra.vrising.discord.persistence.model.ServerTestUtils
+import org.assertj.core.api.Assertions.assertThat
+import org.dizitart.no2.Nitrite
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.assertThrows
+
+class ServerRepositoryTest {
+
+ private val nitrite: Nitrite = DatabaseConfigurationTestUtils.getTestDatabase()
+
+ private val serverRepository = ServerRepository(nitrite)
+
+ @BeforeEach
+ fun setUp() {
+ DatabaseConfigurationTestUtils.clearDatabase(nitrite)
+ }
+
+ @Test
+ fun `should get servers`() {
+
+ serverRepository.addServer(
+ ServerTestUtils.getServer()
+ )
+
+ val serverStatusMonitors = serverRepository.getServers()
+
+ assertThat(serverStatusMonitors).hasSize(1)
+
+ val serverStatusMonitor = serverStatusMonitors.first()
+ assertThat(serverStatusMonitor.id).isEqualTo(ServerTestUtils.ID)
+ assertThat(serverStatusMonitor.discordServerId).isEqualTo(ServerTestUtils.DISCORD_SERVER_ID)
+ assertThat(serverStatusMonitor.hostname).isEqualTo(ServerTestUtils.HOST_NAME)
+ assertThat(serverStatusMonitor.queryPort).isEqualTo(ServerTestUtils.QUERY_PORT)
+ }
+
+ @Test
+ fun `should not update server status monitor with higher version`() {
+
+ val serverStatusMonitor = ServerTestUtils.getServer()
+ serverRepository.addServer(serverStatusMonitor)
+
+ val update1 = serverRepository.getServer(serverStatusMonitor.id, serverStatusMonitor.discordServerId)!!.apply {
+ hostname = "test-1"
+ }
+ val update2 = serverRepository.getServer(serverStatusMonitor.id, serverStatusMonitor.discordServerId)!!.apply {
+ hostname = "test-2"
+ }
+
+ serverRepository.updateServer(update1)
+
+ val e = assertThrows {
+ serverRepository.updateServer(update2)
+ }
+
+ assertThat(e.message).isEqualTo("Server with id '${serverStatusMonitor.id}' was already updated by another thread.")
+ }
+
+ @Test
+ fun `should not insert server status monitor when using updateServerStatusMonitor`() {
+
+ val e = assertThrows {
+ serverRepository.updateServer(
+ ServerTestUtils.getServer()
+ )
+ }
+
+ assertThat(e.message).isEqualTo("Server with id '${ServerTestUtils.ID}' not found.")
+ }
+}
diff --git a/src/test/kotlin/de/darkatra/vrising/discord/persistence/model/ServerTestUtils.kt b/src/test/kotlin/de/darkatra/vrising/discord/persistence/model/ServerTestUtils.kt
new file mode 100644
index 0000000..f02d199
--- /dev/null
+++ b/src/test/kotlin/de/darkatra/vrising/discord/persistence/model/ServerTestUtils.kt
@@ -0,0 +1,22 @@
+package de.darkatra.vrising.discord.persistence.model
+
+import dev.kord.common.entity.Snowflake
+import kotlinx.datetime.toKotlinInstant
+import java.time.Instant
+
+object ServerTestUtils {
+
+ const val ID = "id"
+ val DISCORD_SERVER_ID = Snowflake(Instant.now().toKotlinInstant()).toString()
+ const val HOST_NAME = "localhost"
+ const val QUERY_PORT = 8081
+
+ fun getServer(): Server {
+ return Server(
+ id = ID,
+ discordServerId = DISCORD_SERVER_ID,
+ hostname = HOST_NAME,
+ queryPort = QUERY_PORT,
+ )
+ }
+}
diff --git a/src/test/kotlin/de/darkatra/vrising/discord/serverstatus/ServerStatusMonitorRepositoryTest.kt b/src/test/kotlin/de/darkatra/vrising/discord/serverstatus/ServerStatusMonitorRepositoryTest.kt
deleted file mode 100644
index 73f7662..0000000
--- a/src/test/kotlin/de/darkatra/vrising/discord/serverstatus/ServerStatusMonitorRepositoryTest.kt
+++ /dev/null
@@ -1,91 +0,0 @@
-package de.darkatra.vrising.discord.serverstatus
-
-import de.darkatra.vrising.discord.DatabaseConfigurationTestUtils
-import de.darkatra.vrising.discord.persistence.ServerStatusMonitorRepository
-import de.darkatra.vrising.discord.persistence.model.ServerStatusMonitorStatus
-import de.darkatra.vrising.discord.serverstatus.exceptions.OutdatedServerStatusMonitorException
-import de.darkatra.vrising.discord.serverstatus.model.ServerStatusMonitorTestUtils
-import org.assertj.core.api.Assertions.assertThat
-import org.dizitart.no2.Nitrite
-import org.junit.jupiter.api.BeforeEach
-import org.junit.jupiter.api.Test
-import org.junit.jupiter.api.assertThrows
-import org.junit.jupiter.api.condition.DisabledInNativeImage
-
-@DisabledInNativeImage
-class ServerStatusMonitorRepositoryTest {
-
- private val nitrite: Nitrite = DatabaseConfigurationTestUtils.getTestDatabase()
-
- private val serverStatusMonitorRepository = ServerStatusMonitorRepository(nitrite)
-
- @BeforeEach
- fun setUp() {
- DatabaseConfigurationTestUtils.clearDatabase(nitrite)
- }
-
- @Test
- fun `should get active server status monitors`() {
-
- serverStatusMonitorRepository.addServerStatusMonitor(
- ServerStatusMonitorTestUtils.getServerStatusMonitor(ServerStatusMonitorStatus.ACTIVE)
- )
-
- val serverStatusMonitors = serverStatusMonitorRepository.getServerStatusMonitors(status = ServerStatusMonitorStatus.ACTIVE)
-
- assertThat(serverStatusMonitors).hasSize(1)
-
- val serverStatusMonitor = serverStatusMonitors.first()
- assertThat(serverStatusMonitor.id).isEqualTo(ServerStatusMonitorTestUtils.ID)
- assertThat(serverStatusMonitor.discordServerId).isEqualTo(ServerStatusMonitorTestUtils.DISCORD_SERVER_ID)
- assertThat(serverStatusMonitor.discordChannelId).isEqualTo(ServerStatusMonitorTestUtils.DISCORD_CHANNEL_ID)
- assertThat(serverStatusMonitor.hostname).isEqualTo(ServerStatusMonitorTestUtils.HOST_NAME)
- assertThat(serverStatusMonitor.queryPort).isEqualTo(ServerStatusMonitorTestUtils.QUERY_PORT)
- }
-
- @Test
- fun `should get no active server status monitors`() {
-
- serverStatusMonitorRepository.addServerStatusMonitor(
- ServerStatusMonitorTestUtils.getServerStatusMonitor(ServerStatusMonitorStatus.INACTIVE)
- )
-
- val serverStatusMonitors = serverStatusMonitorRepository.getServerStatusMonitors(status = ServerStatusMonitorStatus.ACTIVE)
-
- assertThat(serverStatusMonitors).hasSize(0)
- }
-
- @Test
- fun `should not update server status monitor with higher version`() {
-
- val serverStatusMonitor = ServerStatusMonitorTestUtils.getServerStatusMonitor(ServerStatusMonitorStatus.ACTIVE)
- serverStatusMonitorRepository.addServerStatusMonitor(serverStatusMonitor)
-
- val update1 = serverStatusMonitorRepository.getServerStatusMonitor(serverStatusMonitor.id, serverStatusMonitor.discordServerId)!!.apply {
- status = ServerStatusMonitorStatus.INACTIVE
- }
- val update2 = serverStatusMonitorRepository.getServerStatusMonitor(serverStatusMonitor.id, serverStatusMonitor.discordServerId)!!.apply {
- status = ServerStatusMonitorStatus.ACTIVE
- }
-
- serverStatusMonitorRepository.updateServerStatusMonitor(update1)
-
- val e = assertThrows {
- serverStatusMonitorRepository.updateServerStatusMonitor(update2)
- }
-
- assertThat(e.message).isEqualTo("Monitor with id '${serverStatusMonitor.id}' was already updated by another thread.")
- }
-
- @Test
- fun `should not insert server status monitor when using updateServerStatusMonitor`() {
-
- val e = assertThrows {
- serverStatusMonitorRepository.updateServerStatusMonitor(
- ServerStatusMonitorTestUtils.getServerStatusMonitor(ServerStatusMonitorStatus.ACTIVE)
- )
- }
-
- assertThat(e.message).isEqualTo("Monitor with id '${ServerStatusMonitorTestUtils.ID}' not found.")
- }
-}
diff --git a/src/test/kotlin/de/darkatra/vrising/discord/serverstatus/model/ServerStatusMonitorTestUtils.kt b/src/test/kotlin/de/darkatra/vrising/discord/serverstatus/model/ServerStatusMonitorTestUtils.kt
deleted file mode 100644
index b2e4289..0000000
--- a/src/test/kotlin/de/darkatra/vrising/discord/serverstatus/model/ServerStatusMonitorTestUtils.kt
+++ /dev/null
@@ -1,29 +0,0 @@
-package de.darkatra.vrising.discord.serverstatus.model
-
-import de.darkatra.vrising.discord.persistence.model.ServerStatusMonitor
-import de.darkatra.vrising.discord.persistence.model.ServerStatusMonitorStatus
-import dev.kord.common.entity.Snowflake
-import kotlinx.datetime.toKotlinInstant
-import java.time.Instant
-
-object ServerStatusMonitorTestUtils {
-
- const val ID = "id"
- val DISCORD_SERVER_ID = Snowflake(Instant.now().toKotlinInstant()).toString()
- val DISCORD_CHANNEL_ID = Snowflake(Instant.now().toKotlinInstant()).toString()
- const val HOST_NAME = "localhost"
- const val QUERY_PORT = 8081
-
- fun getServerStatusMonitor(status: ServerStatusMonitorStatus): ServerStatusMonitor {
- return ServerStatusMonitor(
- id = ID,
- discordServerId = DISCORD_SERVER_ID,
- discordChannelId = DISCORD_CHANNEL_ID,
- hostname = HOST_NAME,
- queryPort = QUERY_PORT,
- status = status,
- displayServerDescription = true,
- displayPlayerGearLevel = true
- )
- }
-}
diff --git a/src/test/resources/persistence/v1.2.db b/src/test/resources/persistence/v1.2.db
new file mode 100644
index 0000000..1280a64
Binary files /dev/null and b/src/test/resources/persistence/v1.2.db differ
diff --git a/src/test/resources/persistence/v2.10.5.db b/src/test/resources/persistence/v2.10.5.db
new file mode 100644
index 0000000..9e04b05
Binary files /dev/null and b/src/test/resources/persistence/v2.10.5.db differ