diff --git a/api/src/main/java/net/kyori/adventure/audience/Audience.java b/api/src/main/java/net/kyori/adventure/audience/Audience.java index 57dbbdf3b..7546fdbcd 100644 --- a/api/src/main/java/net/kyori/adventure/audience/Audience.java +++ b/api/src/main/java/net/kyori/adventure/audience/Audience.java @@ -29,6 +29,8 @@ import java.util.function.Predicate; import java.util.stream.Collector; import net.kyori.adventure.bossbar.BossBar; +import net.kyori.adventure.chat.ChatType; +import net.kyori.adventure.chat.SignedMessage; import net.kyori.adventure.identity.Identified; import net.kyori.adventure.identity.Identity; import net.kyori.adventure.inventory.Book; @@ -39,6 +41,7 @@ import net.kyori.adventure.text.ComponentLike; import net.kyori.adventure.title.Title; import net.kyori.adventure.title.TitlePart; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; /** @@ -174,8 +177,9 @@ default void forEachAudience(final @NotNull Consumer action) { action.accept(this); } + /* Start: system messages */ /** - * Sends a chat message with a {@link Identity#nil() nil} identity to this {@link Audience}. + * Sends a system chat message to this {@link Audience}. * * @param message a message * @see Component @@ -185,157 +189,260 @@ default void forEachAudience(final @NotNull Consumer action) { */ @ForwardingAudienceOverrideNotRequired default void sendMessage(final @NotNull ComponentLike message) { - this.sendMessage(Identity.nil(), message); + this.sendMessage(message.asComponent()); } /** - * Sends a chat message from the given {@link Identified} to this {@link Audience}. + * Sends a system chat message to this {@link Audience}. * - * @param source the source of the message * @param message a message * @see Component - * @since 4.0.0 + * @see #sendMessage(Identified, Component) + * @see #sendMessage(Identity, Component) + * @since 4.1.0 */ - @ForwardingAudienceOverrideNotRequired - default void sendMessage(final @NotNull Identified source, final @NotNull ComponentLike message) { - this.sendMessage(source, message.asComponent()); + @SuppressWarnings("deprecation") + default void sendMessage(final @NotNull Component message) { + this.sendMessage(message, MessageType.SYSTEM); } /** - * Sends a chat message from the entity represented by the given {@link Identity} (or the game using {@link Identity#nil()}) to this {@link Audience}. + * Sends a system chat message to this {@link Audience} ignoring the provided {@link MessageType}. * - * @param source the identity of the source of the message * @param message a message + * @param type the type * @see Component - * @since 4.0.0 + * @see #sendMessage(Identified, ComponentLike, MessageType) + * @see #sendMessage(Identity, ComponentLike, MessageType) + * @since 4.1.0 + * @deprecated for removal since 4.12.0, {@link MessageType} is deprecated for removal, use {@link #sendMessage(ComponentLike)} */ + @ApiStatus.ScheduledForRemoval(inVersion = "5.0.0") + @Deprecated @ForwardingAudienceOverrideNotRequired - default void sendMessage(final @NotNull Identity source, final @NotNull ComponentLike message) { - this.sendMessage(source, message.asComponent()); + default void sendMessage(final @NotNull ComponentLike message, final @NotNull MessageType type) { + this.sendMessage(message.asComponent(), type); } /** - * Sends a chat message with a {@link Identity#nil() nil} identity to this {@link Audience}. + * Sends a system chat message to this {@link Audience} ignoring the provided {@link MessageType}. * * @param message a message + * @param type the type * @see Component - * @see #sendMessage(Identified, Component) - * @see #sendMessage(Identity, Component) + * @see #sendMessage(Identified, Component, MessageType) + * @see #sendMessage(Identity, Component, MessageType) * @since 4.1.0 + * @deprecated for removal since 4.12.0, {@link MessageType} is deprecated for removal, use {@link #sendMessage(Component)} instead */ + @ApiStatus.ScheduledForRemoval(inVersion = "5.0.0") + @Deprecated @ForwardingAudienceOverrideNotRequired - default void sendMessage(final @NotNull Component message) { - this.sendMessage(Identity.nil(), message); + default void sendMessage(final @NotNull Component message, final @NotNull MessageType type) { + this.sendMessage(Identity.nil(), message, type); } + /* End: system messages */ + /* Start: unsigned player messages */ /** - * Sends a chat message from the given {@link Identified} to this {@link Audience}. + * Sends an unsigned player chat message from the given {@link Identified} to this {@link Audience} with the {@link ChatType#CHAT system} chat type. * * @param source the source of the message * @param message a message * @see Component * @since 4.0.0 + * @deprecated since 4.12.0, the client errors on and can reject identified messages without {@link SignedMessage} data, this may be unsupported in the future, use {@link #sendMessage(SignedMessage, ChatType.Bound)} instead */ + @Deprecated @ForwardingAudienceOverrideNotRequired - default void sendMessage(final @NotNull Identified source, final @NotNull Component message) { - this.sendMessage(source, message, MessageType.SYSTEM); + default void sendMessage(final @NotNull Identified source, final @NotNull ComponentLike message) { + this.sendMessage(source, message.asComponent()); } /** - * Sends a chat message from the entity represented by the given {@link Identity} (or the game using {@link Identity#nil()}) to this {@link Audience}. + * Sends an unsigned player chat message from the entity represented by the given {@link Identity} to this {@link Audience} with the {@link ChatType#CHAT system} chat type. * * @param source the identity of the source of the message * @param message a message * @see Component * @since 4.0.0 + * @deprecated since 4.12.0, the client errors on and can reject identified messages without {@link SignedMessage} data, this may be unsupported in the future, use {@link #sendMessage(SignedMessage, ChatType.Bound)} instead */ + @Deprecated @ForwardingAudienceOverrideNotRequired - default void sendMessage(final @NotNull Identity source, final @NotNull Component message) { - this.sendMessage(source, message, MessageType.SYSTEM); + default void sendMessage(final @NotNull Identity source, final @NotNull ComponentLike message) { + this.sendMessage(source, message.asComponent()); } /** - * Sends a chat message with a {@link Identity#nil() nil} identity to this {@link Audience}. + * Sends an unsigned player chat message from the given {@link Identified} to this {@link Audience} with the {@link ChatType#CHAT system} chat type. * + * @param source the source of the message * @param message a message - * @param type the type * @see Component - * @see #sendMessage(Identified, ComponentLike, MessageType) - * @see #sendMessage(Identity, ComponentLike, MessageType) - * @since 4.1.0 + * @since 4.0.0 + * @deprecated since 4.12.0, the client errors on receiving and can reject identified messages without {@link SignedMessage} data, this may be unsupported in the future, use {@link #sendMessage(SignedMessage, ChatType.Bound)} instead */ + @Deprecated @ForwardingAudienceOverrideNotRequired - default void sendMessage(final @NotNull ComponentLike message, final @NotNull MessageType type) { - this.sendMessage(Identity.nil(), message, type); + default void sendMessage(final @NotNull Identified source, final @NotNull Component message) { + this.sendMessage(source, message, MessageType.CHAT); } /** - * Sends a chat message from the given {@link Identified} to this {@link Audience}. + * Sends an unsigned player chat message from the entity represented by the given {@link Identity} to this {@link Audience} with the {@link ChatType#CHAT system} chat type. * - * @param source the source of the message + * @param source the identity of the source of the message * @param message a message - * @param type the type * @see Component * @since 4.0.0 + * @deprecated since 4.12.0, the client errors on receiving and can reject identified messages without {@link SignedMessage} data, this may be unsupported in the future, use {@link #sendMessage(SignedMessage, ChatType.Bound)} instead */ + @Deprecated @ForwardingAudienceOverrideNotRequired - default void sendMessage(final @NotNull Identified source, final @NotNull ComponentLike message, final @NotNull MessageType type) { - this.sendMessage(source, message.asComponent(), type); + default void sendMessage(final @NotNull Identity source, final @NotNull Component message) { + this.sendMessage(source, message, MessageType.CHAT); } /** - * Sends a chat message from the entity represented by the given {@link Identity} (or the game using {@link Identity#nil()}) to this {@link Audience}. + * Sends an unsigned player chat message from the given {@link Identified} to this {@link Audience} with the {@link ChatType} corresponding to the provided {@link MessageType}. * - * @param source the identity of the source of the message + * @param source the source of the message * @param message a message * @param type the type * @see Component * @since 4.0.0 + * @deprecated for removal since 4.12.0, {@link MessageType} is deprecated for removal and the client errors on receiving and can reject identified messages without {@link SignedMessage} data, use {@link #sendMessage(SignedMessage, ChatType.Bound)} instead */ + @ApiStatus.ScheduledForRemoval(inVersion = "5.0.0") + @Deprecated @ForwardingAudienceOverrideNotRequired - default void sendMessage(final @NotNull Identity source, final @NotNull ComponentLike message, final @NotNull MessageType type) { + default void sendMessage(final @NotNull Identified source, final @NotNull ComponentLike message, final @NotNull MessageType type) { this.sendMessage(source, message.asComponent(), type); } /** - * Sends a chat message with a {@link Identity#nil() nil} identity to this {@link Audience}. + * Sends an unsigned player chat message from the entity represented by the given {@link Identity} to this {@link Audience}. * + * @param source the identity of the source of the message * @param message a message * @param type the type * @see Component - * @see #sendMessage(Identified, Component, MessageType) - * @see #sendMessage(Identity, Component, MessageType) - * @since 4.1.0 + * @since 4.0.0 + * @deprecated for removal since 4.12.0, {@link MessageType} is deprecated for removal and the client errors on receiving and can reject identified messages without {@link SignedMessage} data, use {@link #sendMessage(SignedMessage, ChatType.Bound)} instead */ + @ApiStatus.ScheduledForRemoval(inVersion = "5.0.0") + @Deprecated @ForwardingAudienceOverrideNotRequired - default void sendMessage(final @NotNull Component message, final @NotNull MessageType type) { - this.sendMessage(Identity.nil(), message, type); + default void sendMessage(final @NotNull Identity source, final @NotNull ComponentLike message, final @NotNull MessageType type) { + this.sendMessage(source, message.asComponent(), type); } /** - * Sends a chat message. + * Sends an unsigned player chat message from the given {@link Identified} to this {@link Audience} with the {@link ChatType} corresponding to the provided {@link MessageType}. * * @param source the source of the message * @param message a message * @param type the type * @see Component * @since 4.0.0 + * @deprecated for removal since 4.12.0, {@link MessageType} is deprecated for removal and the client errors on receiving and can reject identified messages without {@link SignedMessage} data, use {@link #sendMessage(SignedMessage, ChatType.Bound)} instead */ + @ApiStatus.ScheduledForRemoval(inVersion = "5.0.0") + @Deprecated default void sendMessage(final @NotNull Identified source, final @NotNull Component message, final @NotNull MessageType type) { this.sendMessage(source.identity(), message, type); } /** - * Sends a chat message. + * Sends a player chat message from the entity represented by the given {@link Identity} to this {@link Audience} with the {@link ChatType} corresponding to the provided {@link MessageType}. * * @param source the identity of the source of the message * @param message a message * @param type the type * @see Component * @since 4.0.0 + * @deprecated for removal since 4.12.0, {@link MessageType} is deprecated for removal and the client errors on receiving and can reject identified messages without {@link SignedMessage} data, use {@link #sendMessage(SignedMessage, ChatType.Bound)} instead */ + @ApiStatus.ScheduledForRemoval(inVersion = "5.0.0") + @Deprecated default void sendMessage(final @NotNull Identity source, final @NotNull Component message, final @NotNull MessageType type) { + // implementation required + } + /* End: unsigned player messages */ + + /* Start: disguised player messages */ + /** + * Sends a message to this {@link Audience} with the provided {@link ChatType.Bound bound chat type}. + * + * @param message the component content + * @param boundChatType the bound chat type + * @since 4.12.0 + * @sinceMinecraft 1.19 + */ + @SuppressWarnings("deprecation") + default void sendMessage(final @NotNull Component message, final ChatType.@NotNull Bound boundChatType) { + this.sendMessage(message, MessageType.CHAT); + } + + /** + * Sends a message to this {@link Audience} with the provided {@link ChatType.Bound bound chat type}. + * + * @param message the component content + * @param boundChatType the bound chat type + * @since 4.12.0 + * @sinceMinecraft 1.19 + */ + @ForwardingAudienceOverrideNotRequired + default void sendMessage(final @NotNull ComponentLike message, final ChatType.@NotNull Bound boundChatType) { + this.sendMessage(message.asComponent(), boundChatType); + } + /* End: disguised player messages + + /* Start: signed player messages */ + /** + * Sends a signed player message to this {@link Audience} with the provided {@link ChatType.Bound bound chat type}. + * + * @param signedMessage the signed message data + * @param boundChatType the bound chat type + * @since 4.12.0 + * @sinceMinecraft 1.19 + */ + @SuppressWarnings("deprecation") + default void sendMessage(final @NotNull SignedMessage signedMessage, final ChatType.@NotNull Bound boundChatType) { + final Component content = signedMessage.unsignedContent() != null ? signedMessage.unsignedContent() : Component.text(signedMessage.message()); + if (signedMessage.isSystem()) { + this.sendMessage(content); + } else { + this.sendMessage(signedMessage.identity(), content, MessageType.CHAT); + } + } + + /** + * Requests deletion of a message with the provided {@link SignedMessage}'s signature. + * + * @param signedMessage the message to delete + * @see SignedMessage#canDelete() + * @since 4.12.0 + * @sinceMinecraft 1.19 + */ + @ForwardingAudienceOverrideNotRequired + default void deleteMessage(final @NotNull SignedMessage signedMessage) { + if (signedMessage.canDelete()) { + this.deleteMessage(Objects.requireNonNull(signedMessage.signature())); + } + } + + /** + * Requests deletion of a message with the provided {@link SignedMessage.Signature}. + * + * @param signature the signature + * @since 4.12.0 + * @sinceMinecraft 1.19 + */ + default void deleteMessage(final SignedMessage.@NotNull Signature signature) { } + /* End: signed player messages */ /** * Sends a message on the action bar. diff --git a/api/src/main/java/net/kyori/adventure/audience/EmptyAudience.java b/api/src/main/java/net/kyori/adventure/audience/EmptyAudience.java index 719c11c22..921e8fa46 100644 --- a/api/src/main/java/net/kyori/adventure/audience/EmptyAudience.java +++ b/api/src/main/java/net/kyori/adventure/audience/EmptyAudience.java @@ -27,10 +27,13 @@ import java.util.function.Consumer; import java.util.function.Predicate; import java.util.function.Supplier; +import net.kyori.adventure.chat.ChatType; +import net.kyori.adventure.chat.SignedMessage; import net.kyori.adventure.identity.Identified; import net.kyori.adventure.identity.Identity; import net.kyori.adventure.inventory.Book; import net.kyori.adventure.pointer.Pointer; +import net.kyori.adventure.text.Component; import net.kyori.adventure.text.ComponentLike; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; @@ -70,23 +73,29 @@ public void sendMessage(final @NotNull ComponentLike message) { } @Override - public void sendMessage(final @NotNull Identified source, final @NotNull ComponentLike message) { + public void sendMessage(final @NotNull Component message) { } @Override - public void sendMessage(final @NotNull Identity source, final @NotNull ComponentLike message) { + @Deprecated + public void sendMessage(final @NotNull Identified source, final @NotNull Component message, final @NotNull MessageType type) { } @Override - public void sendMessage(final @NotNull ComponentLike message, final @NotNull MessageType type) { + @Deprecated + public void sendMessage(final @NotNull Identity source, final @NotNull Component message, final @NotNull MessageType type) { } @Override - public void sendMessage(final @NotNull Identified source, final @NotNull ComponentLike message, final @NotNull MessageType type) { + public void sendMessage(final @NotNull Component message, final ChatType.@NotNull Bound boundChatType) { } @Override - public void sendMessage(final @NotNull Identity source, final @NotNull ComponentLike message, final @NotNull MessageType type) { + public void sendMessage(final @NotNull SignedMessage signedMessage, final ChatType.@NotNull Bound boundChatType) { + } + + @Override + public void deleteMessage(final SignedMessage.@NotNull Signature signature) { } @Override diff --git a/api/src/main/java/net/kyori/adventure/audience/ForwardingAudience.java b/api/src/main/java/net/kyori/adventure/audience/ForwardingAudience.java index 3bd50c7e1..a728ebc1d 100644 --- a/api/src/main/java/net/kyori/adventure/audience/ForwardingAudience.java +++ b/api/src/main/java/net/kyori/adventure/audience/ForwardingAudience.java @@ -31,6 +31,8 @@ import java.util.function.Predicate; import java.util.function.Supplier; import net.kyori.adventure.bossbar.BossBar; +import net.kyori.adventure.chat.ChatType; +import net.kyori.adventure.chat.SignedMessage; import net.kyori.adventure.identity.Identified; import net.kyori.adventure.identity.Identity; import net.kyori.adventure.inventory.Book; @@ -96,11 +98,33 @@ default void forEachAudience(final @NotNull Consumer action) { } @Override + default void sendMessage(final @NotNull Component message) { + for (final Audience audience : this.audiences()) audience.sendMessage(message); + } + + @Override + default void sendMessage(final @NotNull Component message, final ChatType.@NotNull Bound boundChatType) { + for (final Audience audience : this.audiences()) audience.sendMessage(message, boundChatType); + } + + @Override + default void sendMessage(final @NotNull SignedMessage signedMessage, final ChatType.@NotNull Bound boundChatType) { + for (final Audience audience : this.audiences()) audience.sendMessage(signedMessage, boundChatType); + } + + @Override + default void deleteMessage(final SignedMessage.@NotNull Signature signature) { + for (final Audience audience : this.audiences()) audience.deleteMessage(signature); + } + + @Override + @Deprecated default void sendMessage(final @NotNull Identified source, final @NotNull Component message, final @NotNull MessageType type) { for (final Audience audience : this.audiences()) audience.sendMessage(source, message, type); } @Override + @Deprecated default void sendMessage(final @NotNull Identity source, final @NotNull Component message, final @NotNull MessageType type) { for (final Audience audience : this.audiences()) audience.sendMessage(source, message, type); } @@ -237,11 +261,33 @@ default void forEachAudience(final @NotNull Consumer action) { } @Override + default void sendMessage(final @NotNull Component message) { + this.audience().sendMessage(message); + } + + @Override + default void sendMessage(final @NotNull Component message, final ChatType.@NotNull Bound boundChatType) { + this.audience().sendMessage(message, boundChatType); + } + + @Override + default void sendMessage(final @NotNull SignedMessage signedMessage, final ChatType.@NotNull Bound boundChatType) { + this.audience().sendMessage(signedMessage, boundChatType); + } + + @Override + default void deleteMessage(final SignedMessage.@NotNull Signature signature) { + this.audience().deleteMessage(signature); + } + + @Override + @Deprecated default void sendMessage(final @NotNull Identified source, final @NotNull Component message, final @NotNull MessageType type) { this.audience().sendMessage(source, message, type); } @Override + @Deprecated default void sendMessage(final @NotNull Identity source, final @NotNull Component message, final @NotNull MessageType type) { this.audience().sendMessage(source, message, type); } diff --git a/api/src/main/java/net/kyori/adventure/audience/MessageType.java b/api/src/main/java/net/kyori/adventure/audience/MessageType.java index d55016621..6de8dec58 100644 --- a/api/src/main/java/net/kyori/adventure/audience/MessageType.java +++ b/api/src/main/java/net/kyori/adventure/audience/MessageType.java @@ -23,22 +23,34 @@ */ package net.kyori.adventure.audience; +import net.kyori.adventure.chat.ChatType; +import org.jetbrains.annotations.ApiStatus; + /** * Message types. * * @since 4.0.0 + * @deprecated for removal since 4.12.0, use separate methods on {@link Audience} for sending player or system messages */ +@ApiStatus.ScheduledForRemoval(inVersion = "5.0.0") +@Deprecated public enum MessageType { /** * Chat message type. * * @since 4.0.0 + * @deprecated for removal since 4.12.0, use {@link ChatType#CHAT} instead */ + @ApiStatus.ScheduledForRemoval(inVersion = "5.0.0") + @Deprecated CHAT, /** * System message type. * * @since 4.0.0 + * @deprecated for removal since 4.12.0 */ + @ApiStatus.ScheduledForRemoval(inVersion = "5.0.0") + @Deprecated SYSTEM; } diff --git a/api/src/main/java/net/kyori/adventure/chat/ChatType.java b/api/src/main/java/net/kyori/adventure/chat/ChatType.java new file mode 100644 index 000000000..8e5af1824 --- /dev/null +++ b/api/src/main/java/net/kyori/adventure/chat/ChatType.java @@ -0,0 +1,192 @@ +/* + * This file is part of adventure, licensed under the MIT License. + * + * Copyright (c) 2017-2022 KyoriPowered + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package net.kyori.adventure.chat; + +import java.util.stream.Stream; +import net.kyori.adventure.key.Key; +import net.kyori.adventure.key.Keyed; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.ComponentLike; +import net.kyori.examination.Examinable; +import net.kyori.examination.ExaminableProperty; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import static java.util.Objects.requireNonNull; + +/** + * A type of chat. + * + * @since 4.12.0 + * @sinceMinecraft 1.19 + */ +public interface ChatType extends Examinable, Keyed { + /** + * A chat message from a player. + * + * @since 4.12.0 + * @sinceMinecraft 1.19 + */ + ChatType CHAT = new ChatTypeImpl(Key.key("chat")); + + /** + * A message send as a result of using the {@code /say} command. + * + * @since 4.12.0 + * @sinceMinecraft 1.19 + */ + ChatType SAY_COMMAND = new ChatTypeImpl(Key.key("say_command")); + + /** + * A message received as a result of using the {@code /msg} command. + * + * @since 4.12.0 + * @sinceMinecraft 1.19 + */ + ChatType MSG_COMMAND_INCOMING = new ChatTypeImpl(Key.key("msg_command_incoming")); + + /** + * A message sent as a result of using the {@code /msg} command. + * + * @since 4.12.0 + * @sinceMinecraft 1.19 + */ + ChatType MSG_COMMAND_OUTGOING = new ChatTypeImpl(Key.key("msg_command_outgoing")); + + /** + * A message received as a result of using the {@code /teammsg} command. + * + * @since 4.12.0 + * @sinceMinecraft 1.19 + */ + ChatType TEAM_MSG_COMMAND_INCOMING = new ChatTypeImpl(Key.key("team_msg_command_incoming")); + + /** + * A message sent as a result of using the {@code /teammsg} command. + * + * @since 4.12.0 + * @sinceMinecraft 1.19 + */ + ChatType TEAM_MSG_COMMAND_OUTGOING = new ChatTypeImpl(Key.key("team_msg_command_outgoing")); + + /** + * A message sent as a result of using the {@code /me} command. + * + * @since 4.12.0 + * @sinceMinecraft 1.19 + */ + ChatType EMOTE_COMMAND = new ChatTypeImpl(Key.key("emote_command")); + + /** + * Creates a new chat type with a given key. + * + * @param key the key + * @return the chat type + * @since 4.12.0 + */ + static @NotNull ChatType chatType(final @NotNull Keyed key) { + return key instanceof ChatType ? (ChatType) key : new ChatTypeImpl(requireNonNull(key, "key").key()); + } + + /** + * Creates a bound chat type with a name {@link Component}. + * + * @param name the name component + * @return a new bound chat type + * @since 4.12.0 + * @sinceMinecraft 1.19 + */ + @Contract(value = "_ -> new", pure = true) + default ChatType.@NotNull Bound bind(final @NotNull ComponentLike name) { + return this.bind(name, null); + } + + /** + * Creates a bound chat type with a name and target {@link Component}. + * + * @param name the name component + * @param target the optional target component + * @return a new bound chat type + * @since 4.12.0 + * @sinceMinecraft 1.19 + */ + @Contract(value = "_, _ -> new", pure = true) + default ChatType.@NotNull Bound bind(final @NotNull ComponentLike name, final @Nullable ComponentLike target) { + return new ChatTypeImpl.BoundImpl(this, requireNonNull(name.asComponent(), "name"), ComponentLike.unbox(target)); + } + + @Override + default @NotNull Stream examinableProperties() { + return Stream.of(ExaminableProperty.of("key", this.key())); + } + + /** + * A bound {@link ChatType}. + * + * @since 4.12.0 + * @sinceMinecraft 1.19 + */ + interface Bound extends Examinable { + + /** + * Gets the chat type. + * + * @return the chat type + * @since 4.12.0 + * @sinceMinecraft 1.19 + */ + @Contract(pure = true) + @NotNull ChatType type(); + + /** + * Get the name component. + * + * @return the name component + * @since 4.12.0 + * @sinceMinecraft 1.19 + */ + @Contract(pure = true) + @NotNull Component name(); + + /** + * Get the target component. + * + * @return the target component or null + * @since 4.12.0 + * @sinceMinecraft 1.19 + */ + @Contract(pure = true) + @Nullable Component target(); + + @Override + default @NotNull Stream examinableProperties() { + return Stream.of( + ExaminableProperty.of("type", this.type()), + ExaminableProperty.of("name", this.name()), + ExaminableProperty.of("target", this.target()) + ); + } + } +} diff --git a/api/src/main/java/net/kyori/adventure/chat/ChatTypeImpl.java b/api/src/main/java/net/kyori/adventure/chat/ChatTypeImpl.java new file mode 100644 index 000000000..d2d46d98f --- /dev/null +++ b/api/src/main/java/net/kyori/adventure/chat/ChatTypeImpl.java @@ -0,0 +1,80 @@ +/* + * This file is part of adventure, licensed under the MIT License. + * + * Copyright (c) 2017-2022 KyoriPowered + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package net.kyori.adventure.chat; + +import net.kyori.adventure.internal.Internals; +import net.kyori.adventure.key.Key; +import net.kyori.adventure.text.Component; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +final class ChatTypeImpl implements ChatType { + private final Key key; + + ChatTypeImpl(final @NotNull Key key) { + this.key = key; + } + + @Override + public @NotNull Key key() { + return this.key; + } + + @Override + public String toString() { + return Internals.toString(this); + } + + static final class BoundImpl implements ChatType.Bound { + private final ChatType chatType; + private final Component name; + private final @Nullable Component target; + + BoundImpl(final ChatType chatType, final Component name, final @Nullable Component target) { + this.chatType = chatType; + this.name = name; + this.target = target; + } + + @Override + public @NotNull ChatType type() { + return this.chatType; + } + + @Override + public @NotNull Component name() { + return this.name; + } + + @Override + public @Nullable Component target() { + return this.target; + } + + @Override + public String toString() { + return Internals.toString(this); + } + } +} diff --git a/api/src/main/java/net/kyori/adventure/chat/SignedMessage.java b/api/src/main/java/net/kyori/adventure/chat/SignedMessage.java new file mode 100644 index 000000000..3b1e216d0 --- /dev/null +++ b/api/src/main/java/net/kyori/adventure/chat/SignedMessage.java @@ -0,0 +1,184 @@ +/* + * This file is part of adventure, licensed under the MIT License. + * + * Copyright (c) 2017-2022 KyoriPowered + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package net.kyori.adventure.chat; + +import java.time.Instant; +import java.util.stream.Stream; +import net.kyori.adventure.identity.Identified; +import net.kyori.adventure.identity.Identity; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.ComponentLike; +import net.kyori.examination.Examinable; +import net.kyori.examination.ExaminableProperty; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * A signed chat message. + * + * @since 4.12.0 + * @sinceMinecraft 1.19 + */ +@ApiStatus.NonExtendable +public interface SignedMessage extends Identified, Examinable { + + /** + * Creates a signature wrapper. + * + * @param signature the signature + * @return a new signature + * @since 4.12.0 + * @sinceMinecraft 1.19 + */ + @Contract(value = "_ -> new", pure = true) + static @NotNull Signature signature(final byte[] signature) { + return new SignedMessageImpl.SignatureImpl(signature); + } + + /** + * Creates a system {@link SignedMessage}. + * + * @param message the message + * @param unsignedContent the optional unsigned component content + * @return a new system {@link SignedMessage} + * @since 4.12.0 + * @sinceMinecraft 1.19 + */ + @Contract(value = "_, _ -> new", pure = true) + static @NotNull SignedMessage system(final @NotNull String message, final @Nullable ComponentLike unsignedContent) { + return new SignedMessageImpl(message, ComponentLike.unbox(unsignedContent)); + } + + /** + * The time that the message was sent. + * + * @return the timestamp + * @since 4.12.0 + * @sinceMinecraft 1.19 + */ + @Contract(pure = true) + @NotNull Instant timestamp(); + + /** + * The salt. + * + * @return the salt + * @since 4.12.0 + * @sinceMinecraft 1.19 + */ + @Contract(pure = true) + long salt(); + + /** + * The signature of the message. + * + * @return the signature + * @since 4.12.0 + * @sinceMinecraft 1.19 + */ + @Contract(pure = true) + @Nullable Signature signature(); + + /** + * The unsigned component content. + * + * @return the component or null + * @since 4.12.0 + * @sinceMinecraft 1.19 + */ + @Contract(pure = true) + @Nullable Component unsignedContent(); + + /** + * The plain string message. + * + * @return the plain string message + * @since 4.12.0 + * @sinceMinecraft 1.19 + */ + @Contract(pure = true) + @NotNull String message(); + + /** + * Checks if this message is a system message. + * + * @return true if system + * @since 4.12.0 + * @sinceMinecraft 1.19 + */ + @Contract(pure = true) + default boolean isSystem() { + return this.identity() == Identity.nil(); + } + + /** + * Checks if this message can be deleted via {@link net.kyori.adventure.audience.Audience#deleteMessage(SignedMessage)}. + * + * @return true if supports deletion + * @since 4.12.0 + * @sinceMinecraft 1.19 + */ + @Contract(pure = true) + default boolean canDelete() { + return this.signature() != null; + } + + @Override + default @NotNull Stream examinableProperties() { + return Stream.of( + ExaminableProperty.of("timestamp", this.timestamp()), + ExaminableProperty.of("salt", this.salt()), + ExaminableProperty.of("signature", this.signature()), + ExaminableProperty.of("unsignedContent", this.unsignedContent()), + ExaminableProperty.of("message", this.message()) + ); + } + + /** + * A signature wrapper type. + * + * @since 4.12.0 + * @sinceMinecraft 1.19 + */ + @ApiStatus.NonExtendable + interface Signature extends Examinable { + + /** + * Gets the bytes for this signature. + * + * @return the bytes + * @since 4.12.0 + * @sinceMinecraft 1.19 + */ + @Contract(pure = true) + byte[] bytes(); + + @Override + default @NotNull Stream examinableProperties() { + return Stream.of(ExaminableProperty.of("bytes", this.bytes())); + } + } +} diff --git a/api/src/main/java/net/kyori/adventure/chat/SignedMessageImpl.java b/api/src/main/java/net/kyori/adventure/chat/SignedMessageImpl.java new file mode 100644 index 000000000..0e75f0edb --- /dev/null +++ b/api/src/main/java/net/kyori/adventure/chat/SignedMessageImpl.java @@ -0,0 +1,92 @@ +/* + * This file is part of adventure, licensed under the MIT License. + * + * Copyright (c) 2017-2022 KyoriPowered + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package net.kyori.adventure.chat; + +import java.security.SecureRandom; +import java.time.Instant; +import net.kyori.adventure.identity.Identity; +import net.kyori.adventure.text.Component; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +// Used for system messages ONLY +final class SignedMessageImpl implements SignedMessage { + static final SecureRandom RANDOM = new SecureRandom(); + + private final Instant instant; + private final long salt; + private final String message; + private final Component unsignedContent; + + SignedMessageImpl(final String message, final Component unsignedContent) { + this.instant = Instant.now(); + this.salt = RANDOM.nextLong(); + this.message = message; + this.unsignedContent = unsignedContent; + } + + @Override + public @NotNull Instant timestamp() { + return this.instant; + } + + @Override + public long salt() { + return this.salt; + } + + @Override + public Signature signature() { + return null; + } + + @Override + public @Nullable Component unsignedContent() { + return this.unsignedContent; + } + + @Override + public @NotNull String message() { + return this.message; + } + + @Override + public @NotNull Identity identity() { + return Identity.nil(); + } + + static final class SignatureImpl implements Signature { + + final byte[] signature; + + SignatureImpl(final byte[] signature) { + this.signature = signature; + } + + @Override + public byte[] bytes() { + return this.signature; + } + } +} diff --git a/api/src/main/java/net/kyori/adventure/chat/package-info.java b/api/src/main/java/net/kyori/adventure/chat/package-info.java new file mode 100644 index 000000000..af45343ca --- /dev/null +++ b/api/src/main/java/net/kyori/adventure/chat/package-info.java @@ -0,0 +1,27 @@ +/* + * This file is part of adventure, licensed under the MIT License. + * + * Copyright (c) 2017-2022 KyoriPowered + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +/** + * Chat-related data. + */ +package net.kyori.adventure.chat;