org.lanternpowered.server.network.vanilla.message.handler.play.HandlerPlayInChatMessage.java Source code

Java tutorial

Introduction

Here is the source code for org.lanternpowered.server.network.vanilla.message.handler.play.HandlerPlayInChatMessage.java

Source

/*
 * This file is part of LanternServer, licensed under the MIT License (MIT).
 *
 * Copyright (c) LanternPowered <https://www.lanternpowered.org>
 * Copyright (c) SpongePowered <https://www.spongepowered.org>
 * Copyright (c) contributors
 *
 * 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 org.lanternpowered.server.network.vanilla.message.handler.play;

import static org.lanternpowered.server.text.translation.TranslationHelper.t;
import static org.spongepowered.api.command.CommandMessageFormatting.error;

import com.google.common.collect.ImmutableMap;
import io.netty.util.Attribute;
import io.netty.util.AttributeKey;
import org.apache.commons.lang3.StringUtils;
import org.lanternpowered.server.config.GlobalConfig;
import org.lanternpowered.server.entity.living.player.LanternPlayer;
import org.lanternpowered.server.game.Lantern;
import org.lanternpowered.server.game.LanternGame;
import org.lanternpowered.server.network.NetworkContext;
import org.lanternpowered.server.network.NetworkSession;
import org.lanternpowered.server.network.message.handler.Handler;
import org.lanternpowered.server.network.vanilla.message.type.play.MessagePlayInChatMessage;
import org.lanternpowered.server.permission.Permissions;
import org.lanternpowered.server.text.TextConstants;
import org.lanternpowered.server.text.action.LanternClickActionCallbacks;
import org.spongepowered.api.Sponge;
import org.spongepowered.api.command.CommandSource;
import org.spongepowered.api.data.key.Keys;
import org.spongepowered.api.event.SpongeEventFactory;
import org.spongepowered.api.event.cause.Cause;
import org.spongepowered.api.event.cause.NamedCause;
import org.spongepowered.api.event.message.MessageChannelEvent;
import org.spongepowered.api.event.message.MessageEvent;
import org.spongepowered.api.text.Text;
import org.spongepowered.api.text.TextTemplate;
import org.spongepowered.api.text.action.TextActions;
import org.spongepowered.api.text.channel.MessageChannel;
import org.spongepowered.api.text.chat.ChatTypes;

import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.Optional;
import java.util.UUID;
import java.util.function.Consumer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public final class HandlerPlayInChatMessage implements Handler<MessagePlayInChatMessage> {

    public static final String URL_ARGUMENT = "url";
    private final static AttributeKey<ChatData> CHAT_DATA = AttributeKey.valueOf("chat-data");

    private static class ChatData {

        private int chatThrottle;
        private long lastChatTime = -1L;
    }

    @Override
    public void handle(NetworkContext context, MessagePlayInChatMessage message) {
        final NetworkSession session = context.getSession();
        final LanternPlayer player = session.getPlayer();
        player.resetIdleTimeoutCounter();
        final String message0 = message.getMessage();

        // Check for a valid click action callback
        final Matcher matcher = LanternClickActionCallbacks.COMMAND_PATTERN.matcher(message0);
        if (matcher.matches()) {
            final UUID uniqueId = UUID.fromString(matcher.group(1));
            final Optional<Consumer<CommandSource>> callback = LanternClickActionCallbacks.get()
                    .getCallbackForUUID(uniqueId);
            if (callback.isPresent()) {
                callback.get().accept(player);
            } else {
                player.sendMessage(
                        error(t("The callback you provided was not valid. Keep in mind that callbacks will expire "
                                + "after 10 minutes, so you might want to consider clicking faster next time!")));
            }
            return;
        }

        String message1 = StringUtils.normalizeSpace(message0);
        if (!isAllowedString(message0)) {
            session.disconnect(t("multiplayer.disconnect.illegal_characters"));
            return;
        }
        if (message1.startsWith("/")) {
            Lantern.getSyncExecutorService()
                    .submit(() -> Sponge.getCommandManager().process(player, message1.substring(1)));
        } else {
            final Text nameText = player.get(Keys.DISPLAY_NAME).get();
            final Text rawMessageText = Text.of(message0);
            final GlobalConfig.Chat.Urls urls = Lantern.getGame().getGlobalConfig().getChat().getUrls();
            final Text messageText;
            if (urls.isEnabled() && player.hasPermission(Permissions.Chat.FORMAT_URLS)) {
                messageText = newTextWithLinks(message0, urls.getTemplate(), false);
            } else {
                messageText = rawMessageText;
            }
            final MessageChannel channel = player.getMessageChannel();
            final MessageChannelEvent.Chat event = SpongeEventFactory.createMessageChannelEventChat(
                    Cause.of(NamedCause.source(player)), channel, Optional.of(channel),
                    new MessageEvent.MessageFormatter(nameText, messageText), rawMessageText, false);
            if (!Sponge.getEventManager().post(event) && !event.isMessageCancelled()) {
                event.getChannel().ifPresent(c -> c.send(player, event.getMessage(), ChatTypes.CHAT));
            }
        }
        final Attribute<ChatData> attr = context.getChannel().attr(CHAT_DATA);
        ChatData chatData = attr.get();
        if (chatData == null) {
            chatData = new ChatData();
            final ChatData chatData1 = attr.setIfAbsent(chatData);
            if (chatData1 != null) {
                chatData = chatData1;
            }
        }
        //noinspection SynchronizationOnLocalVariableOrMethodParameter
        synchronized (chatData) {
            final long currentTime = LanternGame.currentTimeTicks();
            if (chatData.lastChatTime != -1L) {
                chatData.chatThrottle = (int) Math.max(0,
                        chatData.chatThrottle - (currentTime - chatData.lastChatTime));
            }
            chatData.lastChatTime = currentTime;
            chatData.chatThrottle += 20;
            if (chatData.chatThrottle > Lantern.getGame().getGlobalConfig().getChatSpamThreshold()) {
                session.disconnect(t("disconnect.spam"));
            }
        }
    }

    private static final Pattern URL_PATTERN = Pattern.compile(
            "((?:[a-z0-9]{2,}://)?(?:(?:[0-9]{1,3}\\.){3}[0-9]{1,3}|(?:[-\\w_]+\\.[a-z]{2,}?))(?::[0-9]{1,5})?.*?(?=[!\"\u00A7 \n]|$))",
            Pattern.CASE_INSENSITIVE);

    private static Text newTextWithLinks(String message, TextTemplate template, boolean allowMissingHeader) {
        Text.Builder builder = null;
        StringBuilder lastMessage = null;

        Matcher matcher = URL_PATTERN.matcher(message);
        int lastEnd = 0;
        while (matcher.find()) {
            int start = matcher.start();
            int end = matcher.end();

            String part = message.substring(lastEnd, start);
            if (part.length() > 0) {
                if (lastMessage != null) {
                    lastMessage.append(part);
                } else {
                    lastMessage = new StringBuilder(part);
                }
            }

            lastEnd = end;
            String url = message.substring(start, end);
            URL urlInstance = null;

            try {
                URI uri = new URI(url);
                if (uri.getScheme() == null) {
                    if (!allowMissingHeader) {
                        uri = null;
                    } else {
                        uri = new URI("http://" + url);
                    }
                }
                if (uri != null) {
                    urlInstance = uri.toURL();
                }
            } catch (URISyntaxException | MalformedURLException ignored) {
            }

            if (urlInstance == null) {
                if (lastMessage != null) {
                    lastMessage.append(url);
                } else {
                    lastMessage = new StringBuilder(url);
                }
            } else {
                if (builder == null) {
                    builder = Text.builder();
                }
                if (lastMessage != null) {
                    builder.append(Text.of(lastMessage.toString()));
                    lastMessage = null;
                }

                builder.append(template.apply(ImmutableMap.of(URL_ARGUMENT, url))
                        .onClick(TextActions.openUrl(urlInstance)).build());
            }
        }

        if (builder == null) {
            return Text.of(message);
        } else {
            if (lastMessage != null) {
                builder.append(Text.of(lastMessage.toString()));
            }
            String end = message.substring(lastEnd);
            if (end.length() > 0) {
                builder.append(Text.of(message.substring(lastEnd)));
            }
            return builder.build();
        }
    }

    private static boolean isAllowedString(String string) {
        for (char character : string.toCharArray()) {
            if (!isAllowedCharacter(character)) {
                return false;
            }
        }
        return true;
    }

    private static boolean isAllowedCharacter(char character) {
        return character != TextConstants.LEGACY_CHAR && character >= ' ' && character != '\u007F';
    }
}