Skip to content

Commit

Permalink
fix(api): add UserChatEvent #isCancellable and #isModifiable (#275)…
Browse files Browse the repository at this point in the history
… [skip ci]

Due to changes in Minecraft 1.19.1+, some platforms no longer support
cancelling or modifying the chat event.

Add `UserChatEvent#isCancellable()` and `UserChatEvent#isModifiable()`
to allow developers to determine whether these operations are supported
on the current platform.

If `UserChatEvent#isCancellable()` returns false, but the event is
cancelled, the event will not be cancelled on the platform.

If `UserChatEvent#isModifiable()` returns false, but the chat message is
modified, the event's chat message will not be changed on the platform.
  • Loading branch information
joshuasing authored Sep 6, 2023
1 parent fb165f9 commit d66e527
Show file tree
Hide file tree
Showing 7 changed files with 136 additions and 42 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -34,48 +34,87 @@ public final class UserChatEvent extends AbstractCancellable implements UserEven

private final @NotNull User user;
private @NotNull String message;
private final boolean cancellable;
private final boolean modifiable;

/**
* User chat event constructor.
*
* @param user User that sent the message.
* @param message Message that the user attempted to send.
* @param cancelled Whether this event is cancelled.
* @param user User that sent the message.
* @param message Message that the user attempted to send.
* @param cancelled Whether this event is cancelled.
* @param cancellable Whether this event can be cancelled on this platform.
* @param modifiable Whether this event can be modified on this platform.
*/
public UserChatEvent(@NotNull User user, @NotNull String message, boolean cancelled) {
public UserChatEvent(@NotNull User user, @NotNull String message, boolean cancelled, boolean cancellable, boolean modifiable) {
super(cancelled);
this.user = user;
this.message = message;
this.cancellable = cancellable;
this.modifiable = modifiable;
}

/**
* Get the user who sent this message.
* Returns the user who sent this message.
*
* @return the user who sent this message.
* @return message sender.
*/
@Override
public @NotNull User getUser() {
return this.user;
}

/**
* Get the message that was sent.
* Returns the message that was sent.
*
* @return the message.
* @return message.
*/
public @NotNull String getMessage() {
return this.message;
}

/**
* Sets the message that was sent.
* <p>Due to recent Minecraft changes and the addition of signed message, this may not work
* properly on all platforms and versions.</p>
* Changes the chat message.
* <p>Due to changes in Minecraft 1.19.1+, some platforms no longer support modifying the chat
* message. If the current platform does not support changing the chat message, no changes will
* be made.</p>
*
* @param message New message that will be sent.
* @param message New message.
*/
public void setMessage(@NotNull String message) {
this.message = message;
}

/**
* Returns whether this event can be cancelled.
* <p>Due to changes in Minecraft 1.19.1+, some platforms no longer support cancelling signed
* chat packets. If the current platform does not support cancelling this event, this will
* return {@code false}.</p>
*
* <p>If this method returns {@code false}, but the event is cancelled, the event will not be
* cancelled on the platform to prevent problems from occurring.</p>
*
* @return {@code true} if this chat event can be cancelled on the underlying platform,
* otherwise {@code false}.
*/
public boolean isCancellable() {
return this.cancellable;
}

/**
* Returns whether this event can be modified.
* <p>Due to changes in Minecraft 1.19.1+, some platforms no longer support modifying the chat
* message. If the current platform does not support changing the chat message for this event,
* this will return {@code false}.</p>
*
* <p>If this method returns {@code false}, but the chat message is modified, the event's chat
* message will not be changed on the platform to prevent problems from occurring.</p>
*
* @return {@code true} if this chat event can be modified on the underlying platform, otherwise
* {@code false}.
*/
public boolean isModifiable() {
return this.modifiable;
}

}
58 changes: 58 additions & 0 deletions api/src/main/java/dev/hypera/chameleon/util/PlatformEventUtil.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* This file is a part of the Chameleon Framework, licensed under the MIT License.
*
* Copyright (c) 2021-2023 The Chameleon Framework Authors.
*
* 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 dev.hypera.chameleon.util;

import dev.hypera.chameleon.logger.ChameleonLogger;
import org.jetbrains.annotations.ApiStatus.Internal;
import org.jetbrains.annotations.NotNull;

/**
* Platform event utilities.
*/
@Internal
public final class PlatformEventUtil {

private PlatformEventUtil() {
throw new UnsupportedOperationException("PlatformEventUtil is a utility class and cannot be instantiated");
}

/**
* Logs a failure to modify a UserChatEvent message.
*
* @param logger Logger.
*/
public static void logChatModificationFailure(@NotNull ChameleonLogger logger) {
logger.debug("Failed to modify UserChatEvent message: modification may not supported on the current platform");
}

/**
* Logs a failure to cancel a UserChatEvent message.
*
* @param logger Logger.
*/
public static void logChatCancellationFailure(@NotNull ChameleonLogger logger) {
logger.debug("Failed to cancel UserChatEvent: cancellation may not supported on the current platform");
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,8 @@ public void onPlayerJoinEvent(@NotNull PlayerJoinEvent event) {
public void onAsyncPlayerChatEvent(@NotNull AsyncPlayerChatEvent event) {
UserChatEvent chameleonEvent = new UserChatEvent(
this.userManager.wrap(event.getPlayer()),
event.getMessage(), event.isCancelled()
event.getMessage(), event.isCancelled(),
true, true
);
this.chameleon.getEventBus().dispatch(chameleonEvent);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,8 @@ public void onPostLoginEvent(@NotNull PostLoginEvent event) {
public void onChatEvent(@NotNull ChatEvent event) {
UserChatEvent chameleonEvent = new UserChatEvent(
wrap((ProxiedPlayer) event.getSender()),
event.getMessage(),
event.isCancelled()
event.getMessage(), event.isCancelled(),
true, true
);
this.chameleon.getEventBus().dispatch(chameleonEvent);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,8 @@ public void onPlayerJoinEvent(@NotNull PlayerJoinEvent event) {
public void onPlayerChatEvent(@NotNull PlayerChatEvent event) {
UserChatEvent chameleonEvent = new UserChatEvent(
this.chameleon.getUserManager().wrap(event.getPlayer()),
event.getMessage(), event.isCancelled()
event.getMessage(), event.isCancelled(),
true, true
);
this.chameleon.getEventBus().dispatch(chameleonEvent);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,10 @@ public void onChatEvent(@NotNull PlayerChatEvent event) {
}

UserChatEvent chameleonEvent = new UserChatEvent(
this.chameleon.getUserManager().wrap(sender), serialized, event.isCancelled());
this.chameleon.getUserManager().wrap(sender),
serialized, event.isCancelled(),
true, true
);
this.chameleon.getEventBus().dispatch(chameleonEvent);

if (!serialized.equals(chameleonEvent.getMessage())) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@
import com.velocitypowered.api.event.player.PlayerChatEvent.ChatResult;
import com.velocitypowered.api.event.player.ServerConnectedEvent;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.server.RegisteredServer;
import dev.hypera.chameleon.event.common.UserChatEvent;
import dev.hypera.chameleon.event.common.UserConnectEvent;
Expand All @@ -40,6 +39,7 @@
import dev.hypera.chameleon.platform.velocity.VelocityChameleon;
import dev.hypera.chameleon.platform.velocity.platform.objects.VelocityServer;
import dev.hypera.chameleon.user.User;
import dev.hypera.chameleon.util.PlatformEventUtil;
import org.jetbrains.annotations.ApiStatus.Internal;
import org.jetbrains.annotations.NotNull;

Expand Down Expand Up @@ -84,19 +84,31 @@ public void onPostLoginEvent(@NotNull PostLoginEvent event) {
*/
@Subscribe
public void onChatEvent(@NotNull PlayerChatEvent event) {
boolean immutable = event.getPlayer().getProtocolVersion()
.compareTo(ProtocolVersion.MINECRAFT_1_19_1) >= 0;
UserChatEvent chameleonEvent = new UserChatEvent(
this.chameleon.getUserManager().wrap(event.getPlayer()),
event.getMessage(),
!event.getResult().isAllowed()
!event.getResult().isAllowed(),
immutable, immutable
);
this.chameleon.getEventBus().dispatch(chameleonEvent);

if (!event.getMessage().equals(chameleonEvent.getMessage()) &&
catchChatModification(event.getPlayer(), false)) {
// Event message modification
if (!event.getMessage().equals(chameleonEvent.getMessage())) {
if (immutable) {
PlatformEventUtil.logChatModificationFailure(this.chameleon.getInternalLogger());
return;
}
event.setResult(ChatResult.message(chameleonEvent.getMessage()));
}

if (chameleonEvent.isCancelled() && catchChatModification(event.getPlayer(), true)) {
// Event cancellation
if (chameleonEvent.isCancelled() && event.getResult().isAllowed()) {
if (immutable) {
PlatformEventUtil.logChatCancellationFailure(this.chameleon.getInternalLogger());
return;
}
event.setResult(ChatResult.denied());
}
}
Expand Down Expand Up @@ -126,26 +138,6 @@ public void onServerSwitchEvent(@NotNull ServerConnectedEvent event) {
));
}

private boolean catchChatModification(@NotNull Player player, boolean cancel) {
if (player.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_19_1) >= 0) {
this.chameleon.getInternalLogger().error(
"Failed to %s a chat message for a player using 1.19.1 or above, doing so "
+ "may result in Velocity throwing an exception and the sender being disconnected.",
cancel ? "cancel" : "modify"
);
this.chameleon.getInternalLogger().error(
"This IS NOT a bug, but rather an intentional change in Velocity caused by"
+ "changes in Minecraft 1.19.1."
);
this.chameleon.getInternalLogger().error(
"See https://github.com/PaperMC/Velocity/issues/804 for more information."
);
return false;
} else {
return true;
}
}

private @NotNull Server wrap(@NotNull RegisteredServer server) {
return new VelocityServer(this.chameleon, server);
}
Expand Down

0 comments on commit d66e527

Please sign in to comment.