/** * 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.request; import java.io.BufferedReader; import java.io.File; import java.io.PrintStream; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.TreeMap; import java.util.regex.Matcher; import java.util.regex.Pattern; import net.sourceforge.kolmafia.AdventureResult; import net.sourceforge.kolmafia.KoLConstants; import net.sourceforge.kolmafia.KoLmafia; import net.sourceforge.kolmafia.StaticEntity; import net.sourceforge.kolmafia.objectpool.ItemPool; import net.sourceforge.kolmafia.persistence.ItemDatabase; import net.sourceforge.kolmafia.session.ClanManager; import net.sourceforge.kolmafia.utilities.FileUtilities; import net.sourceforge.kolmafia.utilities.LogStream; import net.sourceforge.kolmafia.utilities.StringUtilities; public class ClanLogRequest extends GenericRequest { private static final SimpleDateFormat STASH_FORMAT = new SimpleDateFormat( "MM/dd/yy, hh:mma", Locale.US ); private static final String STASH_ADD = "add"; private static final String STASH_TAKE = "take"; private static final String WAR_BATTLE = "warfare"; private static final String CLAN_WHITELIST = "whitelist"; private static final String CLAN_ACCEPT = "accept"; private static final String CLAN_LEAVE = "leave"; private static final String CLAN_BOOT = "boot"; private static final String TIME_REGEX = "(\\d\\d/\\d\\d/\\d\\d, \\d\\d:\\d\\d[AP]M)"; private static final String PLAYER_REGEX = "<a class=nounder href='showplayer.php\\?who=\\d+'>([^<]*?) \\(#\\d+\\)</a>"; private static final Pattern WAR_PATTERN = Pattern.compile( ClanLogRequest.TIME_REGEX + ": ([^<]*?) launched an attack against (.*?)\\.<br>" ); private static final Pattern LOGENTRY_PATTERN = Pattern.compile( "\t<li class=\"(.*?)\">(.*?): (.*?)</li>" ); private final Map<String, List<StashLogEntry>> stashMap = new TreeMap<String, List<StashLogEntry>>(); public ClanLogRequest() { super( "clan_log.php" ); } @Override protected boolean retryOnTimeout() { return true; } @Override public void run() { KoLmafia.updateDisplay( "Retrieving clan stash log..." ); File file = new File( KoLConstants.ROOT_LOCATION, "clan/" + ClanManager.getClanId() + "/stashlog.htm" ); this.loadPreviousData( file ); super.run(); KoLmafia.updateDisplay( "Stash log retrieved." ); // First, process all additions to the clan stash. // These are designated with the word "added to". this.handleItems( true ); // Next, process all the removals from the clan stash. // These are designated with the word "took from". this.handleItems( false ); // Next, process all the clan warfare log entries. // Though grouping by player isn't very productive, // KoLmafia is meant to show a historic history, and // showing it by player may prove enlightening. this.handleBattles(); // Now, handle all of the administrative-related // things in the clan. this.handleAdmin( ClanLogRequest.CLAN_WHITELIST, "was accepted into the clan \\(whitelist\\)", "", "auto-accepted through whitelist" ); this.handleAdmin( ClanLogRequest.CLAN_ACCEPT, "accepted", " into the clan", "accepted by " ); this.handleAdmin( ClanLogRequest.CLAN_LEAVE, "left the clan", "", "left clan" ); this.handleAdmin( ClanLogRequest.CLAN_BOOT, "booted", "", "booted by " ); this.saveCurrentData( file ); } private void loadPreviousData( final File file ) { this.stashMap.clear(); List<StashLogEntry> entryList = null; StashLogEntry entry = null; if ( file.exists() ) { try { String currentMember = ""; BufferedReader istream = FileUtilities.getReader( file ); String line; boolean startReading = false; while ( ( line = istream.readLine() ) != null ) { if ( startReading ) { if ( line.startsWith( " " ) ) { currentMember = line.substring( 1, line.length() - 1 ); entryList = this.stashMap.get( currentMember ); if ( entryList == null ) { entryList = new ArrayList<StashLogEntry>(); this.stashMap.put( currentMember, entryList ); } } else if ( line.length() > 0 && !line.startsWith( "<" ) ) { entry = new StashLogEntry( line ); if ( !entryList.contains( entry ) ) { entryList.add( entry ); } } } else if ( line.equals( "<!-- Begin Stash Log: Do Not Modify Beyond This Point -->" ) ) { startReading = true; } } istream.close(); } catch ( Exception e ) { // This should not happen. Therefore, print // a stack trace for debug purposes. StaticEntity.printStackTrace( e ); } } } private void saveCurrentData( final File file ) { String[] members = new String[ this.stashMap.size() ]; this.stashMap.keySet().toArray( members ); PrintStream ostream = LogStream.openStream( file, true ); Object[] entries; List<StashLogEntry> entryList = null; ostream.println( "<html><head>" ); ostream.println( "<title>Clan Stash Log @ " + ( new Date() ).toString() + "</title>" ); ostream.println( "<style><!--" ); ostream.println(); ostream.println( "\tbody { font-family: Verdana; font-size: 9pt }" ); ostream.println(); ostream.println( "\t." + ClanLogRequest.STASH_ADD + " { color: green }" ); ostream.println( "\t." + ClanLogRequest.STASH_TAKE + " { color: olive }" ); ostream.println( "\t." + ClanLogRequest.WAR_BATTLE + " { color: orange }" ); ostream.println( "\t." + ClanLogRequest.CLAN_WHITELIST + " { color: blue }" ); ostream.println( "\t." + ClanLogRequest.CLAN_ACCEPT + " { color: blue }" ); ostream.println( "\t." + ClanLogRequest.CLAN_LEAVE + " { color: red }" ); ostream.println( "\t." + ClanLogRequest.CLAN_BOOT + " { color: red }" ); ostream.println(); ostream.println( "--></style></head>" ); ostream.println(); ostream.println( "<body>" ); ostream.println(); ostream.println( "<!-- Begin Stash Log: Do Not Modify Beyond This Point -->" ); for ( int i = 0; i < members.length; ++i ) { ostream.println( " " + members[ i ] + ":" ); entryList = this.stashMap.get( members[ i ] ); Collections.sort( entryList ); entries = entryList.toArray(); ostream.println( "<ul>" ); for ( int j = 0; j < entries.length; ++j ) { ostream.println( entries[ j ].toString() ); } ostream.println( "</ul>" ); ostream.println(); } ostream.println( "</body></html>" ); ostream.close(); } private static final String ADD_REGEX = ClanLogRequest.TIME_REGEX + ": " + ClanLogRequest.PLAYER_REGEX + " added ([\\d,]+) (.*?)\\.<br>"; private static final String TAKE_REGEX = ClanLogRequest.TIME_REGEX + ": " + ClanLogRequest.PLAYER_REGEX + " took ([\\d,]+) (.*?)\\.<br>"; private void handleItems( final boolean parseAdditions ) { String handleType = parseAdditions ? ClanLogRequest.STASH_ADD : ClanLogRequest.STASH_TAKE; String regex = parseAdditions ? ClanLogRequest.ADD_REGEX : ClanLogRequest.TAKE_REGEX; String suffixDescription = parseAdditions ? "added to stash" : "taken from stash"; int lastItemId; int entryCount; List<StashLogEntry> entryList; String currentMember; StashLogEntry entry; StringBuilder entryBuffer = new StringBuilder(); Matcher entryMatcher = Pattern.compile( regex, Pattern.DOTALL ).matcher( this.responseText ); while ( entryMatcher.find() ) { try { entryBuffer.setLength( 0 ); currentMember = entryMatcher.group( 2 ).trim(); if ( !this.stashMap.containsKey( currentMember ) ) { this.stashMap.put( currentMember, new ArrayList<StashLogEntry>() ); } entryList = this.stashMap.get( currentMember ); entryCount = StringUtilities.parseInt( entryMatcher.group( 3 ) ); lastItemId = ItemDatabase.getItemId( entryMatcher.group( 4 ), entryCount ); entryBuffer.append( ( ItemPool.get( lastItemId, entryCount ) ).toString() ); entryBuffer.append( " " ); entryBuffer.append( suffixDescription ); entry = new StashLogEntry( handleType, ClanLogRequest.STASH_FORMAT.parse( entryMatcher.group( 1 ) ), entryBuffer.toString() ); if ( !entryList.contains( entry ) ) { entryList.add( entry ); } } catch ( Exception e ) { // This should not happen. Therefore, print // a stack trace for debug purposes. StaticEntity.printStackTrace( e ); } } this.responseText = entryMatcher.replaceAll( "" ); } private void handleBattles() { List<StashLogEntry> entryList; String currentMember; StashLogEntry entry; Matcher entryMatcher = ClanLogRequest.WAR_PATTERN.matcher( this.responseText ); while ( entryMatcher.find() ) { try { currentMember = entryMatcher.group( 2 ).trim(); if ( !this.stashMap.containsKey( currentMember ) ) { this.stashMap.put( currentMember, new ArrayList<StashLogEntry>() ); } entryList = this.stashMap.get( currentMember ); entry = new StashLogEntry( ClanLogRequest.WAR_BATTLE, ClanLogRequest.STASH_FORMAT.parse( entryMatcher.group( 1 ) ), "<i>" + entryMatcher.group( 3 ) + "</i> attacked" ); if ( !entryList.contains( entry ) ) { entryList.add( entry ); } } catch ( Exception e ) { // This should not happen. Therefore, print // a stack trace for debug purposes. StaticEntity.printStackTrace( e ); } } this.responseText = entryMatcher.replaceAll( "" ); } private void handleAdmin( final String entryType, final String searchString, final String suffixString, final String descriptionString ) { String regex = ClanLogRequest.TIME_REGEX + ": ([^<]*?) \\(#\\d+\\) " + searchString + "(.*?)" + suffixString + "\\.?<br>"; List<StashLogEntry> entryList; String currentMember; StashLogEntry entry; String entryString; Matcher entryMatcher = Pattern.compile( regex ).matcher( this.responseText ); while ( entryMatcher.find() ) { try { currentMember = entryMatcher.group( descriptionString.endsWith( " " ) ? 3 : 2 ).trim(); if ( !this.stashMap.containsKey( currentMember ) ) { this.stashMap.put( currentMember, new ArrayList<StashLogEntry>() ); } entryList = this.stashMap.get( currentMember ); entryString = descriptionString.endsWith( " " ) ? descriptionString + entryMatcher.group( 2 ) : descriptionString; entry = new StashLogEntry( entryType, ClanLogRequest.STASH_FORMAT.parse( entryMatcher.group( 1 ) ), entryString ); if ( !entryList.contains( entry ) ) { entryList.add( entry ); } } catch ( Exception e ) { // This should not happen. Therefore, print // a stack trace for debug purposes. StaticEntity.printStackTrace( e ); } } this.responseText = entryMatcher.replaceAll( "" ); } public static class StashLogEntry implements Comparable<StashLogEntry> { private Date timestamp; private final String stringform; public StashLogEntry( final String entryType, final Date timestamp, final String entry ) { this.timestamp = timestamp; this.stringform = "\t<li class=\"" + entryType + "\">" + ClanLogRequest.STASH_FORMAT.format( timestamp ) + ": " + entry + "</li>"; } public StashLogEntry( final String stringform ) { Matcher entryMatcher = ClanLogRequest.LOGENTRY_PATTERN.matcher( stringform ); entryMatcher.find(); //this.entryType = entryMatcher.group( 1 ); try { this.timestamp = ClanLogRequest.STASH_FORMAT.parse( entryMatcher.group( 2 ) ); } catch ( Exception e ) { // This should not happen. Therefore, print // a stack trace for debug purposes. StaticEntity.printStackTrace( e ); this.timestamp = new Date(); } this.stringform = stringform; } public int compareTo( final StashLogEntry o ) { return o == null || !( o instanceof StashLogEntry ) ? -1 : this.timestamp.before( ( (StashLogEntry) o ).timestamp ) ? 1 : this.timestamp.after( ( (StashLogEntry) o ).timestamp ) ? -1 : 0; } @Override public boolean equals( final Object o ) { return o == null || !( o instanceof StashLogEntry ) ? false : this.stringform.equals( o.toString() ); } @Override public int hashCode() { return this.stringform != null ? this.stringform.hashCode() : 0; } @Override public String toString() { return this.stringform; } } }