diff --git a/Mage.Client/src/main/java/mage/client/chat/ChatPanelBasic.java b/Mage.Client/src/main/java/mage/client/chat/ChatPanelBasic.java index 67dfd47f7d4..ce99d6e805a 100644 --- a/Mage.Client/src/main/java/mage/client/chat/ChatPanelBasic.java +++ b/Mage.Client/src/main/java/mage/client/chat/ChatPanelBasic.java @@ -97,6 +97,7 @@ public class ChatPanelBasic extends javax.swing.JPanel { */ public ChatPanelBasic() { initComponents(); + txtConversation.enableHyperlinksAndCardPopups(); setBackground(new Color(0, 0, 0, CHAT_ALPHA)); changeGUISize(GUISizeHelper.chatFont); if (jScrollPaneTxt != null) { diff --git a/Mage.Client/src/main/java/mage/client/remote/CallbackClientImpl.java b/Mage.Client/src/main/java/mage/client/remote/CallbackClientImpl.java index ed3bed84271..cc923107c8f 100644 --- a/Mage.Client/src/main/java/mage/client/remote/CallbackClientImpl.java +++ b/Mage.Client/src/main/java/mage/client/remote/CallbackClientImpl.java @@ -533,6 +533,7 @@ public class CallbackClientImpl implements CallbackClient { .append("
").append("/w username message - send private message to player (whisper)") .append("
").append("/pings - show players and watchers ping") .append("
").append("/fix - fix frozen game") + .append("
").append("[[card name]] - insert card with popup info") .toString(), null, null, MessageType.USER_INFO, ChatMessage.MessageColor.BLUE); break; diff --git a/Mage.Server/src/main/java/mage/server/ChatManagerImpl.java b/Mage.Server/src/main/java/mage/server/ChatManagerImpl.java index 4b9ef0d4b11..bf9adc9a42c 100644 --- a/Mage.Server/src/main/java/mage/server/ChatManagerImpl.java +++ b/Mage.Server/src/main/java/mage/server/ChatManagerImpl.java @@ -2,11 +2,13 @@ package mage.server; import mage.cards.repository.CardInfo; import mage.cards.repository.CardRepository; +import mage.constants.Constants; import mage.game.Game; import mage.server.exceptions.UserNotFoundException; import mage.server.game.GameController; import mage.server.managers.ChatManager; import mage.server.managers.ManagerFactory; +import mage.util.GameLog; import mage.utils.SystemUtil; import mage.view.ChatMessage.MessageColor; import mage.view.ChatMessage.MessageType; @@ -24,12 +26,12 @@ import java.util.regex.Pattern; import java.util.stream.Collectors; /** - * @author BetaSteward_at_googlemail.com + * @author BetaSteward_at_googlemail.com, JayDi85 */ public class ChatManagerImpl implements ChatManager { private static final Logger logger = Logger.getLogger(ChatManagerImpl.class); - private static final HashMap userMessages = new HashMap<>(); + private static final HashMap lastUserMessages = new HashMap<>(); // user->chat+message private final ManagerFactory managerFactory; private final ConcurrentHashMap chatSessions = new ConcurrentHashMap<>(); @@ -59,7 +61,7 @@ public class ChatManagerImpl implements ChatManager { @Override public void clearUserMessageStorage() { - userMessages.clear(); + lastUserMessages.clear(); } @Override @@ -91,14 +93,16 @@ public class ChatManagerImpl implements ChatManager { } } - final Pattern cardNamePattern = Pattern.compile("\\[(.*?)\\]"); + final Pattern cardNamePattern = Pattern.compile("\\[\\[(.*?)\\]\\]"); // uses scryfall bot style from github: [[name]] @Override public void broadcast(UUID chatId, String userName, String message, MessageColor color, boolean withTime, Game game, MessageType messageType, SoundToPlay soundToPlay) { + String finalMessage = message; ChatSession chatSession = chatSessions.get(chatId); + Optional user = managerFactory.userManager().getUserByName(userName); if (chatSession != null) { + // special commads if (message.startsWith("\\") || message.startsWith("/")) { - Optional user = managerFactory.userManager().getUserByName(userName); if (user.isPresent()) { if (!performUserCommand(user.get(), message, chatId, false)) { performUserCommand(user.get(), message, chatId, true); @@ -107,66 +111,70 @@ public class ChatManagerImpl implements ChatManager { } } + // enrich non-game messages with popup hints and other refs if (messageType != MessageType.GAME && !userName.isEmpty()) { - Optional u = managerFactory.userManager().getUserByName(userName); - if (u.isPresent()) { - - User user = u.get(); - String messageId = chatId.toString() + message; - if (messageId.equals(userMessages.get(userName))) { - // prevent identical messages - String informUser = "Your message appears to be identical to your last message"; - chatSessions.get(chatId).broadcastInfoToUser(user, informUser); - return; - } - - if (message.length() > 500) { - message = message.replaceFirst("^(.{500}).*", "$1 (rest of message truncated)"); - } - - String messageToCheck = message; - Matcher matchPattern = cardNamePattern.matcher(message); - while (matchPattern.find()) { - String cardName = matchPattern.group(1); - CardInfo cardInfo = CardRepository.instance.findPreferredCoreExpansionCard(cardName); - if (cardInfo != null) { - String colour = "silver"; - if (cardInfo.getCard().getColor(null).isMulticolored()) { - colour = "yellow"; - } else if (cardInfo.getCard().getColor(null).isWhite()) { - colour = "white"; - } else if (cardInfo.getCard().getColor(null).isBlue()) { - colour = "blue"; - } else if (cardInfo.getCard().getColor(null).isBlack()) { - colour = "black"; - } else if (cardInfo.getCard().getColor(null).isRed()) { - colour = "red"; - } else if (cardInfo.getCard().getColor(null).isGreen()) { - colour = "green"; - } - messageToCheck = messageToCheck.replaceFirst("\\[" + cardName + "\\]", "card"); - String displayCardName = ""; - message = message.replaceFirst("\\[" + cardName + "\\]", displayCardName); - } - } - - userMessages.put(userName, messageId); + if (user.isPresent()) { + // muted by admin if (messageType == MessageType.TALK) { - if (user.getChatLockedUntil() != null) { - if (user.getChatLockedUntil().compareTo(Calendar.getInstance().getTime()) > 0) { - chatSessions.get(chatId).broadcastInfoToUser(user, "Your chat is muted until " + SystemUtil.dateFormat.format(user.getChatLockedUntil())); + if (user.get().getChatLockedUntil() != null) { + if (user.get().getChatLockedUntil().compareTo(Calendar.getInstance().getTime()) > 0) { + // still muted + chatSessions.get(chatId).broadcastInfoToUser(user.get(), "Your chat is muted until " + + SystemUtil.dateFormat.format(user.get().getChatLockedUntil())); return; } else { - user.setChatLockedUntil(null); + // reset and allows + user.get().setChatLockedUntil(null); } } - } + // spam protection + String messageId = chatId.toString() + message; + if (messageId.equals(lastUserMessages.get(userName))) { + chatSessions.get(chatId).broadcastInfoToUser(user.get(), "Ignore duplicated message"); + return; + } + lastUserMessages.put(userName, messageId); + + // message limit + if (message.length() > Constants.MAX_CHAT_MESSAGE_SIZE) { + // too big messages must be ignored in parent code, so only system messages possible here + message = message.replaceFirst("^(.{" + Constants.MAX_CHAT_MESSAGE_SIZE + "}).*", "$1 (rest of message truncated)"); + } + + // insert card names with popup support + String messageToCheck = message; + Matcher matchPattern = cardNamePattern.matcher(message); + int foundCount = 0; + while (matchPattern.find()) { + // abuse protection + foundCount++; + if (foundCount > 5) { + break; + } + + // TODO: add search by lower case and partly text like github bot (maybe slow and abuseable, so test before implement) + String searchName = matchPattern.group(1); + CardInfo cardInfo = CardRepository.instance.findCard(searchName, true); + if (cardInfo != null) { + String newMessagePart = GameLog.getColoredObjectIdName( + cardInfo.getCard().getColor(), + UUID.randomUUID(), + cardInfo.getName(), + "", + cardInfo.getName() + ); + messageToCheck = messageToCheck.replaceFirst("\\[\\[" + searchName + "\\]\\]", "[[" + newMessagePart + "]]"); + } + } + finalMessage = messageToCheck; } } - chatSession.broadcast(userName, message, color, withTime, game, messageType, soundToPlay); + + // all other messages + chatSession.broadcast(userName, finalMessage, color, withTime, game, messageType, soundToPlay); } }