Skip to content

Commit a12f735

Browse files
authored
feat: Add signed message docs (#632)
1 parent ba2d5be commit a12f735

4 files changed

Lines changed: 116 additions & 1 deletion

File tree

astro.config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,7 @@ export default defineConfig({
211211
"paper/dev/component-api/introduction",
212212
"paper/dev/component-api/i18n",
213213
"paper/dev/component-api/audiences",
214+
"paper/dev/component-api/signed-messages",
214215
],
215216
},
216217
{
6.07 MB
Loading
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
---
2+
title: Signed messages
3+
description: A guide to working with SignedMessage objects.
4+
slug: paper/dev/component-api/signed-messages
5+
version: 1.19.4+
6+
---
7+
8+
Since [Minecraft version 1.19](https://minecraft.wiki/w/Java_Edition_1.19), the client now signs any
9+
messages it sends so that they are uniquely identifiable and verifiable to be sent by a specific player.
10+
With this update, they also introduced the ability **delete specific messages** previously sent by a player.
11+
12+
:::tip[Note]
13+
14+
This guide does not go in-depth into the specifics of chat signing and its implementation on either client or server.
15+
For a full overview, you can refer [to this linked gist](https://gist.github.com/kennytv/ed783dd244ca0321bbd882c347892874).
16+
17+
:::
18+
19+
## How are signed messages represented in code?
20+
Paper uses Adventure's [`SignedMessage`](https://jd.advntr.dev/api/latest/net/kyori/adventure/chat/SignedMessage.html)
21+
object to represent a signed message. We differentiate two kinds of signed messages: system messages and non-system messages.
22+
System messages (checked with [`SignedMessage#isSystem()`](https://jd.advntr.dev/api/latest/net/kyori/adventure/chat/SignedMessage.html#isSystem()))
23+
are messages send by the server, whilst non-system messages are not.
24+
25+
You can also differentiate the **signed plain text** `String` content of the message
26+
([`SignedMessage#message()`](https://jd.advntr.dev/api/latest/net/kyori/adventure/chat/SignedMessage.html#message()))
27+
from the unsigned, nullable [`Component`](https://jd.advntr.dev/api/latest/net/kyori/adventure/text/Component.html)
28+
content ([`SignedMessage#unsignedContent()`](https://jd.advntr.dev/api/latest/net/kyori/adventure/chat/SignedMessage.html#unsignedContent())).
29+
30+
## Obtaining a signed message
31+
Signed messages can be obtained in two ways.
32+
33+
1. From an [`AsyncChatEvent`](jd:paper:io.papermc.paper.event.player.AsyncChatEvent) using
34+
[`AbstractChatEvent#signedMessage()`](jd:paper:io.papermc.paper.event.player.AbstractChatEvent#signedMessage()).
35+
36+
2. From an [`ArgumentTypes.signedMessage()`](jd:paper:io.papermc.paper.command.brigadier.argument.ArgumentTypes#signedMessage())
37+
Brigadier argument type.
38+
39+
## Using signed messages
40+
You can send signed message objects to an [`Audience`](https://jd.advntr.dev/api/latest/net/kyori/adventure/audience/Audience.html)
41+
using the [`Audience#sendMessage(SignedMessage, ChatType.Bound)`](https://jd.advntr.dev/api/latest/net/kyori/adventure/audience/Audience.html#sendMessage(net.kyori.adventure.chat.SignedMessage,net.kyori.adventure.chat.ChatType.Bound))
42+
method. You can obtain a [`ChatType.Bound`](https://jd.advntr.dev/api/latest/net/kyori/adventure/chat/ChatType.Bound.html) object
43+
from the [`ChatType`](https://jd.advntr.dev/api/latest/net/kyori/adventure/chat/ChatType.html) interface.
44+
45+
Deleting messages is much simpler. Adventure provides the [`Audience#deleteMessage(SignedMessage)`](https://jd.advntr.dev/api/latest/net/kyori/adventure/audience/Audience.html#deleteMessage(net.kyori.adventure.chat.SignedMessage))
46+
or [`Audience#deleteMessage(SignedMessage.Signature)`](https://jd.advntr.dev/api/latest/net/kyori/adventure/audience/Audience.html#deleteMessage(net.kyori.adventure.chat.SignedMessage.Signature))
47+
methods for that.
48+
49+
## Example: Making user sent messages deletable
50+
For our example, we will create a chat format plugin which allows a user to delete
51+
their own messages in case they made a mistake. For this we will use the [`AsyncChatEvent`](jd:paper:io.papermc.paper.event.player.AsyncChatEvent).
52+
53+
:::tip[AsyncChatEvent]
54+
55+
The [`AsyncChatEvent`](jd:paper:io.papermc.paper.event.player.AsyncChatEvent) is covered in the [chat events](/paper/dev/chat-events)
56+
documentation page. If you want to read up on more detail on the chat renderer, you can do so there.
57+
58+
:::
59+
60+
### In-game preview
61+
![](./assets/signed-messages-deletion.gif)
62+
63+
### Code
64+
```java title="SignedChatListener.java" collapse={1-10} showLineNumbers
65+
package io.papermc.docs.signedmessages;
66+
67+
import io.papermc.paper.event.player.AsyncChatEvent;
68+
import net.kyori.adventure.text.Component;
69+
import net.kyori.adventure.text.event.ClickEvent;
70+
import net.kyori.adventure.text.format.NamedTextColor;
71+
import net.kyori.adventure.text.format.TextDecoration;
72+
import org.bukkit.Bukkit;
73+
import org.bukkit.event.EventHandler;
74+
import org.bukkit.event.Listener;
75+
76+
public class SignedChatListener implements Listener {
77+
78+
@EventHandler
79+
void onPlayerChat(AsyncChatEvent event) {
80+
// We modify the chat format, so we use a chat renderer.
81+
event.renderer((player, playerName, message, viewer) -> {
82+
// This is the base format of our message. It will format chat as "<player> » <message>".
83+
final Component base = Component.textOfChildren(
84+
playerName.colorIfAbsent(NamedTextColor.GOLD),
85+
Component.text(" » ", NamedTextColor.DARK_GRAY),
86+
message
87+
);
88+
89+
// Send the base format to any player who is not the sender.
90+
if (viewer != player) {
91+
return base;
92+
}
93+
94+
// Create a base delete suffix. The creation is separated into two
95+
// parts purely for readability reasons.
96+
final Component deleteCrossBase = Component.textOfChildren(
97+
Component.text("[", NamedTextColor.DARK_GRAY),
98+
Component.text("X", NamedTextColor.DARK_RED, TextDecoration.BOLD),
99+
Component.text("]", NamedTextColor.DARK_GRAY)
100+
);
101+
102+
// Add a hover and click event to the delete suffix.
103+
final Component deleteCross = deleteCrossBase
104+
.hoverEvent(Component.text("Click to delete your message!", NamedTextColor.RED))
105+
// We retrieve the signed message with event.signedMessage() and request a server-wide deletion if the
106+
// deletion cross were to be clicked.
107+
.clickEvent(ClickEvent.callback(audience -> Bukkit.getServer().deleteMessage(event.signedMessage())));
108+
109+
// Send the base format but with the delete suffix.
110+
return base.appendSpace().append(deleteCross);
111+
});
112+
}
113+
}
114+
```

src/content/docs/paper/dev/api/menu-type-api.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ The bottom linked inventory is the player's inventory.
3636

3737
Some views have specialized subinterfaces for quickly checking their type, like [`FurnaceView`](jd:paper:org.bukkit.inventory.view.FurnaceView)
3838
for furnace inventories. For other views, which don't have their own sub type, you can instead use the
39-
[`InventoryView#getMenuType`](jd:paper.org.bukkit.inventory.InventoryView#getMenuType()) method.
39+
[`InventoryView#getMenuType`](jd:paper:org.bukkit.inventory.InventoryView#getMenuType()) method.
4040

4141
## Building inventory views from menu types
4242
The most common way to create inventory views from menu types is by using their respective builders. **Every menu type

0 commit comments

Comments
 (0)