/** * Copyright (c) 2005-2017, KoLmafia development team * http://kolmafia.sourceforge.net/ * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * [1] Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * [2] Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * [3] Neither the name "KoLmafia" nor the names of its contributors may * be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ package net.sourceforge.kolmafia.chat; import java.awt.Color; import java.util.HashMap; import java.util.regex.Pattern; import net.sourceforge.kolmafia.KoLCharacter; import net.sourceforge.kolmafia.KoLConstants; import net.sourceforge.kolmafia.preferences.Preferences; import net.sourceforge.kolmafia.session.ContactManager; import net.sourceforge.kolmafia.utilities.InputFieldUtilities; import net.sourceforge.kolmafia.utilities.StringUtilities; public class ChatFormatter { // KoL itself inserts "chat effect" images. One of these results from the Pirate Bellow skill. // If we want to excise images but keep that one, the following pattern will do. private static final Pattern IMAGE_PATTERN = Pattern.compile( "<img.*?/(.*?)(?<!12x12skull)\\.gif.*?>" ); private static final Pattern LASTSEEN_PATTERN = Pattern.compile( "<!--lastseen:.*?-->" ); private static final Pattern EXPAND_PATTERN = Pattern.compile( "</?p>" ); private static final Pattern COLOR_PATTERN = Pattern.compile( "</?font.*?>" ); private static final Pattern LINEBREAK_PATTERN = Pattern.compile( "</?br>", Pattern.CASE_INSENSITIVE ); private static final Pattern TABLE_PATTERN = Pattern.compile( "<table>.*?</table>" ); private static final Pattern EMPTY_SPAN_PATTERN = Pattern.compile( "<span[^>]*></span>" ); private static final Pattern GREEN_PATTERN = Pattern.compile( "<font color=green><b>(.*?)</font></a></b> (.*?)</font>" ); private static final Pattern LINE_COLOR_PATTERN = Pattern.compile( "^<font color=(?:red|green)>(.*?)</font>$" ); private static final Pattern NESTED_LINKS_PATTERN = Pattern.compile( "<a target=mainpane href=\"([^<]*?)\"><font color=green>(.*?) <a[^>]+><font color=green>([^<]*?)</font></a>.</font></a>" ); private static final Pattern CHAT_LINKS_PATTERN = Pattern.compile( "<a target=_blank href=\"([^<]*?)\">(.*?)</a>" ); private static final Pattern WHOIS_PATTERN = Pattern.compile( "(<a [^>]*?>)<b><font color=green>([^>]*? \\(#\\d+\\))</b></a>([^<]*?)<br>" ); private static final Pattern MULTILINE_PATTERN = Pattern.compile( "\n+" ); private static final String DEFAULT_TIMESTAMP_COLOR = "#7695B4"; private static final HashMap chatColors = new HashMap(); public static final String formatInternalMessage( final String originalContent ) { // This is called once for all of the lines that arrive in a single response String normalizedContent = ChatFormatter.getNormalizedMessage( originalContent.trim() ); // KoL inserts HTML comments for special "chat effects". Keep them. // But, we don't want the lastseen comment normalizedContent = ChatFormatter.LASTSEEN_PATTERN.matcher( normalizedContent ).replaceAll( "" ); // noTableContent normalizedContent = ChatFormatter.TABLE_PATTERN.matcher( normalizedContent ).replaceAll( "" ); return normalizedContent; } public static final String removeMessageColors( final String originalContent ) { // This is called for each message from a response. String normalizedContent = originalContent; // noColorContent normalizedContent = ChatFormatter.COLOR_PATTERN.matcher( normalizedContent ).replaceAll( "" ); return normalizedContent; } public static final String removeLineColor( final String originalContent ) { // This is called for each message from a response. String normalizedContent = originalContent; // noLineColorContent normalizedContent = ChatFormatter.LINE_COLOR_PATTERN.matcher( normalizedContent ).replaceAll( "$1" ); return normalizedContent; } public static final String formatExternalMessage( final String originalContent ) { String normalizedContent = ChatFormatter.getNormalizedMessage( originalContent.trim() ); // normalPrivateContent normalizedContent = StringUtilities.globalStringReplace( normalizedContent, "<font color=blue>private to ", "<font color=blue>private to</font></b> <b>" ); normalizedContent = StringUtilities.globalStringReplace( normalizedContent, "(private)</a></b>", "(private)</b></font></a><font color=blue>" ); return normalizedContent; } private static final String getNormalizedMessage( final String originalContent ) { if ( originalContent == null || originalContent.length() == 0 ) { return ""; } String normalizedContent = originalContent; // KoL inserts images for special "chat effects". Let the user see them. // noImageContent // normalizedContent = ChatFormatter.IMAGE_PATTERN.matcher( normalizedContent ).replaceAll( "" ); // normalBreaksContent normalizedContent = ChatFormatter.LINEBREAK_PATTERN.matcher( normalizedContent ).replaceAll( "<br>" ); // condensedContent normalizedContent = ChatFormatter.EXPAND_PATTERN.matcher( normalizedContent ).replaceAll( "<br>" ); // normalBoldsContent normalizedContent = StringUtilities.globalStringReplace( normalizedContent , "<br></b>", "</b><br>" ); // normalFontsContent normalizedContent = StringUtilities.globalStringReplace( normalizedContent , "<br></font>", "</font><br>" ); // normalSpanContent normalizedContent = StringUtilities.globalStringReplace( normalizedContent , "<br></span>", "</span><br>" ); normalizedContent = ChatFormatter.EMPTY_SPAN_PATTERN.matcher( normalizedContent ).replaceAll( "" ); // colonOrderedContent normalizedContent = StringUtilities.globalStringReplace( normalizedContent, ":</b></a>", "</a></b>:" ); normalizedContent = StringUtilities.globalStringReplace( normalizedContent, "</a>:</b>", "</a></b>:" ); normalizedContent = StringUtilities.globalStringReplace( normalizedContent, "</b></a>:", "</a></b>:" ); // italicOrderedContent normalizedContent = StringUtilities.globalStringReplace( normalizedContent, "<b><i>", "<i><b>" ); normalizedContent = StringUtilities.globalStringReplace( normalizedContent, "</b></font></a>", "</font></a></b>" ); // fixedGreenContent normalizedContent = ChatFormatter.GREEN_PATTERN.matcher( normalizedContent ).replaceAll( "<font color=green><b>$1</b></font></a> $2</font>" ); normalizedContent = ChatFormatter.NESTED_LINKS_PATTERN.matcher( normalizedContent ).replaceAll( "<a target=mainpane href=\"$1\"><font color=green>$2 $3</font></a>" ); normalizedContent = ChatFormatter.WHOIS_PATTERN.matcher( normalizedContent ).replaceAll( "$1<b><font color=green>$2</font></b></a><font color=green>$3</font><br>" ); // leftAlignContent normalizedContent = StringUtilities.globalStringDelete( normalizedContent, "<center>" ); normalizedContent = StringUtilities.globalStringReplace( normalizedContent, "</center>", "<br>" ); return normalizedContent; } public static final String formatChatMessage( final ChatMessage message ) { return ChatFormatter.formatChatMessage( message, true ); } public static final String formatChatMessage( final ChatMessage message, boolean includeTimestamp ) { StringBuilder displayHTML = new StringBuilder(); if ( includeTimestamp ) { displayHTML.append( "<font color=\"" ); displayHTML.append( ChatFormatter.DEFAULT_TIMESTAMP_COLOR ); displayHTML.append( "\">" ); displayHTML.append( message.getTimestamp() ); displayHTML.append( "</font>" ); displayHTML.append( " " ); } String sender = message.getSender(); if ( message instanceof SystemMessage ) { displayHTML.append( "<b><font color=\"red\">" ); displayHTML.append( "<a href=\"showplayer.php?who=-1\"><b>System Message</b></a>" ); displayHTML.append( ": " ); displayHTML.append( message.getContent() ); displayHTML.append( "</font></b>" ); } else if ( sender == null ) { String messageColor = null; if ( message instanceof EventMessage ) { messageColor = ( (EventMessage) message ).getColor(); } if ( messageColor != null ) { displayHTML.append( "<font color=" ); displayHTML.append( messageColor ); displayHTML.append( ">" ); } displayHTML.append( message.getContent() ); if ( messageColor != null ) { displayHTML.append( "</font>" ); } } else if ( message instanceof ModeratorMessage ) { ModeratorMessage modMessage = (ModeratorMessage) message; String open, close; if ( sender.equals( "Mod Announcement" ) ) { open = "<font color=green>"; close = "</font>"; } else { open = "<b><font color=red>"; close = "</font></b>"; } displayHTML.append( open ); displayHTML.append( "<a href=\"showplayer.php?who=" ); displayHTML.append( modMessage.getModeratorId() ); displayHTML.append( "\">" ); displayHTML.append("<b>" ); displayHTML.append( sender ); displayHTML.append( "</b>" ); displayHTML.append( "</a>" ); displayHTML.append( ": " ); displayHTML.append( message.getContent() ); displayHTML.append( close ); } else { if ( message.isAction() ) { displayHTML.append( "<i>" ); } displayHTML.append( "<a href=\"showplayer.php?who=" ); displayHTML.append( ContactManager.getPlayerId( sender ) ); displayHTML.append( "\"><b>" ); displayHTML.append( "<font color=\"" ); displayHTML.append( ChatFormatter.getChatColor( sender ) ); displayHTML.append( "\">" ); displayHTML.append( sender ); displayHTML.append( "</font>" ); displayHTML.append( "</b></a>" ); if ( !message.isAction() ) { displayHTML.append( ":" ); } displayHTML.append( " " ); String chatLinkContent = ChatFormatter.CHAT_LINKS_PATTERN.matcher( message.getContent() ).replaceAll( "<a target=_blank href=\"$1\"><font color=\"blue\">$2</font></a>" ); displayHTML.append( chatLinkContent ); if ( message.isAction() ) { displayHTML.append( "</i>" ); } } displayHTML.append( "<br>" ); return displayHTML.toString(); } public static final String getChatColor( final String sender ) { if ( ChatFormatter.chatColors.containsKey( sender ) ) { return (String) ChatFormatter.chatColors.get( sender ); } if ( sender.startsWith( "/" ) ) { return "green"; } if ( sender.equalsIgnoreCase( KoLCharacter.getUserName() ) && ChatFormatter.chatColors.containsKey( "chatcolorself" ) ) { return (String) ChatFormatter.chatColors.get( "chatcolorself" ); } if ( ContactManager.isMailContact( sender.toLowerCase() ) && ChatFormatter.chatColors.containsKey( "chatcolorcontacts" ) ) { return (String) ChatFormatter.chatColors.get( "chatcolorcontacts" ); } if ( ChatFormatter.chatColors.containsKey( "chatcolorothers" ) ) { return (String) ChatFormatter.chatColors.get( "chatcolorothers" ); } return "black"; } public static void setChatColor( final String sender, final String color ) { ChatFormatter.chatColors.put( sender, color ); } public static final Color getRandomColor() { int[] colors = new int[ 3 ]; do { for ( int i = 0; i < 3; ++i ) { colors[ i ] = 40 + KoLConstants.RNG.nextInt( 160 ); } } while ( colors[ 0 ] > 128 && colors[ 1 ] > 128 && colors[ 2 ] > 128 ); return new Color( colors[ 0 ], colors[ 1 ], colors[ 2 ] ); } /** * Utility method to add a highlight word to the list of words currently being handled by the highlighter. This * method will prompt the user for the word or phrase which is to be highlighted, followed by a prompt for the color * which they would like to use. Cancellation during any point of this process results in no chat highlighting being * added. */ public static final void addHighlighting() { String highlight = InputFieldUtilities.input( "What word/phrase would you like to highlight?", KoLCharacter.getUserName() ); if ( highlight == null || highlight.length() == 0 ) { return; } Color color = ChatFormatter.getRandomColor(); StringBuffer newSetting = new StringBuffer(); newSetting.append( Preferences.getString( "highlightList" ) ); newSetting.append( "\n" ); newSetting.append( StyledChatBuffer.addHighlight( highlight, color ) ); Preferences.setString( "highlightList", newSetting.toString().trim() ); ChatManager.applyHighlights(); } /** * Utility method to remove a word or phrase from being highlighted. The user will be prompted with the highlights * which are currently active, and the user can select which one they would like to remove. Note that only one * highlight at a time can be removed with this method. */ public static final void removeHighlighting() { Object[] patterns = StyledChatBuffer.searchStrings.toArray(); if ( patterns.length == 0 ) { InputFieldUtilities.alert( "No active highlights." ); return; } String selectedValue = (String) InputFieldUtilities.input( "Currently highlighting the following terms:", patterns ); if ( selectedValue == null ) { return; } for ( int i = 0; i < patterns.length; ++i ) { if ( patterns[ i ].equals( selectedValue ) ) { String settingString = StyledChatBuffer.removeHighlight( i ); String oldSetting = Preferences.getString( "highlightList" ); int startIndex = oldSetting.indexOf( settingString ); int endIndex = startIndex + settingString.length(); StringBuffer newSetting = new StringBuffer(); if ( startIndex != -1 ) { newSetting.append( oldSetting.substring( 0, startIndex ) ); if ( endIndex < oldSetting.length() ) { newSetting.append( oldSetting.substring( endIndex ) ); } } String cleanString = newSetting.toString(); cleanString = ChatFormatter.MULTILINE_PATTERN.matcher( cleanString ).replaceAll( "\n" ); cleanString = cleanString.trim(); Preferences.setString( "highlightList", cleanString ); } } } }