From d83f21f22dc2eacb16cda9fd08b3e33829ad6c4c Mon Sep 17 00:00:00 2001 From: Lukas Jost Date: Thu, 29 Oct 2026 19:47:05 +0100 Subject: [PATCH] feat(chat): make permission command guidance clickable --- .../velocity/PermissionCommandInteraction.kt | 73 +++++++++++++++++++ .../velocity/VelocityPermissionsCommand.kt | 14 +++- .../grounds/permissions/messages.properties | 20 ++--- .../permissions/messages_de.properties | 20 ++--- .../velocity/PermissionsMessagesTest.kt | 35 +++++++++ 5 files changed, 141 insertions(+), 21 deletions(-) create mode 100644 velocity/src/main/kotlin/gg/grounds/permissions/velocity/PermissionCommandInteraction.kt diff --git a/velocity/src/main/kotlin/gg/grounds/permissions/velocity/PermissionCommandInteraction.kt b/velocity/src/main/kotlin/gg/grounds/permissions/velocity/PermissionCommandInteraction.kt new file mode 100644 index 0000000..d6a5656 --- /dev/null +++ b/velocity/src/main/kotlin/gg/grounds/permissions/velocity/PermissionCommandInteraction.kt @@ -0,0 +1,73 @@ +package gg.grounds.permissions.velocity + +import gg.grounds.i18n.Translations +import java.util.Locale +import net.kyori.adventure.audience.Audience +import net.kyori.adventure.text.Component +import net.kyori.adventure.text.event.ClickEvent +import net.kyori.adventure.text.event.HoverEvent + +/** Renders command guidance as localized actions without exposing raw syntax as the label. */ +object PermissionCommandInteraction { + fun supports(message: PermissionsMessage): Boolean = message in interactions + + fun render(messages: Translations, message: PermissionsMessage, locale: Locale): Component { + return attach(messages.render(message, locale), message) + } + + fun render(messages: Translations, message: PermissionsMessage, audience: Audience): Component { + return attach(messages.render(message, audience), message) + } + + private fun attach(component: Component, message: PermissionsMessage): Component { + val interaction = + requireNotNull(interactions[message]) { "No command interaction for ${message.id}" } + return component + .clickEvent(interaction.click) + .hoverEvent(HoverEvent.showText(Component.text(interaction.syntax))) + } + + private data class Interaction(val click: ClickEvent, val syntax: String) + + private val interactions = + mapOf( + PermissionsMessage.HELP_STATUS to + Interaction(ClickEvent.runCommand("/perm status"), "/perm status"), + PermissionsMessage.USAGE_STATUS to + Interaction(ClickEvent.runCommand("/perm status"), "/perm status"), + PermissionsMessage.HELP_USER_INFO to + Interaction(ClickEvent.suggestCommand("/perm user "), "/perm user info"), + PermissionsMessage.USAGE_INFO to + Interaction(ClickEvent.suggestCommand("/perm user "), "/perm user info"), + PermissionsMessage.HELP_PERMISSION_CHECK to + Interaction( + ClickEvent.suggestCommand("/perm user "), + "/perm user permission check [scope]", + ), + PermissionsMessage.USAGE_PERMISSION_CHECK to + Interaction( + ClickEvent.suggestCommand("/perm user "), + "/perm user permission check [scope]", + ), + PermissionsMessage.HELP_USER_REFRESH to + Interaction( + ClickEvent.suggestCommand("/perm user "), + "/perm user refresh", + ), + PermissionsMessage.USAGE_USER to + Interaction( + ClickEvent.suggestCommand("/perm user "), + "/perm user ", + ), + PermissionsMessage.HELP_REFRESH to + Interaction( + ClickEvent.suggestCommand("/perm refresh "), + "/perm refresh ", + ), + PermissionsMessage.USAGE_REFRESH to + Interaction( + ClickEvent.suggestCommand("/perm refresh "), + "/perm refresh ", + ), + ) +} diff --git a/velocity/src/main/kotlin/gg/grounds/permissions/velocity/VelocityPermissionsCommand.kt b/velocity/src/main/kotlin/gg/grounds/permissions/velocity/VelocityPermissionsCommand.kt index 09e9546..f1e7a49 100644 --- a/velocity/src/main/kotlin/gg/grounds/permissions/velocity/VelocityPermissionsCommand.kt +++ b/velocity/src/main/kotlin/gg/grounds/permissions/velocity/VelocityPermissionsCommand.kt @@ -65,7 +65,19 @@ class VelocityPermissionsCommand( } private fun sendLocalized(source: CommandSource, message: LocalizedPermissionMessage) { - messages.send(source, message.key, *message.args.map { it.key to it.value }.toTypedArray()) + if (message.args.isEmpty() && PermissionCommandInteraction.supports(message.key)) { + messages.sendRendered( + source, + message.key, + PermissionCommandInteraction.render(messages, message.key, source), + ) + } else { + messages.send( + source, + message.key, + *message.args.map { it.key to it.value }.toTypedArray(), + ) + } } companion object { diff --git a/velocity/src/main/resources/gg/grounds/permissions/messages.properties b/velocity/src/main/resources/gg/grounds/permissions/messages.properties index b525c1b..9361bde 100644 --- a/velocity/src/main/resources/gg/grounds/permissions/messages.properties +++ b/velocity/src/main/resources/gg/grounds/permissions/messages.properties @@ -1,15 +1,15 @@ command.output= command.failure= -help.status=/perm status -help.user-info=/perm user \\ info -help.permission-check=/perm user \\ permission check \\ [scope] -help.user-refresh=/perm user \\ refresh -help.refresh=/perm refresh \\ -usage.status=Usage: /perm status -usage.user=Usage: /perm user \\ \\ -usage.info=Usage: /perm user \\ info -usage.refresh=Usage: /perm refresh \\ -usage.permission-check=Usage: /perm user \\ permission check \\ [scope] +help.status=Show permission status +help.user-info=Show player permissions +help.permission-check=Check player permission +help.user-refresh=Refresh player permissions +help.refresh=Refresh permissions +usage.status=Show permission status +usage.user=Complete player command +usage.info=Show player permissions +usage.refresh=Refresh permissions +usage.permission-check=Check player permission error.invalid-scope=Invalid permission scope: error.player-offline=Player is not online: error.no-snapshot=No permission snapshot is loaded for this player. diff --git a/velocity/src/main/resources/gg/grounds/permissions/messages_de.properties b/velocity/src/main/resources/gg/grounds/permissions/messages_de.properties index 19291a2..f242268 100644 --- a/velocity/src/main/resources/gg/grounds/permissions/messages_de.properties +++ b/velocity/src/main/resources/gg/grounds/permissions/messages_de.properties @@ -1,15 +1,15 @@ command.output= command.failure= -help.status=/perm status -help.user-info=/perm user \\ info -help.permission-check=/perm user \\ permission check \\ [scope] -help.user-refresh=/perm user \\ refresh -help.refresh=/perm refresh \\ -usage.status=Benutzung: /perm status -usage.user=Benutzung: /perm user \\ \\ -usage.info=Benutzung: /perm user \\ info -usage.refresh=Benutzung: /perm refresh \\ -usage.permission-check=Benutzung: /perm user \\ permission check \\ [scope] +help.status=Berechtigungsstatus anzeigen +help.user-info=Spielerberechtigungen anzeigen +help.permission-check=Spielerberechtigung prüfen +help.user-refresh=Spielerberechtigungen aktualisieren +help.refresh=Berechtigungen aktualisieren +usage.status=Berechtigungsstatus anzeigen +usage.user=Spielerbefehl vervollständigen +usage.info=Spielerberechtigungen anzeigen +usage.refresh=Berechtigungen aktualisieren +usage.permission-check=Spielerberechtigung prüfen error.invalid-scope=Ungültiger Berechtigungsbereich: error.player-offline=Spieler ist nicht online: error.no-snapshot=Für diesen Spieler ist kein Berechtigungs-Snapshot geladen. diff --git a/velocity/src/test/kotlin/gg/grounds/permissions/velocity/PermissionsMessagesTest.kt b/velocity/src/test/kotlin/gg/grounds/permissions/velocity/PermissionsMessagesTest.kt index 1b579b7..b6eb90c 100644 --- a/velocity/src/test/kotlin/gg/grounds/permissions/velocity/PermissionsMessagesTest.kt +++ b/velocity/src/test/kotlin/gg/grounds/permissions/velocity/PermissionsMessagesTest.kt @@ -5,6 +5,8 @@ import gg.grounds.i18n.SystemCategory import gg.grounds.i18n.SystemLevel import gg.grounds.i18n.Translations import java.util.Locale +import net.kyori.adventure.text.event.ClickEvent +import net.kyori.adventure.text.event.HoverEvent import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test @@ -52,4 +54,37 @@ class PermissionsMessagesTest { ), ) } + + @Test + fun `localized permission help actions preserve commands and distinguish execution from entry`() { + val status = + PermissionCommandInteraction.render( + messages, + PermissionsMessage.HELP_STATUS, + Locale.GERMAN, + ) + val check = + PermissionCommandInteraction.render( + messages, + PermissionsMessage.USAGE_PERMISSION_CHECK, + Locale.GERMAN, + ) + + assertEquals("Berechtigungsstatus anzeigen", plain.serialize(status)) + assertEquals(ClickEvent.runCommand("/perm status"), status.clickEvent()) + assertEquals( + HoverEvent.showText(net.kyori.adventure.text.Component.text("/perm status")), + status.hoverEvent(), + ) + assertEquals("Spielerberechtigung prüfen", plain.serialize(check)) + assertEquals(ClickEvent.suggestCommand("/perm user "), check.clickEvent()) + assertEquals( + HoverEvent.showText( + net.kyori.adventure.text.Component.text( + "/perm user permission check [scope]" + ) + ), + check.hoverEvent(), + ) + } }