Skip to content

Commit

Permalink
feat: adds leaderboards [WIP]
Browse files Browse the repository at this point in the history
Note: heavily refactors the database schema and bot commands
  • Loading branch information
DarkAtra committed Jun 23, 2024
1 parent fa2dcac commit e259d59
Show file tree
Hide file tree
Showing 54 changed files with 1,945 additions and 1,006 deletions.
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

<groupId>de.darkatra</groupId>
<artifactId>v-rising-discord-bot</artifactId>
<version>2.10.5</version>
<version>2.11.0</version>
<packaging>jar</packaging>

<licenses>
Expand Down
34 changes: 23 additions & 11 deletions src/main/kotlin/de/darkatra/vrising/discord/Bot.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<Command>,
private val databaseMigrationService: DatabaseMigrationService,
private val serverStatusMonitorService: ServerStatusMonitorService
private val serverService: ServerService
) : ApplicationRunner, DisposableBean, SchedulingConfigurer {

private val logger = LoggerFactory.getLogger(javaClass)
Expand All @@ -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
}
Expand Down Expand Up @@ -127,7 +139,7 @@ class Bot(
{
if (isReady.get() && kord.isActive) {
runBlocking {
serverStatusMonitorService.updateServerStatusMonitors(kord)
serverService.updateServers(kord)
}
}
},
Expand All @@ -142,7 +154,7 @@ class Bot(
{
if (isReady.get() && kord.isActive) {
runBlocking {
serverStatusMonitorService.cleanupInactiveServerStatusMonitors(kord)
serverService.cleanupInactiveServers(kord)
}
}
},
Expand Down
12 changes: 11 additions & 1 deletion src/main/kotlin/de/darkatra/vrising/discord/KordExtensions.kt
Original file line number Diff line number Diff line change
@@ -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<MessageChannelBehavior> {
val channel = try {
getChannel(Snowflake(discordChannelId))
Expand All @@ -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
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand All @@ -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)
}
}

Expand All @@ -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'."
}
}
}
Original file line number Diff line number Diff line change
@@ -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<String>): 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'."
}
}
}
Loading

0 comments on commit e259d59

Please sign in to comment.