package org.bukkit.craftbukkit.util; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import net.minecraft.server.ChatClickable; import net.minecraft.server.ChatComponentText; import net.minecraft.server.ChatModifier; import net.minecraft.server.EnumChatFormat; import net.minecraft.server.ChatClickable.EnumClickAction; import net.minecraft.server.IChatBaseComponent; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap.Builder; import net.minecraft.server.ChatMessage; public final class CraftChatMessage { private static final Pattern LINK_PATTERN = Pattern.compile("((?:(?:https?):\\/\\/)?(?:[-\\w_\\.]{2,}\\.[a-z]{2,4}.*?(?=[\\.\\?!,;:]?(?:[" + String.valueOf(org.bukkit.ChatColor.COLOR_CHAR) + " \\n]|$))))"); private static class StringMessage { private static final Map<Character, EnumChatFormat> formatMap; private static final Pattern INCREMENTAL_PATTERN = Pattern.compile("(" + String.valueOf(org.bukkit.ChatColor.COLOR_CHAR) + "[0-9a-fk-or])|(\\n)|((?:(?:https?):\\/\\/)?(?:[-\\w_\\.]{2,}\\.[a-z]{2,4}.*?(?=[\\.\\?!,;:]?(?:[" + String.valueOf(org.bukkit.ChatColor.COLOR_CHAR) + " \\n]|$))))", Pattern.CASE_INSENSITIVE); static { Builder<Character, EnumChatFormat> builder = ImmutableMap.builder(); for (EnumChatFormat format : EnumChatFormat.values()) { builder.put(Character.toLowerCase(format.toString().charAt(1)), format); } formatMap = builder.build(); } private final List<IChatBaseComponent> list = new ArrayList<IChatBaseComponent>(); private IChatBaseComponent currentChatComponent = new ChatComponentText(""); private ChatModifier modifier = new ChatModifier(); private final IChatBaseComponent[] output; private int currentIndex; private final String message; private StringMessage(String message, boolean keepNewlines) { this.message = message; if (message == null) { output = new IChatBaseComponent[] { currentChatComponent }; return; } list.add(currentChatComponent); Matcher matcher = INCREMENTAL_PATTERN.matcher(message); String match = null; while (matcher.find()) { int groupId = 0; while ((match = matcher.group(++groupId)) == null) { // NOOP } appendNewComponent(matcher.start(groupId)); switch (groupId) { case 1: EnumChatFormat format = formatMap.get(match.toLowerCase(java.util.Locale.ENGLISH).charAt(1)); if (format == EnumChatFormat.RESET) { modifier = new ChatModifier(); } else if (format.isFormat()) { switch (format) { case BOLD: modifier.setBold(Boolean.TRUE); break; case ITALIC: modifier.setItalic(Boolean.TRUE); break; case STRIKETHROUGH: modifier.setStrikethrough(Boolean.TRUE); break; case UNDERLINE: modifier.setUnderline(Boolean.TRUE); break; case OBFUSCATED: modifier.setRandom(Boolean.TRUE); break; default: throw new AssertionError("Unexpected message format"); } } else { // Color resets formatting modifier = new ChatModifier().setColor(format); } break; case 2: if (keepNewlines) { currentChatComponent.addSibling(new ChatComponentText("\n")); } else { currentChatComponent = null; } break; case 3: if ( !( match.startsWith( "http://" ) || match.startsWith( "https://" ) ) ) { match = "http://" + match; } modifier.setChatClickable(new ChatClickable(EnumClickAction.OPEN_URL, match)); appendNewComponent(matcher.end(groupId)); modifier.setChatClickable((ChatClickable) null); } currentIndex = matcher.end(groupId); } if (currentIndex < message.length()) { appendNewComponent(message.length()); } output = list.toArray(new IChatBaseComponent[list.size()]); } private void appendNewComponent(int index) { if (index <= currentIndex) { return; } IChatBaseComponent addition = new ChatComponentText(message.substring(currentIndex, index)).setChatModifier(modifier); currentIndex = index; modifier = modifier.clone(); if (currentChatComponent == null) { currentChatComponent = new ChatComponentText(""); list.add(currentChatComponent); } currentChatComponent.addSibling(addition); } private IChatBaseComponent[] getOutput() { return output; } } public static IChatBaseComponent[] fromString(String message) { return fromString(message, false); } public static IChatBaseComponent[] fromString(String message, boolean keepNewlines) { return new StringMessage(message, keepNewlines).getOutput(); } public static String fromComponent(IChatBaseComponent component) { return fromComponent(component, EnumChatFormat.BLACK); } public static String fromComponent(IChatBaseComponent component, EnumChatFormat defaultColor) { if (component == null) return ""; StringBuilder out = new StringBuilder(); for (IChatBaseComponent c : component) { ChatModifier modi = c.getChatModifier(); out.append(modi.getColor() == null ? defaultColor : modi.getColor()); if (modi.isBold()) { out.append(EnumChatFormat.BOLD); } if (modi.isItalic()) { out.append(EnumChatFormat.ITALIC); } if (modi.isUnderlined()) { out.append(EnumChatFormat.UNDERLINE); } if (modi.isStrikethrough()) { out.append(EnumChatFormat.STRIKETHROUGH); } if (modi.isRandom()) { out.append(EnumChatFormat.OBFUSCATED); } out.append(c.getText()); } return out.toString().replaceFirst("^(" + defaultColor + ")*", ""); } public static IChatBaseComponent fixComponent(IChatBaseComponent component) { Matcher matcher = LINK_PATTERN.matcher(""); return fixComponent(component, matcher); } private static IChatBaseComponent fixComponent(IChatBaseComponent component, Matcher matcher) { if (component instanceof ChatComponentText) { ChatComponentText text = ((ChatComponentText) component); String msg = text.g(); if (matcher.reset(msg).find()) { matcher.reset(); ChatModifier modifier = text.getChatModifier() != null ? text.getChatModifier() : new ChatModifier(); List<IChatBaseComponent> extras = new ArrayList<IChatBaseComponent>(); List<IChatBaseComponent> extrasOld = new ArrayList<IChatBaseComponent>(text.a()); component = text = new ChatComponentText(""); int pos = 0; while (matcher.find()) { String match = matcher.group(); if ( !( match.startsWith( "http://" ) || match.startsWith( "https://" ) ) ) { match = "http://" + match; } ChatComponentText prev = new ChatComponentText(msg.substring(pos, matcher.start())); prev.setChatModifier(modifier); extras.add(prev); ChatComponentText link = new ChatComponentText(matcher.group()); ChatModifier linkModi = modifier.clone(); linkModi.setChatClickable(new ChatClickable(EnumClickAction.OPEN_URL, match)); link.setChatModifier(linkModi); extras.add(link); pos = matcher.end(); } ChatComponentText prev = new ChatComponentText(msg.substring(pos)); prev.setChatModifier(modifier); extras.add(prev); extras.addAll(extrasOld); for (IChatBaseComponent c : extras) { text.addSibling(c); } } } List extras = component.a(); for (int i = 0; i < extras.size(); i++) { IChatBaseComponent comp = (IChatBaseComponent) extras.get(i); if (comp.getChatModifier() != null && comp.getChatModifier().h() == null) { extras.set(i, fixComponent(comp, matcher)); } } if (component instanceof ChatMessage) { Object[] subs = ((ChatMessage) component).j(); for (int i = 0; i < subs.length; i++) { Object comp = subs[i]; if (comp instanceof IChatBaseComponent) { IChatBaseComponent c = (IChatBaseComponent) comp; if (c.getChatModifier() != null && c.getChatModifier().h() == null) { subs[i] = fixComponent(c, matcher); } } else if (comp instanceof String && matcher.reset((String)comp).find()) { subs[i] = fixComponent(new ChatComponentText((String) comp), matcher); } } } return component; } private CraftChatMessage() { } }