/** * 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.util.regex.Matcher; import java.util.regex.Pattern; import net.sourceforge.kolmafia.KoLCharacter; import net.sourceforge.kolmafia.KoLConstants; import net.sourceforge.kolmafia.KoLmafia; import net.sourceforge.kolmafia.StaticEntity; import net.sourceforge.kolmafia.request.CharPaneRequest; import net.sourceforge.kolmafia.request.SpelunkyRequest; import net.sourceforge.kolmafia.session.EquipmentManager; import net.sourceforge.kolmafia.session.InventoryManager; import net.sourceforge.kolmafia.session.Limitmode; import net.sourceforge.kolmafia.utilities.LockableListFactory; import org.json.JSONException; import org.json.JSONObject; public class ApiRequest extends GenericRequest { private static final ApiRequest INSTANCE = new ApiRequest( "status" ); private static final CharPaneRequest CHARPANE = new CharPaneRequest(); private String what; private String id; public JSONObject JSON; private boolean silent = false; public ApiRequest() { this( "status" ); } public ApiRequest( final String what ) { super( "api.php" ); this.what = what; this.addFormField( "what", what ); this.addFormField( "for", "KoLmafia" ); this.id = ""; } public ApiRequest( final String what, final String id ) { this( what ); this.addFormField( "id", id ); this.id = id; } public ApiRequest( final String what, final int id ) { this( what, String.valueOf( id ) ); } public static String updateStatus() { return ApiRequest.updateStatus( false ); } public synchronized static String updateStatus( final boolean silent ) { // If in limitmode or Noobcore, API status doesn't contain the full information, so use Character Sheet instead if ( KoLCharacter.getLimitmode() != null || KoLCharacter.inNoobcore() ) { ApiRequest.CHARPANE.run(); return ApiRequest.CHARPANE.redirectLocation; } ApiRequest.INSTANCE.silent = silent; ApiRequest.INSTANCE.run(); return ApiRequest.INSTANCE.redirectLocation; } @Override protected boolean retryOnTimeout() { return true; } @Override public void run() { String message = this.silent ? null : this.what.equals( "status" ) ? "Loading character status..." : this.what.equals( "inventory" ) ? "Updating inventory..." : this.what.equals( "closet" ) ? "Updating closet..." : this.what.equals( "storage" ) ? "Updating storage..." : this.what.equals( "item" ) ? "Looking at item #" + this.id + "..." : null; if ( message != null ) { KoLmafia.updateDisplay( message ); } this.JSON = null; super.run(); // Save the JSON object so caller can look further at it this.JSON = ApiRequest.getJSON( this.responseText, this.what ); } @Override public void processResults() { if ( this.redirectLocation != null ) { return; } ApiRequest.parseResponse( this.getURLString(), this.responseText ); } private static final Pattern WHAT_PATTERN = Pattern.compile( "what=([^&]*)"); public static final void parseResponse( final String location, final String responseText ) { Matcher whatMatcher = ApiRequest.WHAT_PATTERN.matcher( location ); if ( !whatMatcher.find() ) { return; } String what = whatMatcher.group(1); if ( what.equals( "status" ) ) { ApiRequest.parseStatus( responseText ); } else if ( what.equals( "inventory" ) ) { ApiRequest.parseInventory( responseText ); } else if ( what.equals( "closet" ) ) { ApiRequest.parseCloset( responseText ); } else if ( what.equals( "storage" ) ) { ApiRequest.parseStorage( responseText ); } } /* Here's a sample collected on January 8, 2012 { "playerid":"121572", "name":"Veracity", "hardcore":"0", "ascensions":"242", "path":"0", "sign":"Mongoose", "roninleft":"0", "casual":"0", "drunk":"24", "full":"15", "turnsplayed":"758794", "familiar":"92", "hp":"2198", "mp":"586", "meat":"5231459", "adventures":"31", "level":"28", "rawmuscle":"589584", "rawmysticality":"267061", "rawmoxie":"267770", "basemuscle":"767", "basemysticality":"516", "basemoxie":"517", "familiarexp":"0", "class":"1", "lastadv":{ "id":"338", "name":"Dreadsylvanian Woods", "link":"adventure.php?snarfblat=338", "container":"clan_dreadsylvania.php" }, "title":"28", "pvpfights":"100", "maxhp":2326, "maxmp":1409, "spleen":"15", "muscle":1205, "mysticality":811, "moxie":709, "famlevel":16, "locked":false, "daysthisrun":"15", "equipment":{ "hat":"5122", "shirt":"2586", "pants":"4306", "weapon":"1325", "offhand":"3543", "acc1":"5460", "acc2":"4309", "acc3":"6530", "container":"6335", "familiarequip":"1325", "fakehands":0, "cardsleeve":"4968" }, "stickers":[0,0,0], "eleronkey":"xxx", "flag_config":{ "lazyinventory":0, "compactfights":0, "poppvpsearch":0, "questtracker":"1", "charpanepvp":"1", "fffights":"1", "compactchar":0, "noframesize":0, "fullnesscounter":0, "nodevdebug":0, "noquestnudge":"1", "nocalendar":"1", "alwaystag":"1", "clanlogins":0, "quickskills":0, "hprestorers":0, "hidejacko":0, "anchorshelf":0, "showoutfit":"1", "wowbar":0, "swapfam":0, "invimages":0, "showhandedness":0, "acclinks":"1", "invadvancedsort":"1", "powersort":0, "autodiscard":0, "unfamequip":0, "invclose":0, "sellstuffugly":"1", "oneclickcraft":0, "dontscroll":0, "multisume":"1", "threecolinv":"1", "profanity":"1", "tc_updatetitle":0, "tc_alwayswho":0, "tc_times":0, "tc_combineallpublic":0, "tc_eventsactive":0, "tc_hidebadges":0, "tc_colortabs":0, "tc_modifierkey":0, "tc_tabsonbottom":0, "chatversion":"1", "aabosses":0, "compacteffects":0, "slimhpmpdisplay":0, "ignorezonewarnings":"1", "whichpenpal":"1", "compactmanuel":0, "hideefarrows":0, "autoattack":0, "topmenu":0 }, "recalledskills":1, "freedralph":1, "mcd":0, "pwd":"638cd7484036dcac3bb4039fef93cb4c", "rollover":1378611001, "turnsthisrun":6465, "familiar_wellfed":0, "intrinsics":[], "familiarpic":"timesword", "pathname":"", "effects":{ "2d6d3ab04b40e1523aa9c716a04b3aab":["Leash of Linguini","14","string","skill:3010","16"], "ac32e95f470a7e0999863fa0db58d808":["Empathy","29","empathy","skill:2009","50"], "63e73adb3ecfb0cbf544db435eeeaf00":["Fat Leon's Phat Loot Lyric","22","fatleons","skill:6010","67"], "626c8ef76cfc003c6ac2e65e9af5fd7a":["Ode to Booze","18","odetobooze","skill:6014","71"], "bb44871dd165d4dc9b4d35daa46908ef":["Springy Fusilli","4","fusilli","skill:3015","130"], "5c8d3b5b4a6d403f95f27f5d29528c59":["Rage of the Reindeer","4","reindeer","skill:1015","131"], "c45d2469bf8cfd8c9bbc953e2a44f3c4":["Starry-Eyed","8","snowflakes","","350"], "d75aaa2cc6a8dcfa7cdddef658968e26":["Brother Smothers's Blessing","18","monkhead","","461"], "ff0e9ff0189cea375f60b5860463518d":["The Ballad of Richie Thingfinder","4","richie","item:4497","530"], "a614dc2ae977f948665b230f5723b567":["Brass Loins","98","flask","item:6428","1273"], "1839cbfe882d9243964d9097c1a3b0a6":["Silver Age Secrets","98","flask","item:6429","1274"] } } */ public static final void parseStatus( final String responseText ) { ApiRequest.parseStatus( ApiRequest.getJSON( responseText, "status" ) ); } public static final void parseStatus( final JSONObject JSON ) { if ( JSON == null ) { return; } // The data in the status is culled from many other KoL // pages. Let each page request handler parse the appropriate // data from it try { // Pull out the current ascension count. Do this first. // Some later processing depends on this. int ascensions = JSON.getInt( "ascensions" ); KoLCharacter.setAscensions( ascensions ); // Pull out the current password hash String pwd = JSON.getString( "pwd" ); GenericRequest.setPasswordHash( pwd ); // Many config options are available AccountRequest.parseStatus( JSON ); // Many things from the Char Pane are available CharPaneRequest.parseStatus( JSON ); // Many things from the Char Sheet are available CharSheetRequest.parseStatus( JSON ); String limitmode = KoLCharacter.getLimitmode(); if ( limitmode == Limitmode.SPELUNKY ) { // Parse Spelunky equipment SpelunkyRequest.parseStatus( JSON ); } else if ( limitmode == Limitmode.BATMAN ) { // Don't mess with equipment } else { // Parse currently worn equipment EquipmentManager.parseStatus( JSON ); } } catch ( JSONException e ) { ApiRequest.reportParseError( "status", JSON.toString(), e ); } finally { KoLCharacter.recalculateAdjustments(); KoLCharacter.updateStatus(); // Mana cost adjustment may have changed LockableListFactory.sort( KoLConstants.summoningSkills ); LockableListFactory.sort( KoLConstants.usableSkills ); } } public static final void parseInventory( final String responseText ) { InventoryManager.parseInventory( ApiRequest.getJSON( responseText, "inventory" ) ); } public static final void parseCloset( final String responseText ) { ClosetRequest.parseCloset( ApiRequest.getJSON( responseText, "closet" ) ); } public static final void parseStorage( final String responseText ) { StorageRequest.parseStorage( ApiRequest.getJSON( responseText, "storage" ) ); } public static final JSONObject getJSON( final String text, final String what ) { // Parse the string into a JSON object try { String str = ApiRequest.getJSONString( text ); return str == null ? null : new JSONObject( str ); } catch ( JSONException e ) { ApiRequest.reportParseError( what, text, e ); } return null; } private static final String getJSONString( String responseText ) { if ( responseText == null ) { return null; } int pos = responseText.indexOf( "{" ); return pos == -1 ? null : pos == 0 ? responseText : responseText.substring( pos ); } public static final void reportParseError( final String what, final String responseText, final JSONException e ) { KoLmafia.updateDisplay( "api.php?what=" + what + " parse error: " + e.getMessage() ); StaticEntity.printStackTrace( e, responseText ); } }