/** * 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.Toolkit; import java.io.File; import java.util.ArrayList; import java.util.Date; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map.Entry; import java.util.Set; import java.util.TreeMap; import java.util.regex.Matcher; import java.util.regex.Pattern; import net.java.dev.spellcast.utilities.LockableListModel; import net.sourceforge.kolmafia.AdventureResult; import net.sourceforge.kolmafia.BuffBotHome; import net.sourceforge.kolmafia.CreateFrameRunnable; import net.sourceforge.kolmafia.EdServantData; import net.sourceforge.kolmafia.KoLCharacter; import net.sourceforge.kolmafia.KoLConstants; import net.sourceforge.kolmafia.KoLmafia; import net.sourceforge.kolmafia.KoLmafiaASH; import net.sourceforge.kolmafia.KoLmafiaCLI; import net.sourceforge.kolmafia.RequestThread; import net.sourceforge.kolmafia.StaticEntity; import net.sourceforge.kolmafia.moods.RecoveryManager; import net.sourceforge.kolmafia.preferences.Preferences; import net.sourceforge.kolmafia.request.AltarOfLiteracyRequest; import net.sourceforge.kolmafia.request.ApiRequest; import net.sourceforge.kolmafia.request.ChannelColorsRequest; import net.sourceforge.kolmafia.request.LoginRequest; import net.sourceforge.kolmafia.request.SendMailRequest; import net.sourceforge.kolmafia.request.UneffectRequest; import net.sourceforge.kolmafia.session.ClanManager; import net.sourceforge.kolmafia.session.EventManager; import net.sourceforge.kolmafia.swingui.ChatFrame; import net.sourceforge.kolmafia.swingui.ContactListFrame; import net.sourceforge.kolmafia.swingui.GenericFrame; import net.sourceforge.kolmafia.swingui.TabbedChatFrame; import net.sourceforge.kolmafia.textui.Interpreter; import net.sourceforge.kolmafia.utilities.RollingLinkedList; import net.sourceforge.kolmafia.utilities.StringUtilities; public abstract class ChatManager { private static final LinkedList<Object> clanMessages = new RollingLinkedList( 20 ); private static final Set<String> validChatReplyRecipients = new HashSet<String>(); private static final TreeMap<String, StyledChatBuffer> instantMessageBuffers = new TreeMap<String, StyledChatBuffer>(); private static Entry<String,StyledChatBuffer>[] bufferEntries = new Entry[ 0 ]; private static boolean isRunning = false; private static boolean checkedLiteracy = false; private static boolean chatLiterate = false; private static String currentChannel = null; private static List<String> activeWindows = new ArrayList<String>(); public static List<String> activeChannels = new ArrayList<String>(); private static TabbedChatFrame tabbedFrame = null; private static boolean triviaGameActive = false; private static int triviaGameIndex = 0; private static String triviaGameId = "[trivia0]"; private static LockableListModel<String> triviaGameContacts = new LockableListModel<String>(); private static ContactListFrame triviaGameContactListFrame = null; private static String faxbot = null; private static ChatMessage faxbotMessage = null; public static final void reset() { ChatManager.dispose(); ChatPoller.reset(); ChatManager.clanMessages.clear(); ChatManager.instantMessageBuffers.clear(); ChatManager.bufferEntries = new Entry[0]; ChatManager.activeChannels.clear(); ChatManager.currentChannel = null; ChatManager.triviaGameActive = false; ChatManager.triviaGameIndex = 0; ChatManager.triviaGameId = "[trivia0]"; ChatManager.triviaGameContacts.clear(); ChatManager.faxbot = null; ChatManager.faxbotMessage = null; } public static final void resetChatLiteracy() { ChatManager.checkedLiteracy = false; } public static final void resetClanMessages() { ChatManager.clanMessages.clear(); } public static final boolean checkedChatLiteracy() { return ChatManager.checkedLiteracy; } public static final boolean getChatLiteracy() { return ChatManager.chatLiterate; } public static final void setChatLiteracy( final boolean on ) { ChatManager.checkedLiteracy = true; ChatManager.chatLiterate = on; } public static final boolean chatLiterate() { // If login is incomplete because we are stuck in a fight or // choice, don't bother checking the Altar of Literacy if ( KoLmafia.isRefreshing() ) { return true; } if( !ChatManager.checkedLiteracy ) { ChatManager.chatLiterate = Preferences.getBoolean( "chatLiterate" ); if ( !ChatManager.chatLiterate ) { AltarOfLiteracyRequest request = new AltarOfLiteracyRequest(); RequestThread.postRequest( request ); } Preferences.setBoolean( "chatLiterate", ChatManager.chatLiterate ); } return ChatManager.chatLiterate; } /** * Initializes the chat buffer with the provided chat pane. Note that * the chat refresher will also be initialized by calling this method; * to stop the chat refresher, call the <code>dispose()</code> method. */ public static final void initialize() { if ( ChatManager.isRunning || !LoginRequest.completedLogin() ) { return; } if ( !ChatManager.chatLiterate() ) { KoLmafia.updateDisplay( "You cannot access chat until you complete the Altar of Literacy" ); return; } ChatManager.isRunning = true; StyledChatBuffer.initializeHighlights(); synchronized ( ChatManager.activeChannels ) { for ( String channel : ChatManager.activeChannels ) { ChatManager.openWindow( channel, false ); } } ChatPoller.startInstance(); RequestThread.postRequest( new ChannelColorsRequest() ); } public static final void setFaxBot( final String faxbot ) { ChatManager.faxbot = faxbot; } public static synchronized final String getLastFaxBotMessage() { if ( ChatManager.faxbotMessage != null ) { String message = ChatManager.faxbotMessage.getContent(); ChatManager.faxbotMessage = null; return message; } return null; } public static final boolean isValidChatReplyRecipient( String playerName ) { synchronized ( ChatManager.validChatReplyRecipients ) { return validChatReplyRecipients.contains( playerName ); } } /** * Disposes the messenger's frames. */ public static final void dispose() { if ( ChatManager.isRunning ) { ChatManager.isRunning = false; ChatSender.sendMessage( null, "/exit", false ); } ChatPoller.stopInstance(); ChatManager.activeWindows.clear(); ChatManager.activeChannels.clear(); ChatManager.currentChannel = null; ChatManager.tabbedFrame = null; } public static final boolean isRunning() { return ChatManager.isRunning; } public static final String getCurrentChannel() { return ChatManager.currentChannel; } public static final StyledChatBuffer getBuffer( final String bufferKey ) { StyledChatBuffer buffer = ChatManager.instantMessageBuffers.get( bufferKey ); if ( buffer != null ) { return buffer; } buffer = new StyledChatBuffer( bufferKey, "black", !bufferKey.equals( "[high]" ) ); if ( Preferences.getBoolean( "logChatMessages" ) ) { String fileSuffix = bufferKey; if ( fileSuffix.startsWith( "/" ) ) { fileSuffix = "[" + fileSuffix.substring( 1 ) + "]"; } StringBuilder fileName = new StringBuilder(); fileName.append( KoLConstants.DAILY_FORMAT.format( new Date() ) ); fileName.append( "_" ); fileName.append( KoLCharacter.baseUserName() ); fileName.append( "_" ); fileName.append( fileSuffix ); fileName.append( ".html" ); buffer.setLogFile( new File( KoLConstants.CHATLOG_LOCATION, fileName.toString() ) ); } synchronized ( ChatManager.bufferEntries ) { ChatManager.instantMessageBuffers.put( bufferKey, buffer ); ChatManager.bufferEntries = new Entry[ ChatManager.instantMessageBuffers.size() ]; ChatManager.instantMessageBuffers.entrySet().toArray( ChatManager.bufferEntries ); } return buffer; } public static final void startTriviaGame() { ChatManager.triviaGameContacts.clear(); ChatManager.triviaGameId = "[trivia" + (++ChatManager.triviaGameIndex) + "]"; ChatManager.triviaGameActive = true; if ( ChatManager.triviaGameContactListFrame == null ) { ChatManager.triviaGameContactListFrame = new ContactListFrame( ChatManager.triviaGameContacts ); } ChatManager.triviaGameContactListFrame.setTitle( "Contestants for " + triviaGameId ); ChatManager.triviaGameContactListFrame.setVisible( true ); } public static final void stopTriviaGame() { ChatManager.triviaGameActive = false; } public static void processMessages( final List<ChatMessage> messages ) { for ( ChatMessage message : messages ) { ChatManager.processMessage( message ); } } private static void displayMessage( final String bufferKey, final boolean highlight, final String message ) { ChatManager.openWindow( bufferKey, highlight ); StyledChatBuffer buffer = ChatManager.getBuffer( bufferKey ); buffer.append( message ); } public static void processMessage( final ChatMessage message ) { if ( message instanceof EventMessage ) { ChatManager.processEvent( (EventMessage) message ); return; } if ( message instanceof SystemMessage ) { ChatManager.processSystemMessage( (SystemMessage) message ); return; } if ( message instanceof EnableMessage ) { ChatManager.processChannelEnable( (EnableMessage) message ); return; } if ( message instanceof DisableMessage ) { ChatManager.processChannelDisable( (DisableMessage) message ); return; } String sender = message.getSender(); String recipient = message.getRecipient(); if ( message instanceof ModeratorMessage ) { // Format the message String displayHTML = ChatFormatter.formatChatMessage( message ); // Display the message in the current channel ChatManager.displayMessage( ChatManager.getBufferKey( recipient ), true, displayHTML ); return; } if ( ChatManager.faxbot != null && sender.equalsIgnoreCase( ChatManager.faxbot ) ) { ChatManager.faxbotMessage = message; } String content = message.getContent(); if ( recipient == null ) { ChatManager.processCommand( sender, content, recipient ); } else if ( recipient.equals( "/clan" ) || recipient.equals( "/hobopolis" ) || recipient.equals( "/slimetube" ) || recipient.equals( "/dread" ) || recipient.equals( "/hauntedhouse" ) ) { ChatManager.clanMessages.add( message ); ChatManager.processCommand( sender, content, recipient ); } else if ( recipient.equals( "/talkie" ) ) { // Allow chatbot scripts to process talkie messages ChatManager.processCommand( sender, content, recipient ); } else if ( Preferences.getBoolean( "chatBeep" ) && ( StringUtilities.globalStringReplace( KoLCharacter.getUserName(), " ", "_" ).equalsIgnoreCase( recipient ) ) ) { Toolkit.getDefaultToolkit().beep(); } String destination = recipient; if ( KoLCharacter.getUserName().equals( recipient ) ) { if ( ChatManager.triviaGameActive ) { if ( !ChatManager.triviaGameContacts.contains( message.getSender() ) ) { ChatManager.triviaGameContacts.add( message.getSender() ); } } ChatManager.processCommand( sender, content, "" ); destination = sender; } // Display the message in the appropriate tab ChatManager.displayMessage( ChatManager.getBufferKey( destination ), true, ChatFormatter.formatChatMessage( message ) ); } public static final String getBufferKey( String destination ) { String bufferKey = destination.toLowerCase(); if ( Preferences.getBoolean( "mergeHobopolisChat" ) ) { if ( destination.equals( "/hobopolis" ) || destination.equals( "/slimetube" ) || destination.equals( "/dread" ) || destination.equals( "/hauntedhouse" ) ) { bufferKey = "/clan"; } } if ( !bufferKey.startsWith( "/" ) && ChatManager.triviaGameActive ) { bufferKey = triviaGameId; } return bufferKey; } public static final void processEvent( final EventMessage message ) { String content = message.getContent(); // Parse buffs bestowed upon us, etc. ChatManager.parseEvent( content ); // If we are suppressing event display, bail now if ( Preferences.getBoolean( "greenScreenProtection" ) || BuffBotHome.isBuffBotActive() || message.isHidden() ) { return; } // Otherwise, munge it, save it, and display it EventManager.addChatEvent( ChatFormatter.formatChatMessage( message, false ) ); String cleanContent = KoLConstants.ANYTAG_PATTERN.matcher( content ).replaceAll( "" ); ChatManager.processCommand( "", cleanContent, "Events" ); ChatManager.broadcastEvent( message ); } // <a class=nounder target=mainpane href=showplayer.php?who=121572><b>Veracity</b></a> has played Fat Leon's Phat Loot Lyric for you. (10 Adventures) // An Elemental Saucesphere has been conjured around you by <a class=nounder target=mainpane href=showplayer.php?who=121572><b>Veracity</b></a> (5 Adventures) // An Jalapeño Saucesphere has been conjured around you by <a class=nounder target=mainpane href=showplayer.php?who=121572><b>Veracity</b></a> (5 Adventures) // An Antibiotic Saucesphere has been conjured around you by <a class=nounder target=mainpane href=showplayer.php?who=121572><b>Veracity</b></a> (5 Adventures) // A layer of Scarysauce has been conjured around you by <a class=nounder target=mainpane href=showplayer.php?who=121572><b>Veracity</b></a> (5 Adventures) // <a href='showplayer.php?who=121572' style='color: green' target='mainpane'>Veracity</a> has given you a psychokinetic hug! // <a class=nounder target=mainpane href=showplayer.php?who=121572><b>Veracity</b></a> has fortified you with the Empathy of the Newt. (10 Adventures) // <a class=nounder target=mainpane href=showplayer.php?who=121572><b>Veracity</b></a> has imbued you with Reptilian Fortitude. (10 Adventures) // <a class=nounder target=mainpane href=showplayer.php?who=121572><b>Veracity</b></a> has given you the Tenacity of the Snapper. (10 Adventures) // An Astral Shell has been conjured around you by <a class=nounder target=mainpane href=showplayer.php?who=121572><b>Veracity</b></a>. (10 Adventures) // A Ghostly Shell has been conjured around you by <a class=nounder target=mainpane href=showplayer.php?who=121572><b>Veracity</b></a>. (10 Adventures) private static final Pattern BUFF_PATTERN = Pattern.compile( "(?:A layer of |A |An |has played |has fortified you with |has imbued you with |has given you the )(.*?) *(?:for you|has been conjured around you|\\.).*?\\(([\\d]*) Adventures\\)" ); // "Hey, Lazy Servant, get to work" you bark at your Priest.<!--js(parent.charpane.location.href="charpane.php";)--> private static final Pattern BARK_PATTERN = Pattern.compile( "you bark at your (.*?)\\." ); public static final void parseEvent( final String content ) { if ( content.contains( "Lazy Servant" ) ) { Matcher barkMatcher = BARK_PATTERN.matcher( content ); if ( barkMatcher.find() ) { String servantType = barkMatcher.group( 1 ); EdServantData.setEdServant( servantType ); } return; } if ( content.contains( " has " ) ) { // This is a nice idea, but if we are doing other // things, we may already have seen api.php or // charpane.php and have up-to-date effects if ( false ) { Matcher buffMatcher = BUFF_PATTERN.matcher( content ); if ( buffMatcher.find() ) { String skillName = buffMatcher.group( 1 ); String effectName = UneffectRequest.skillToEffect( skillName ); int duration = StringUtilities.parseInt( buffMatcher.group( 2 ) ); AdventureResult effect = new AdventureResult( effectName, duration, true ); AdventureResult.addResultToList( KoLConstants.activeEffects, effect ); return; } } // If we can't figure it out, refresh effects via api.php ApiRequest.updateStatus( true ); } } public static final void processSystemMessage( final SystemMessage message ) { // Format the message String displayHTML = ChatFormatter.formatChatMessage( message ); // If the user doesn't want events to appear in all tabs, // put the System Message only in the Events tab if ( !Preferences.getBoolean( "broadcastEvents" ) ) { // Put the message in the Events tab and highlight it ChatManager.displayMessage( "[events]", true, displayHTML ); return; } // Otherwise, broadcast it synchronized ( ChatManager.bufferEntries ) { for ( Entry<String,StyledChatBuffer> entry : ChatManager.bufferEntries ) { String key = entry.getKey(); StyledChatBuffer buffer = entry.getValue(); buffer.append( displayHTML ); // Open the Events tab if ( key.equals( "[events]" ) ) { ChatManager.openWindow( key, false ); } } } } public static final void processChannelEnable( final EnableMessage message ) { String sender = message.getSender(); if ( !ChatManager.activeChannels.contains( sender ) ) { String bufferKey = ChatManager.getBufferKey( sender ); ChatManager.activeChannels.add( sender ); ChatManager.openWindow( bufferKey, false ); } if ( message.isTalkChannel() ) { ChatManager.currentChannel = sender; } } public static final void processChannelDisable( final DisableMessage message ) { String sender = message.getSender(); if ( ChatManager.activeChannels.contains( sender ) ) { String bufferKey = ChatManager.getBufferKey( sender ); ChatManager.activeChannels.remove( sender ); ChatManager.closeWindow( bufferKey ); } } public static final void processCommand( final String sender, final String content, final String channel ) { if ( sender == null || content == null ) { return; } // If a buffbot is running, certain commands become active, such // as help, restores, and logoff. if ( "".equals( channel ) && BuffBotHome.isBuffBotActive() ) { if ( content.equalsIgnoreCase( "help" ) ) { ChatSender.sendMessage( sender, "Please check my profile.", false ); return; } if ( content.equalsIgnoreCase( "restores" ) ) { ChatSender.sendMessage( sender, "I currently have " + RecoveryManager.getRestoreCount() + " mana restores at my disposal.", false ); return; } if ( content.equalsIgnoreCase( "logoff" ) ) { BuffBotHome.update( BuffBotHome.ERRORCOLOR, "Logoff requested by " + sender ); if ( ClanManager.isCurrentMember( sender ) ) { KoLmafia.quit(); } BuffBotHome.update( BuffBotHome.ERRORCOLOR, sender + " added to ignore list" ); ChatSender.sendMessage( sender, "/baleet", false ); return; } } // Otherwise, sometimes clannies want to take advantage of KoLmafia's // automatic chat logging. In that case... if ( "".equals( channel ) && content.equalsIgnoreCase( "update" ) ) { if ( !Preferences.getBoolean( "chatServesUpdates" ) ) { KoLmafia.updateDisplay( sender + " tried to retrieve recent clan chat messages from you, but you have that feature disabled." ); return; } if ( !ClanManager.isCurrentMember( sender ) ) { KoLmafia.updateDisplay( sender + " tried to retrieve recent clan chat messages from you, but they are not in your clan." ); return; } StringBuilder mailContent = new StringBuilder(); Iterator<Object> clanMessageIterator = ChatManager.clanMessages.iterator(); while ( clanMessageIterator.hasNext() ) { ChatMessage message = (ChatMessage) clanMessageIterator.next(); String cleanMessage = KoLConstants.ANYTAG_PATTERN.matcher( ChatFormatter.formatChatMessage( message ) ).replaceAll( "" ); mailContent.append( cleanMessage ); mailContent.append( "\n" ); } RequestThread.postRequest( new SendMailRequest( sender, mailContent.toString() ) ); KoLmafia.updateDisplay( "Recent clan chat messages were sent to " + sender ); return; } ChatManager.invokeChatScript( sender, content, channel ); } public static final void invokeChatScript( final String sender, final String content, final String channel ) { String scriptName = Preferences.getString( "chatbotScript" ); if ( scriptName.equals( "" ) ) { return; } List<File> scriptFiles = KoLmafiaCLI.findScriptFile( scriptName ); Interpreter interpreter = KoLmafiaASH.getInterpreter( scriptFiles ); if ( interpreter == null ) { return; } String name = scriptFiles.get( 0 ).getName(); int parameterCount = interpreter.getParser().getMainMethod().getVariableReferences().size(); String[] scriptParameters; if ( parameterCount == 3 ) { scriptParameters = new String[] { sender, content, channel }; } else if ( channel != null && !channel.equals( "" ) ) { scriptParameters = new String[] { sender, content }; } else { return; } synchronized ( ChatManager.validChatReplyRecipients ) { ChatManager.validChatReplyRecipients.add( sender ); } synchronized ( interpreter ) { KoLmafiaASH.logScriptExecution( "Starting chat script: ", name, interpreter ); interpreter.execute( "main", scriptParameters ); KoLmafiaASH.logScriptExecution( "Finished chat script: ", name, interpreter ); } synchronized ( ChatManager.validChatReplyRecipients ) { ChatManager.validChatReplyRecipients.remove( sender ); } } public static final void broadcastEvent( final EventMessage message ) { if ( !ChatManager.isRunning() ) { return; } String displayHTML = ChatFormatter.formatChatMessage( message ); StyledChatBuffer buffer = ChatManager.getBuffer( "[events]" ); buffer.append( displayHTML ); if ( message instanceof InternalMessage || !Preferences.getBoolean( "broadcastEvents" ) ) { ChatManager.openWindow( "[events]", true ); } else { ChatManager.openWindow( "[events]", false ); synchronized ( ChatManager.bufferEntries ) { for ( Entry<String,StyledChatBuffer> entry : ChatManager.bufferEntries ) { String key = entry.getKey(); if ( key.equals( "[events]" ) ) { continue; } buffer = entry.getValue(); buffer.append( displayHTML ); } } } } public static final void openWindow( final String bufferKey, boolean highlightOnOpen ) { if ( StaticEntity.isHeadless() || !ChatManager.isRunning || bufferKey == null ) { return; } if ( ChatManager.activeWindows.contains( bufferKey ) ) { if ( ChatManager.tabbedFrame != null ) { ChatManager.tabbedFrame.highlightTab( bufferKey ); } return; } ChatManager.activeWindows.add( bufferKey ); if ( Preferences.getBoolean( "useTabbedChatFrame" ) ) { if ( ChatManager.tabbedFrame == null ) { CreateFrameRunnable creator = new CreateFrameRunnable( TabbedChatFrame.class ); boolean appearsInTab = GenericFrame.appearsInTab( "ChatManager" ); ChatManager.tabbedFrame = (TabbedChatFrame) creator.createFrame( appearsInTab ); } ChatManager.tabbedFrame.addTab( bufferKey ); if ( highlightOnOpen ) { ChatManager.tabbedFrame.highlightTab( bufferKey ); } return; } ChatFrame frame = new ChatFrame( bufferKey ); CreateFrameRunnable.decorate( frame ); frame.setVisible( true ); } public static final void closeWindow( String closedWindow ) { if ( closedWindow == null ) { ChatManager.dispose(); return; } ChatManager.activeWindows.remove( closedWindow ); if ( !ChatManager.isRunning() || !closedWindow.startsWith( "/" ) ) { return; } if ( !ChatManager.activeChannels.contains( closedWindow ) ) { return; } if ( !closedWindow.equals( ChatManager.getCurrentChannel() ) ) { ChatSender.sendMessage( closedWindow, "/listen", false ); return; } String selectedWindow = null; for ( String channel : ChatManager.activeChannels ) { if ( channel.startsWith( "/" ) && !channel.equals( closedWindow ) ) { selectedWindow = channel; break; } } if ( selectedWindow != null ) { ChatSender.sendMessage( selectedWindow, "/channel", false ); return; } } public static final void checkFriends() { ChatSender.sendMessage( null, "/friends", false ); } public static void applyHighlights() { for ( Entry<String,StyledChatBuffer> entry : ChatManager.bufferEntries ) { StyledChatBuffer buffer = entry.getValue(); buffer.applyHighlights(); } } }