/** * 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.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import net.sourceforge.kolmafia.AdventureResult; import net.sourceforge.kolmafia.RequestThread; import net.sourceforge.kolmafia.persistence.ItemFinder; import net.sourceforge.kolmafia.request.ChatRequest; import net.sourceforge.kolmafia.session.ContactManager; import net.sourceforge.kolmafia.swingui.CommandDisplayFrame; import net.sourceforge.kolmafia.swingui.widget.ShowDescriptionList; import net.sourceforge.kolmafia.utilities.StringUtilities; public class ChatSender { private static final Pattern PRIVATE_MESSAGE_PATTERN = Pattern.compile( "^/(?:msg|whisper|w|tell)\\s+(\\S+)\\s+", Pattern.CASE_INSENSITIVE ); private static boolean scriptedMessagesEnabled = true; private static final ArrayList<String> CHANNEL_COMMANDS = new ArrayList<String>(); static { ChatSender.CHANNEL_COMMANDS.add( "/em" ); ChatSender.CHANNEL_COMMANDS.add( "/me" ); ChatSender.CHANNEL_COMMANDS.add( "/ann" ); } public static final void executeMacro( String macro ) { if ( !ChatSender.scriptedMessagesEnabled || !ChatManager.chatLiterate() ) { return; } ChatRequest request = new ChatRequest( macro, false ); List<ChatMessage> accumulatedMessages = new LinkedList<ChatMessage>(); accumulatedMessages.addAll( ChatSender.sendRequest( request, false ) ); ChatPoller.addSentEntry( request.responseText, false ); for ( ChatMessage message : accumulatedMessages ) { String recipient = message.getRecipient(); ChatSender.scriptedMessagesEnabled = recipient == null || recipient.equals( "" ) || recipient.equals( "/clan" ) || recipient.equals( "/hobopolis" ) || recipient.equals( "/slimetube" ) || recipient.equals( "/dread" ) || recipient.equals( "/hauntedhouse" ); } } public static final void sendMessage( String contact, String message, boolean channelRestricted ) { if ( !ChatManager.chatLiterate() ) { return; } List<String> grafs = getGrafs( contact, message ); if ( grafs == null ) { return; } List<ChatMessage> accumulatedMessages = new LinkedList<ChatMessage>(); for ( String graf : grafs ) { String responseText = ChatSender.sendMessage( accumulatedMessages, graf, false, channelRestricted, false ); ChatPoller.addSentEntry( responseText, false ); } } public static final String sendMessage( List<ChatMessage> accumulatedMessages, String graf, boolean isRelayRequest, boolean channelRestricted, boolean tabbedChat ) { if ( graf == null ) { return ""; } if ( !ChatManager.chatLiterate() ) { return ""; } if ( channelRestricted && !ChatSender.scriptedMessagesEnabled ) { return ""; } if ( ChatSender.executeCommand( graf ) ) { return ""; } if ( graf.startsWith( "/examine" ) ) { String item = graf.substring( graf.indexOf( " " ) ).trim(); AdventureResult result = ItemFinder.getFirstMatchingItem( item, false, ItemFinder.ANY_MATCH ); if ( result != null ) { ShowDescriptionList.showGameDescription( result ); } else { EventMessage message = new EventMessage( "Unable to find a unique match for " + item, "green" ); ChatManager.broadcastEvent( message ); } return ""; } ChatPoller.sentMessage( tabbedChat ); ChatRequest request = new ChatRequest( graf, tabbedChat ); List<ChatMessage> messages = ChatSender.sendRequest( request, tabbedChat ); accumulatedMessages.addAll( messages ); if ( channelRestricted ) { Iterator<ChatMessage> messageIterator = accumulatedMessages.iterator(); while ( messageIterator.hasNext() && ChatSender.scriptedMessagesEnabled ) { ChatMessage message = (ChatMessage) messageIterator.next(); String recipient = message.getRecipient(); ChatSender.scriptedMessagesEnabled = recipient == null || recipient.equals( "/clan" ) || recipient.equals( "/hobopolis" ) || recipient.equals( "/slimetube" ) || recipient.equals( "/dread" ) || recipient.equals( "/hauntedhouse" ); } } return request.responseText == null ? "" : request.responseText; } public static final List<ChatMessage> sendRequest( ChatRequest request ) { return ChatSender.sendRequest( request, false ); } public static final List<ChatMessage> sendRequest( ChatRequest request, boolean tabbedChat ) { if ( !ChatManager.chatLiterate() ) { return Collections.EMPTY_LIST; } RequestThread.postRequest( request ); if ( request.responseText == null ) { return Collections.EMPTY_LIST; } List<ChatMessage> newMessages = new LinkedList<ChatMessage>(); if ( !tabbedChat ) { String graf = request.getGraf(); ChatSender.processResponse( newMessages, request.responseText, graf ); ChatManager.processMessages( newMessages ); } return newMessages; } public static final void processResponse( List<ChatMessage> newMessages, String responseText, String graf ) { // Protect against server lagging out if ( responseText == null || responseText.equals( "" ) ) { return; } if ( graf.equals( "/listen" ) ) { ChatParser.parseChannelList( newMessages, responseText ); } else if ( graf.startsWith( "/l " ) || graf.startsWith( "/listen " ) ) { ChatParser.parseListen( newMessages, responseText ); } else if ( graf.startsWith( "/c " ) || graf.startsWith( "/channel " ) ) { ChatParser.parseChannel( newMessages, responseText ); } else if ( graf.startsWith( "/s " ) || graf.startsWith( "/switch " ) ) { ChatParser.parseSwitch( newMessages, responseText ); } else if ( graf.startsWith( "/who " ) || graf.equals( "/f" ) || graf.equals( "/friends" ) || graf.equals( "/romans" ) || graf.equals( "/clannies" ) || graf.equals( "/countrymen" ) ) { ChatParser.parseContacts( newMessages, responseText, graf.equals( "/clannies" ) ); } else { ChatParser.parseLines( newMessages, responseText ); } } private static final List<String> getGrafs( String contact, String message ) { List<String> grafs = new LinkedList<String>(); if ( message.startsWith( "/do " ) || message.startsWith( "/run " ) || message.startsWith( "/cli " ) ) { grafs.add( message ); return grafs; } Matcher privateMessageMatcher = ChatSender.PRIVATE_MESSAGE_PATTERN.matcher( message ); if ( privateMessageMatcher.find() ) { contact = privateMessageMatcher.group( 1 ).trim(); message = message.substring( privateMessageMatcher.end() ).trim(); } // contact is null only for very short internally generated messages. if ( message.length() <= 256 || contact == null ) { String graf = ChatSender.getGraf( contact, message ); if ( graf != null ) { grafs.add( graf ); } return grafs; } // If the message is too long for one message, then // divide it into its component pieces. if ( message.startsWith( "/" ) ) { // This is one or more chained game commands. We need // to split on && boundaries. String[] commands = message.split( " +&& +" ); StringBuilder buffer = new StringBuilder(); for ( String command : commands ) { int current = buffer.length(); int needed = command.length(); // If you have a single command that is longer // than 256 characters, that's almost certainly // not going to work, but it gets its own graf. if ( current > 0 && ( needed > 256 || ( current + needed ) > ( 256 - 4 ) ) ) { String graf = ChatSender.getGraf( contact, buffer.toString() ); if ( graf != null ) { grafs.add( graf ); } buffer.setLength( 0 ); current = 0; } if ( current > 0 ) { buffer.append( " && " ); } buffer.append( command ); } if ( buffer.length() > 0 ) { String graf = ChatSender.getGraf( contact, buffer.toString() ); if ( graf != null ) { grafs.add( graf ); } } return grafs; } String command = ""; String splitter = " "; String prefix = "... "; String suffix = " ..."; if ( message.startsWith( "/" ) ) { int spaceIndex = message.indexOf( " " ); if ( spaceIndex != -1 ) { command = message.substring( 0, spaceIndex ).trim(); message = message.substring( spaceIndex ).trim(); } else { command = message.trim(); message = ""; } } String graf; int maxPiece = 255 - command.length() - suffix.length(); while ( message.length() > maxPiece ) { int splitPos = message.lastIndexOf( splitter, maxPiece ); if ( splitPos <= prefix.length() ) { splitPos = maxPiece; } graf = ChatSender.getGraf( contact, command + " " + message.substring( 0, splitPos ) + suffix ); if ( graf != null ) { grafs.add( graf ); } message = prefix + message.substring( splitPos + splitter.length() ); } graf = ChatSender.getGraf( contact, command + " " + message ); if ( graf != null ) { grafs.add( graf ); } return grafs; } private static final String getGraf( String contact, String message ) { String contactId = "[none]"; if ( contact != null ) { contactId = ContactManager.getPlayerId( contact ); contactId = StringUtilities.globalStringReplace( contactId, " ", "_" ).trim(); } String graf = message == null ? "" : message.trim(); if ( graf.startsWith( "/exit" ) ) { // Exiting chat should dispose. KoLmafia should send the // message to be server-friendly. if ( ChatManager.isRunning() ) { ChatManager.dispose(); } return null; } if ( contactId.startsWith( "[" ) ) { // This is a message coming from an aggregated window, so // leave it as is. } else if ( !contact.startsWith( "/" ) && !graf.startsWith( "/" ) ) { // Implied requests for a private message should be wrapped // in a /msg block. graf = "/msg " + contactId + " " + graf; } else if ( !graf.startsWith( "/" ) ) { // All non-command messages are directed to a channel. Append the // name of the channel to the beginning of the message so you // ensure the message gets there. graf = contact + " " + graf; } else { int spaceIndex = graf.indexOf( " " ); String baseCommand = spaceIndex == -1 ? graf.toLowerCase() : graf.substring( 0, spaceIndex ).toLowerCase(); if ( graf.equals( "/c" ) || graf.equals( "/channel" ) ) { graf = "/channel " + contact.substring( 1 ); } else if ( graf.equals( "/l" ) || graf.equals( "/listen" ) ) { graf = "/listen " + contact.substring( 1 ); } else if ( graf.equals( "/s") || graf.equals( "/switch" ) ) { graf = "/switch " + contact.substring( 1 ); } else if ( graf.equals( "/w" ) || graf.equals( "/who" ) ) { // Attempts to view the /who list use the name of the channel // when the user doesn't specify the channel. graf = "/who " + contact.substring( 1 ); } else if ( graf.equals( "/whois" ) || graf.equals( "/friend" ) || graf.equals( "/baleet" ) ) { graf = graf + " " + contact; } else if ( ChatSender.CHANNEL_COMMANDS.contains( baseCommand ) ) { if ( contact.startsWith( "/" ) ) { // Direct the message to a channel by appending the name // of the channel to the beginning of the message. graf = contact + " " + graf; } else { // And if it's a private message graf = "/msg " + contact + " " + graf; } } } if ( graf.startsWith( "/l " ) || graf.startsWith( "/listen " ) ) { String currentChannel = ChatManager.getCurrentChannel(); if ( currentChannel != null && graf.endsWith( currentChannel ) ) { return null; } } return graf; } private static final boolean executeCommand( String graf ) { if ( graf == null ) { return false; } graf = graf.trim(); int spaceIndex = graf.indexOf( " " ); // Strip off channel name if ( spaceIndex != -1 && ChatManager.activeChannels.contains( graf.substring( 0, spaceIndex ) ) ) { graf = graf.substring( spaceIndex ).trim(); spaceIndex = graf.indexOf( " " ); } if ( graf.equalsIgnoreCase( "/trivia" ) ) { ChatManager.startTriviaGame(); return true; } if ( graf.equalsIgnoreCase( "/endtrivia" ) || graf.equalsIgnoreCase( "/stoptrivia" ) ) { ChatManager.stopTriviaGame(); return true; } if ( spaceIndex == -1 ) { return false; } if ( !graf.startsWith( "/do " ) && !graf.startsWith( "/run " ) && !graf.startsWith( "/cli " ) ) { return false; } String command = graf.substring( spaceIndex ).trim(); CommandDisplayFrame.executeCommand( command ); return true; } }