summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSho Sakuma <me@m1sk9.dev>2026-09-28 23:00:29 +0900
committerSho Sakuma <me@m1sk9.dev>2026-09-28 23:00:29 +0900
commitaf2feee95e7f6ebaef5466402789917ae65c0796 (patch)
tree1a4bcd9c9ea3a32ddfb861ce541ca0877ef3aa88
parente725de11e80abd9d96784c7f08a09893c852fef0 (diff)
downloadLunaticChat-af2feee95e7f6ebaef5466402789917ae65c0796.tar.gz
LunaticChat-af2feee95e7f6ebaef5466402789917ae65c0796.tar.bz2
LunaticChat-af2feee95e7f6ebaef5466402789917ae65c0796.zip
feat(paper): add world, display_name, channel_id and role chat placeholders
Operators asked for more of the sender's context in chat formats than the name alone. {world} covers direct messages and channel chat; the channel details and the sender's display name and role only exist for channel chat, so only that format accepts them. A direct message from another server renders {world} empty: the relay does not carry the sender's world, and adding it would need a protocol bump and a coordinated Velocity release for one placeholder. The renderer now accepts underscores so the snake_case names match as written. Co-Authored-By: Claude <noreply@anthropic.com>
-rw-r--r--platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/chat/handler/ChannelMessageHandler.kt29
-rw-r--r--platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/chat/handler/DirectMessageHandler.kt18
-rw-r--r--platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/i18n/ChatFormat.kt12
-rw-r--r--platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/i18n/ChatPlaceholder.kt4
-rw-r--r--platform-paper/src/main/resources/config.yml4
-rw-r--r--platform-paper/src/main/resources/languages/en.yml4
-rw-r--r--platform-paper/src/main/resources/languages/ja.yml4
-rw-r--r--platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/TestUtils.kt8
-rw-r--r--platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/chat/handler/ChannelMessageHandlerTest.kt31
-rw-r--r--platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/chat/handler/DirectMessageHandlerTest.kt23
-rw-r--r--platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/i18n/ChatFormatTest.kt51
-rw-r--r--website/src/docs/configuration.md4
-rw-r--r--website/src/docs/reference/message-format.md4
-rw-r--r--website/src/ja/docs/configuration.md4
-rw-r--r--website/src/ja/docs/reference/message-format.md4
15 files changed, 178 insertions, 26 deletions
diff --git a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/chat/handler/ChannelMessageHandler.kt b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/chat/handler/ChannelMessageHandler.kt
index d75b8ef..b9a8239 100644
--- a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/chat/handler/ChannelMessageHandler.kt
+++ b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/chat/handler/ChannelMessageHandler.kt
@@ -1,6 +1,8 @@
package dev.m1sk9.lunaticChat.paper.chat.handler
+import dev.m1sk9.lunaticChat.engine.chat.channel.ChannelContext
import dev.m1sk9.lunaticChat.engine.chat.channel.ChannelMessageLogEntry
+import dev.m1sk9.lunaticChat.engine.chat.channel.ChannelRole
import dev.m1sk9.lunaticChat.engine.debug.DebugCategory
import dev.m1sk9.lunaticChat.engine.debug.DebugLogger
import dev.m1sk9.lunaticChat.paper.chat.channel.ChannelManager
@@ -13,6 +15,7 @@ import dev.m1sk9.lunaticChat.paper.i18n.LanguageManager
import dev.m1sk9.lunaticChat.paper.i18n.channelMessage
import dev.m1sk9.lunaticChat.paper.settings.PlayerSettingsManager
import net.kyori.adventure.text.Component
+import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer
import org.bukkit.Bukkit
import org.bukkit.entity.Player
@@ -35,7 +38,7 @@ class ChannelMessageHandler(
val senderSettings = settingsManager?.getSettings(playerId)
- val formattedMessage = formatChannelMessage(player.name, context.channel.name, message)
+ val formattedMessage = formatChannelMessage(player, context, message)
// Play notification sound to sender if enabled
if (senderSettings?.channelMessageNotificationEnabled == true) {
@@ -87,8 +90,26 @@ class ChannelMessageHandler(
}
private fun formatChannelMessage(
- senderName: String,
- channelName: String,
+ sender: Player,
+ context: ChannelContext,
message: String,
- ): Component = messageFormats.current.channelMessage(senderName, channelName, message)
+ ): Component =
+ messageFormats.current.channelMessage(
+ sender = sender.name,
+ channel = context.channel.name,
+ channelId = context.channel.id,
+ displayName = LegacyComponentSerializer.legacySection().serialize(sender.displayName()),
+ message = message,
+ world = sender.world.name,
+ role = roleLabel(context.members.firstOrNull { it.playerId == sender.uniqueId }?.role),
+ )
+
+ private fun roleLabel(role: ChannelRole?): String =
+ languageManager.getMessage(
+ when (role) {
+ ChannelRole.OWNER -> "channel.role.owner"
+ ChannelRole.MODERATOR -> "channel.role.moderator"
+ ChannelRole.MEMBER, null -> "channel.role.member"
+ },
+ )
}
diff --git a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/chat/handler/DirectMessageHandler.kt b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/chat/handler/DirectMessageHandler.kt
index aa4f0d8..af3aa3f 100644
--- a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/chat/handler/DirectMessageHandler.kt
+++ b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/chat/handler/DirectMessageHandler.kt
@@ -142,9 +142,10 @@ class DirectMessageHandler(
val formats = messageFormats.current
- notifySpies(formats, sender.name, recipient.name, message)
+ notifySpies(formats, sender.name, recipient.name, message, sender.world.name)
- val userMessage = formatMessage(formats, sender.name, recipient.name, displayMessage, replyTo = sender.name)
+ val userMessage =
+ formatMessage(formats, sender.name, recipient.name, displayMessage, sender.world.name, replyTo = sender.name)
sender.apply {
sendMessage(userMessage)
takeIf { senderSettings?.directMessageNotificationEnabled == true }
@@ -180,10 +181,10 @@ class DirectMessageHandler(
val formats = messageFormats.current
val recipientDisplay = "$targetName@$targetServerName"
- notifySpies(formats, sender.name, recipientDisplay, message)
+ notifySpies(formats, sender.name, recipientDisplay, message, sender.world.name)
val userMessage =
- formatMessage(formats, sender.name, recipientDisplay, displayMessage, replyTo = recipientDisplay)
+ formatMessage(formats, sender.name, recipientDisplay, displayMessage, sender.world.name, replyTo = recipientDisplay)
sender.apply {
sendMessage(userMessage)
takeIf { senderSettings?.directMessageNotificationEnabled == true }
@@ -208,7 +209,8 @@ class DirectMessageHandler(
val senderDisplay = "$senderName@$sourceServerName"
val userMessage =
- formatMessage(formats, senderDisplay, recipient.name, message, replyTo = senderDisplay)
+ // The relay does not carry the sender's world, so {world} renders empty for a remote sender.
+ formatMessage(formats, senderDisplay, recipient.name, message, world = "", replyTo = senderDisplay)
recipient.apply {
sendMessage(userMessage)
takeIf { recipientSettings?.directMessageNotificationEnabled == true }
@@ -233,12 +235,13 @@ class DirectMessageHandler(
senderName: String,
recipientName: String,
rawMessage: String,
+ world: String,
) {
SpyPermissionManager.notifySpies(
noticeText = { languageManager.getMessage("general.spyMessage") },
exclude = { it.name == senderName || it.name == recipientName },
) {
- formatMessage(formats, senderName, recipientName, rawMessage, replyTo = senderName)
+ formatMessage(formats, senderName, recipientName, rawMessage, world, replyTo = senderName)
}
}
@@ -247,8 +250,9 @@ class DirectMessageHandler(
senderName: String,
recipientName: String,
message: String,
+ world: String,
replyTo: String,
) = formats
- .directMessage(senderName, recipientName, message)
+ .directMessage(senderName, recipientName, message, world)
.clickEvent(ClickEvent.suggestCommand("/tell $replyTo "))
}
diff --git a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/i18n/ChatFormat.kt b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/i18n/ChatFormat.kt
index 1ce872b..85e6b52 100644
--- a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/i18n/ChatFormat.kt
+++ b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/i18n/ChatFormat.kt
@@ -15,25 +15,35 @@ fun MessageFormatConfig.directMessage(
sender: String,
recipient: String,
message: String,
+ world: String,
): Component =
directMessageFormat.render(
mapOf(
ChatPlaceholder.SENDER to sender,
ChatPlaceholder.RECIPIENT to recipient,
ChatPlaceholder.MESSAGE to message,
+ ChatPlaceholder.WORLD to world,
),
)
fun MessageFormatConfig.channelMessage(
sender: String,
channel: String,
+ channelId: String,
+ displayName: String,
message: String,
+ world: String,
+ role: String,
): Component =
channelMessageFormat.render(
mapOf(
ChatPlaceholder.SENDER to sender,
ChatPlaceholder.CHANNEL to channel,
+ ChatPlaceholder.CHANNEL_ID to channelId,
+ ChatPlaceholder.DISPLAY_NAME to displayName,
ChatPlaceholder.MESSAGE to message,
+ ChatPlaceholder.WORLD to world,
+ ChatPlaceholder.ROLE to role,
),
)
@@ -50,7 +60,7 @@ fun MessageFormatConfig.crossServerGlobalChat(
),
)
-private val PLACEHOLDER = Regex("""\{([A-Za-z]+)}""")
+private val PLACEHOLDER = Regex("""\{([A-Za-z_]+)}""")
// One pass over the format rather than one replace per placeholder, so a value that itself
// contains "{message}" (a channel named that way, say) is inserted literally instead of being
diff --git a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/i18n/ChatPlaceholder.kt b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/i18n/ChatPlaceholder.kt
index b0b8034..1edb6d2 100644
--- a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/i18n/ChatPlaceholder.kt
+++ b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/i18n/ChatPlaceholder.kt
@@ -14,7 +14,11 @@ enum class ChatPlaceholder(
RECIPIENT("recipient"),
MESSAGE("message"),
CHANNEL("channel"),
+ CHANNEL_ID("channel_id"),
+ DISPLAY_NAME("display_name"),
SERVER("server"),
+ WORLD("world"),
+ ROLE("role"),
;
companion object {
diff --git a/platform-paper/src/main/resources/config.yml b/platform-paper/src/main/resources/config.yml
index 4ca17b9..6f9a9d9 100644
--- a/platform-paper/src/main/resources/config.yml
+++ b/platform-paper/src/main/resources/config.yml
@@ -107,6 +107,10 @@ features:
# {message} - The content of the message
# {channel} - The name of the chat channel (only for channel chat)
# {server} - The name of the server (only for Velocity cross-server chat)
+# {world} - The world the sender is in (direct messages and channel chat; empty for a direct message from another server)
+# {display_name} - The display name of the sender (only for channel chat)
+# {channel_id} - The ID of the chat channel (only for channel chat)
+# {role} - The sender's role in the channel: Owner, Moderator or Member (only for channel chat)
# ----------------------------------------------
# These are the only settings /lc reload applies without a restart.
diff --git a/platform-paper/src/main/resources/languages/en.yml b/platform-paper/src/main/resources/languages/en.yml
index 7d1925e..8fd7144 100644
--- a/platform-paper/src/main/resources/languages/en.yml
+++ b/platform-paper/src/main/resources/languages/en.yml
@@ -235,6 +235,10 @@ channel:
cannotTransferToSelf: "You cannot transfer ownership to yourself"
receivedOwnership: "You are now the owner of {channel} (previously owned by {previousOwner})"
error: "Failed to transfer ownership"
+ role:
+ owner: "Owner"
+ moderator: "Moderator"
+ member: "Member"
general:
playerOnlyCommand: "This command can only be executed by players."
diff --git a/platform-paper/src/main/resources/languages/ja.yml b/platform-paper/src/main/resources/languages/ja.yml
index 0350ad5..321a043 100644
--- a/platform-paper/src/main/resources/languages/ja.yml
+++ b/platform-paper/src/main/resources/languages/ja.yml
@@ -235,6 +235,10 @@ channel:
cannotTransferToSelf: "自分自身にオーナー権限を譲渡することはできません"
receivedOwnership: "チャンネル {channel} のオーナーになりました (前のオーナー: {previousOwner})"
error: "オーナー権限の譲渡に失敗しました"
+ role:
+ owner: "オーナー"
+ moderator: "モデレーター"
+ member: "メンバー"
general:
playerOnlyCommand: "このコマンドはプレイヤーのみが実行できます"
diff --git a/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/TestUtils.kt b/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/TestUtils.kt
index 5b1a9a0..b38cbb4 100644
--- a/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/TestUtils.kt
+++ b/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/TestUtils.kt
@@ -17,6 +17,8 @@ import dev.m1sk9.lunaticChat.paper.config.key.VelocityIntegrationConfig
import dev.m1sk9.lunaticChat.paper.i18n.Language
import dev.m1sk9.lunaticChat.paper.storage.AsyncScheduler
import io.mockk.mockk
+import net.kyori.adventure.text.Component
+import org.bukkit.World
import org.bukkit.entity.Player
import org.bukkit.plugin.java.JavaPlugin
import java.util.UUID
@@ -183,11 +185,17 @@ object TestUtils {
uuid: UUID = UUID.randomUUID(),
name: String = "TestPlayer",
isOnline: Boolean = true,
+ displayName: Component = Component.text(name),
+ worldName: String = "world",
): Player {
val player = mockk<Player>(relaxed = true)
+ val world = mockk<World>(relaxed = true)
+ io.mockk.every { world.name } returns worldName
io.mockk.every { player.uniqueId } returns uuid
io.mockk.every { player.name } returns name
io.mockk.every { player.isOnline } returns isOnline
+ io.mockk.every { player.displayName() } returns displayName
+ io.mockk.every { player.world } returns world
return player
}
diff --git a/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/chat/handler/ChannelMessageHandlerTest.kt b/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/chat/handler/ChannelMessageHandlerTest.kt
index 26e2cc6..24f2897 100644
--- a/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/chat/handler/ChannelMessageHandlerTest.kt
+++ b/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/chat/handler/ChannelMessageHandlerTest.kt
@@ -2,6 +2,7 @@ package dev.m1sk9.lunaticChat.paper.chat.handler
import dev.m1sk9.lunaticChat.engine.chat.channel.ChannelContext
import dev.m1sk9.lunaticChat.engine.chat.channel.ChannelMessageLogEntry
+import dev.m1sk9.lunaticChat.engine.chat.channel.ChannelRole
import dev.m1sk9.lunaticChat.engine.debug.DebugLogger
import dev.m1sk9.lunaticChat.paper.TestUtils
import dev.m1sk9.lunaticChat.paper.chat.channel.ChannelManager
@@ -18,6 +19,7 @@ import io.mockk.unmockkStatic
import io.mockk.verify
import net.kyori.adventure.sound.Sound
import net.kyori.adventure.text.Component
+import net.kyori.adventure.text.format.NamedTextColor
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer
import org.bukkit.Bukkit
import java.util.UUID
@@ -97,6 +99,35 @@ class ChannelMessageHandlerTest {
}
@Test
+ fun `the channel format renders the sender's display name, world and role and the channel ID`() {
+ val owner =
+ TestUtils.createMockPlayer(
+ uuid = senderId,
+ name = "Alice",
+ displayName = Component.text("Queen", NamedTextColor.RED),
+ worldName = "world_nether",
+ )
+ every { Bukkit.getPlayer(senderId) } returns owner
+ every { channelManager.getPlayerChannelContext(senderId) } returns
+ ChannelContext(
+ channel = channel,
+ members =
+ listOf(
+ TestUtils.createTestChannelMember(channelId = channel.id, playerId = senderId, role = ChannelRole.OWNER),
+ TestUtils.createTestChannelMember(channelId = channel.id, playerId = memberId),
+ ),
+ )
+ every { languageManager.getMessage("channel.role.owner") } returns "Owner"
+ messageFormats.replace(MessageFormatConfig(channelMessageFormat = "{channel_id}|{display_name}|{world}|{role}"))
+ val delivered = slot<Component>()
+
+ handler().sendChannelMessage(owner, "hello")
+
+ verify { member.sendMessage(capture(delivered)) }
+ assertEquals("ch-1|§cQueen|world_nether|Owner", LegacyComponentSerializer.legacySection().serialize(delivered.captured))
+ }
+
+ @Test
fun `a member who went offline receives nothing`() {
val offline = TestUtils.createMockPlayer(uuid = memberId, name = "Bob", isOnline = false)
every { Bukkit.getPlayer(memberId) } returns offline
diff --git a/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/chat/handler/DirectMessageHandlerTest.kt b/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/chat/handler/DirectMessageHandlerTest.kt
index 0246549..5c1e9ee 100644
--- a/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/chat/handler/DirectMessageHandlerTest.kt
+++ b/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/chat/handler/DirectMessageHandlerTest.kt
@@ -113,6 +113,29 @@ class DirectMessageHandlerTest {
}
@Test
+ fun `the direct message format renders the sender's world`() {
+ val formats = TestUtils.createTestConfiguration().messageFormat.copy(directMessageFormat = "{sender}@{world}: {message}")
+ val handler = createHandler(configuration = TestUtils.createTestConfiguration().copy(messageFormat = formats))
+ val sender = TestUtils.createMockPlayer(name = "Alice", worldName = "world_the_end")
+ val recipient = TestUtils.createMockPlayer(name = "Bob")
+
+ runBlocking { handler.sendDirectMessage(sender, recipient, "hi") }
+
+ assertEquals("Alice@world_the_end: hi", recipient.receivedMessage().legacy())
+ }
+
+ @Test
+ fun `a direct message from another server renders the sender's world empty`() {
+ val formats = TestUtils.createTestConfiguration().messageFormat.copy(directMessageFormat = "{sender}[{world}]: {message}")
+ val handler = createHandler(configuration = TestUtils.createTestConfiguration().copy(messageFormat = formats))
+ val recipient = TestUtils.createMockPlayer(name = "Bob")
+
+ handler.handleIncomingCrossServerMessage(recipient, "Alice", "lobby", "hi")
+
+ assertEquals("Alice@lobby[]: hi", recipient.receivedMessage().legacy())
+ }
+
+ @Test
fun `handleIncomingCrossServerMessage shows the recipient the sender qualified by server`() {
val handler = createHandler()
val recipient = TestUtils.createMockPlayer(name = "Bob")
diff --git a/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/i18n/ChatFormatTest.kt b/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/i18n/ChatFormatTest.kt
index 0d23395..c977fd1 100644
--- a/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/i18n/ChatFormatTest.kt
+++ b/platform-paper/src/test/kotlin/dev/m1sk9/lunaticChat/paper/i18n/ChatFormatTest.kt
@@ -4,6 +4,7 @@ import dev.m1sk9.lunaticChat.paper.config.key.MessageFormatConfig
import net.kyori.adventure.text.Component
import net.kyori.adventure.text.format.NamedTextColor
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer
+import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer
import kotlin.reflect.full.memberProperties
import kotlin.test.Test
import kotlin.test.assertEquals
@@ -12,25 +13,28 @@ import kotlin.test.assertTrue
class ChatFormatTest {
private val defaults = MessageFormatConfig()
- private val placeholderPattern = Regex("""\{([A-Za-z]+)}""")
+ private val placeholderPattern = Regex("""\{([A-Za-z_]+)}""")
private fun Component.legacy(): String = LegacyComponentSerializer.legacySection().serialize(this)
private fun MessageFormatConfig.renderAll(): List<String> =
listOf(
- directMessage("Alice", "Bob", "hi"),
- channelMessage("Alice", "general", "hi"),
+ directMessage("Alice", "Bob", "hi", "world"),
+ channelMessage("Alice", "general", "general", "Queen", "hi", "world", "Owner"),
crossServerGlobalChat("lobby", "Alice", "hi"),
).map { it.legacy() }
@Test
fun `the default direct message format renders sender, recipient and message`() {
- assertEquals("§7[§eAlice §7>> §eBob§7] §fhi", defaults.directMessage("Alice", "Bob", "hi").legacy())
+ assertEquals("§7[§eAlice §7>> §eBob§7] §fhi", defaults.directMessage("Alice", "Bob", "hi", "world").legacy())
}
@Test
fun `the default channel message format renders channel, sender and message`() {
- assertEquals("§7[§b#general§7] §eAlice: §fhi", defaults.channelMessage("Alice", "general", "hi").legacy())
+ assertEquals(
+ "§7[§b#general§7] §eAlice: §fhi",
+ defaults.channelMessage("Alice", "general", "general", "Queen", "hi", "world", "Owner").legacy(),
+ )
}
@Test
@@ -39,29 +43,56 @@ class ChatFormatTest {
}
@Test
+ fun `the direct message format renders the sender's world`() {
+ val formats = MessageFormatConfig(directMessageFormat = "{sender} ({world}): {message}")
+
+ assertEquals("Alice (world_nether): hi", formats.directMessage("Alice", "Bob", "hi", "world_nether").legacy())
+ }
+
+ @Test
+ fun `the channel format renders channel ID, display name, world and role`() {
+ val formats = MessageFormatConfig(channelMessageFormat = "{channel_id} {display_name} {world} {role}")
+
+ assertEquals(
+ "general-1 Queen world_nether Owner",
+ formats.channelMessage("Alice", "General", "general-1", "Queen", "hi", "world_nether", "Owner").legacy(),
+ )
+ }
+
+ @Test
+ fun `section sign codes in a display name become styles`() {
+ val formats = MessageFormatConfig(channelMessageFormat = "{display_name}")
+
+ val rendered = formats.channelMessage("Alice", "general", "general", "§cQueen", "hi", "world", "Owner")
+
+ assertEquals("Queen", PlainTextComponentSerializer.plainText().serialize(rendered))
+ assertEquals(NamedTextColor.RED, rendered.color())
+ }
+
+ @Test
fun `unknown placeholders and ones the format does not accept are left as written`() {
val formats = MessageFormatConfig(directMessageFormat = "{foo} {channel} {message}")
- assertEquals("{foo} {channel} hi", formats.directMessage("Alice", "Bob", "hi").legacy())
+ assertEquals("{foo} {channel} hi", formats.directMessage("Alice", "Bob", "hi", "world").legacy())
}
@Test
fun `a value containing a placeholder is inserted literally`() {
val formats = MessageFormatConfig(channelMessageFormat = "<{channel}> {message}")
- assertEquals("<{message}> hi", formats.channelMessage("Alice", "{message}", "hi").legacy())
+ assertEquals("<{message}> hi", formats.channelMessage("Alice", "{message}", "general", "Queen", "hi", "world", "Owner").legacy())
}
@Test
fun `dollar signs and backslashes in a value are inserted literally`() {
val formats = MessageFormatConfig(directMessageFormat = "{message}")
- assertEquals("$1 \\ {sender}", formats.directMessage("Alice", "Bob", "$1 \\ {sender}").legacy())
+ assertEquals("$1 \\ {sender}", formats.directMessage("Alice", "Bob", "$1 \\ {sender}", "world").legacy())
}
@Test
fun `section sign codes become styles rather than text`() {
- val rendered = defaults.directMessage("Alice", "Bob", "hi")
+ val rendered = defaults.directMessage("Alice", "Bob", "hi", "world")
assertTrue(rendered.children().isNotEmpty())
assertEquals(NamedTextColor.GRAY, rendered.children().first().color())
@@ -102,6 +133,6 @@ class ChatFormatTest {
val keys = ChatPlaceholder.entries.map { it.key }
assertEquals(keys.size, keys.toSet().size)
- keys.forEach { assertTrue(Regex("[A-Za-z]+").matches(it), "$it cannot be matched by the renderer") }
+ keys.forEach { assertTrue(Regex("[A-Za-z_]+").matches(it), "$it cannot be matched by the renderer") }
}
}
diff --git a/website/src/docs/configuration.md b/website/src/docs/configuration.md
index 4b46b55..35e7599 100644
--- a/website/src/docs/configuration.md
+++ b/website/src/docs/configuration.md
@@ -172,8 +172,8 @@ proxy logs for everyone else.
| Key | Default | Available Placeholders |
|-----|---------|----------------------|
-| `directMessageFormat` | `§7[§e{sender} §7>> §e{recipient}§7] §f{message}` | `{sender}`, `{recipient}`, `{message}` |
-| `channelMessageFormat` | `§7[§b#{channel}§7] §e{sender}: §f{message}` | `{sender}`, `{message}`, `{channel}` |
+| `directMessageFormat` | `§7[§e{sender} §7>> §e{recipient}§7] §f{message}` | `{sender}`, `{recipient}`, `{message}`, `{world}` |
+| `channelMessageFormat` | `§7[§b#{channel}§7] §e{sender}: §f{message}` | `{sender}`, `{message}`, `{channel}`, `{channel_id}`, `{display_name}`, `{world}`, `{role}` |
| `crossServerGlobalChatFormat` | `§7[§6{server}§7] §e{sender}: §f{message}` | `{sender}`, `{message}`, `{server}` |
## Data Files
diff --git a/website/src/docs/reference/message-format.md b/website/src/docs/reference/message-format.md
index 68fee84..b0976fa 100644
--- a/website/src/docs/reference/message-format.md
+++ b/website/src/docs/reference/message-format.md
@@ -17,6 +17,10 @@ These are the only settings `/lc reload` <Badge type="tip" text="v1.4.0~" /> app
| `{message}` | Message content | All |
| `{channel}` | Channel name | `channelMessageFormat` |
| `{server}` | Server name | `crossServerGlobalChatFormat` |
+| `{world}` <Badge type="tip" text="v1.5.0~" /> | World the sender is in. Empty for a direct message from another server | `directMessageFormat`, `channelMessageFormat` |
+| `{display_name}` <Badge type="tip" text="v1.5.0~" /> | Display name of the sender, including colors set by other plugins | `channelMessageFormat` |
+| `{channel_id}` <Badge type="tip" text="v1.5.0~" /> | Channel ID | `channelMessageFormat` |
+| `{role}` <Badge type="tip" text="v1.5.0~" /> | Sender's role in the channel (Owner / Moderator / Member) | `channelMessageFormat` |
## Format List
diff --git a/website/src/ja/docs/configuration.md b/website/src/ja/docs/configuration.md
index 59d0c73..a5c211a 100644
--- a/website/src/ja/docs/configuration.md
+++ b/website/src/ja/docs/configuration.md
@@ -167,8 +167,8 @@ LUNATICCHAT_DEBUG=velocity,protocol
| キー | デフォルト | 利用可能なプレースホルダー |
|------|------------|--------------------------|
-| `directMessageFormat` | `§7[§e{sender} §7>> §e{recipient}§7] §f{message}` | `{sender}`, `{recipient}`, `{message}` |
-| `channelMessageFormat` | `§7[§b#{channel}§7] §e{sender}: §f{message}` | `{sender}`, `{message}`, `{channel}` |
+| `directMessageFormat` | `§7[§e{sender} §7>> §e{recipient}§7] §f{message}` | `{sender}`, `{recipient}`, `{message}`, `{world}` |
+| `channelMessageFormat` | `§7[§b#{channel}§7] §e{sender}: §f{message}` | `{sender}`, `{message}`, `{channel}`, `{channel_id}`, `{display_name}`, `{world}`, `{role}` |
| `crossServerGlobalChatFormat` | `§7[§6{server}§7] §e{sender}: §f{message}` | `{sender}`, `{message}`, `{server}` |
## データファイル
diff --git a/website/src/ja/docs/reference/message-format.md b/website/src/ja/docs/reference/message-format.md
index 3eddd1a..79c6745 100644
--- a/website/src/ja/docs/reference/message-format.md
+++ b/website/src/ja/docs/reference/message-format.md
@@ -17,6 +17,10 @@ layout: doc
| `{message}` | メッセージの内容 | すべて |
| `{channel}` | チャンネル名 | `channelMessageFormat` |
| `{server}` | サーバー名 | `crossServerGlobalChatFormat` |
+| `{world}` <Badge type="tip" text="v1.5.0~" /> | 送信者がいるワールド名.別サーバーから届いたダイレクトメッセージでは空になります | `directMessageFormat`, `channelMessageFormat` |
+| `{display_name}` <Badge type="tip" text="v1.5.0~" /> | 送信者の表示名.他のプラグインが設定した色も含みます | `channelMessageFormat` |
+| `{channel_id}` <Badge type="tip" text="v1.5.0~" /> | チャンネル ID | `channelMessageFormat` |
+| `{role}` <Badge type="tip" text="v1.5.0~" /> | チャンネル内での送信者の役割 (オーナー / モデレーター / メンバー) | `channelMessageFormat` |
## フォーマット一覧