├── .gitignore ├── AssetBuilder-TF2 ├── pom.xml └── src │ └── main │ └── java │ └── com │ └── nosoop │ └── steamtrade │ └── assetbuilders │ └── TF2AssetBuilder.java ├── README.md ├── SteamTrade-Example ├── pom.xml └── src │ └── main │ └── java │ └── com │ └── nosoop │ ├── steamtradeasset │ ├── ROT13F2AssetBuilder.java │ └── ROT13F2Item.java │ └── steamtradeexample │ ├── SampleTradeListener.java │ └── SteamClientExcerpt.java └── SteamTrade-Java ├── nb-configuration.xml ├── pom.xml └── src ├── main └── java │ ├── bundled │ └── steamtrade │ │ └── org │ │ └── json │ │ ├── JSONArray.java │ │ ├── JSONException.java │ │ ├── JSONObject.java │ │ ├── JSONString.java │ │ ├── JSONStringer.java │ │ ├── JSONTokener.java │ │ └── JSONWriter.java │ └── com │ └── nosoop │ └── steamtrade │ ├── TradeListener.java │ ├── TradeSession.java │ ├── inventory │ ├── AppContextPair.java │ ├── AssetBuilder.java │ ├── ItemAttribute.java │ ├── TradeInternalAsset.java │ ├── TradeInternalCurrency.java │ ├── TradeInternalInventories.java │ ├── TradeInternalInventory.java │ └── TradeInternalItem.java │ └── status │ ├── Status.java │ ├── TradeAssetsObj.java │ ├── TradeEvent.java │ └── TradeUserStatus.java └── test ├── java └── com │ └── nosoop │ └── steamtrade │ └── inventory │ └── TradeInternalAssetTest.java └── resources ├── TEST_README.md └── inventorytest_99900.json /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | 3 | # Package Files # 4 | *.jar 5 | *.war 6 | *.ear 7 | -------------------------------------------------------------------------------- /AssetBuilder-TF2/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | com.nosoop.steamtrade.assets 6 | AssetBuilder-TF2 7 | 1.0-SNAPSHOT 8 | jar 9 | 10 | AssetBuilder-TF2 11 | http://maven.apache.org 12 | 13 | 14 | UTF-8 15 | 16 | 17 | 18 | 19 | junit 20 | junit 21 | 3.8.1 22 | test 23 | 24 | 25 | com.nosoop.steamtrade 26 | SteamTrade-Java 27 | 1.0-SNAPSHOT 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /AssetBuilder-TF2/src/main/java/com/nosoop/steamtrade/assetbuilders/TF2AssetBuilder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this template, choose Tools | Templates 3 | * and open the template in the editor. 4 | */ 5 | package com.nosoop.steamtrade.assetbuilders; 6 | 7 | import bundled.steamtrade.org.json.JSONArray; 8 | import bundled.steamtrade.org.json.JSONException; 9 | import bundled.steamtrade.org.json.JSONObject; 10 | import com.nosoop.steamtrade.inventory.AppContextPair; 11 | import com.nosoop.steamtrade.inventory.AssetBuilder; 12 | import com.nosoop.steamtrade.inventory.TradeInternalItem; 13 | 14 | /** 15 | * An asset builder that generates TF2Item instances. Add this to a list and 16 | * pass it to the TradeSession instance to use. 17 | * 18 | * @author nosoop < nosoop at users.noreply.github.com > 19 | */ 20 | public class TF2AssetBuilder extends AssetBuilder { 21 | /** 22 | * Parses the inventory data and returns a TF2Item instance. 23 | * 24 | * @param appContext The appid/contextid pair for the inventory this item 25 | * resides in. 26 | * @param rgInventory The item's rgInventory object. 27 | * @param rgDescription The item's rgDescription object. 28 | * @return A TF2Item instance. 29 | * @throws JSONException 30 | */ 31 | @Override 32 | public TradeInternalItem generateItem(AppContextPair appContext, JSONObject rgInventory, JSONObject rgDescription) throws JSONException { 33 | return new TF2Item(appContext, rgInventory, rgDescription); 34 | } 35 | 36 | @Override 37 | public boolean isSupported(AppContextPair appContext) { 38 | return appContext.getAppid() == 440 && appContext.getContextid() == 2; 39 | } 40 | 41 | /** 42 | * Enumeration of known TF2 item qualities. And undefined. 43 | */ 44 | public enum Quality { 45 | NORMAL, RARITY1, RARITY2, VINTAGE, RARITY3, RARITY4, UNIQUE, 46 | COMMUNITY, DEVELOPER, SELFMADE, CUSTOMIZED, STRANGE, COMPLETED, 47 | HAUNTED, COLLECTORS, UNDEFINED; 48 | 49 | static Quality getQuality(int quality) { 50 | switch (quality) { 51 | case 0: 52 | return NORMAL; 53 | case 1: 54 | return RARITY1; 55 | case 2: 56 | return RARITY2; 57 | case 3: 58 | return VINTAGE; 59 | case 4: 60 | return RARITY3; 61 | case 5: 62 | return RARITY4; 63 | case 6: 64 | return UNIQUE; 65 | case 7: 66 | return COMMUNITY; 67 | case 8: 68 | return DEVELOPER; 69 | case 9: 70 | return SELFMADE; 71 | case 10: 72 | return CUSTOMIZED; 73 | case 11: 74 | return STRANGE; 75 | case 12: 76 | return COMPLETED; 77 | case 13: 78 | return HAUNTED; 79 | case 14: 80 | return COLLECTORS; 81 | default: 82 | return UNDEFINED; 83 | } 84 | } 85 | } 86 | 87 | /** 88 | * A TradeInternalItem instance that holds data specific to Team Fortress 2 89 | * items. 90 | * 91 | * @author nosoop < nosoop at users.noreply.github.com > 92 | */ 93 | public class TF2Item extends TradeInternalItem { 94 | /** 95 | * Whether or not the item was an item put into gift wrap. 96 | */ 97 | boolean wasGifted; 98 | /** 99 | * The defindex of the item. Good to have if you'd like to refer to the 100 | * schema for some reason. 101 | */ 102 | int defIndex; 103 | /** 104 | * The quality indicator of the item. Also only good with the schema 105 | * really, though the name should have it. 106 | */ 107 | Quality quality; 108 | 109 | public TF2Item(AppContextPair appContext, JSONObject rgInventoryItem, 110 | JSONObject rgDescriptionItem) throws JSONException { 111 | super(appContext, rgInventoryItem, rgDescriptionItem); 112 | 113 | this.wasGifted = false; 114 | 115 | JSONObject appData = rgDescriptionItem.optJSONObject("app_data"); 116 | if (appData != null) { 117 | if (appData.has("def_index")) { 118 | defIndex = Integer.parseInt(appData.getString("def_index")); 119 | } 120 | 121 | if (appData.has("quality")) { 122 | quality = Quality.getQuality( 123 | Integer.parseInt(appData.getString("quality"))); 124 | } 125 | } 126 | 127 | // Iterate through descriptions. 128 | JSONArray descs = rgDescriptionItem.optJSONArray("descriptions"); 129 | if (descs != null) { 130 | for (int i = 0; i < descs.length(); i++) { 131 | JSONObject descriptionItem = descs.getJSONObject(i); 132 | String descriptionValue = descriptionItem.getString("value"); 133 | 134 | /** 135 | * Check if the description contains text that states if the 136 | * item is gifted. 137 | * 138 | * TODO Make this language dependent, as it assumes the 139 | * trade interface is in English. 140 | */ 141 | if (descriptionValue.contains("Gift from")) { 142 | wasGifted = true; 143 | } 144 | } 145 | } 146 | 147 | /** 148 | * If you have access to IEconItems_440/GetPlayerItems, and assuming 149 | * the trading partner does not have a private inventory, you can 150 | * also load the player data from the API yourself and identify the 151 | * item by its "id" key matching the assetid of this instance. 152 | * 153 | * Assuming JSON: 154 | * 155 | * In .attributes[], the existence of defindex 186 means the item 156 | * was a gift. 157 | * 158 | * .custom_name and .custom_desc or the existence of defindices 500 159 | * and 501 means that the item has a custom name and custom 160 | * description. 161 | */ 162 | } 163 | 164 | /** 165 | * Some function to check if this item is renamed based off of the 166 | * difference in market name and visible name. 167 | * 168 | * @return 169 | */ 170 | public boolean isRenamed() { 171 | return !getMarketName().equals(getName()) 172 | && getName().matches("''.*''"); 173 | } 174 | 175 | @Override 176 | public String getDisplayName() { 177 | String invName; 178 | 179 | invName = this.getName(); 180 | 181 | // Format item name for renamed items. 182 | if (this.isRenamed()) { 183 | invName = String.format("%s (%s)", getMarketName()); 184 | } 185 | 186 | // Format item name for gifted items. 187 | if (this.wasGifted) { 188 | invName = String.format("%s (gifted)", invName); 189 | } 190 | 191 | // TODO Format item for unusual effect, etc? 192 | 193 | return invName; 194 | } 195 | } 196 | 197 | } 198 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | SteamTrade-Java 2 | =============== 3 | 4 | An unofficial Java library for Valve's Steam Community item trading service. 5 | 6 | 7 | About 8 | ----- 9 | 10 | A heavily modified fork of [Top-Cat's ScrapBank.tf project](https://github.com/Top-Cat/ScrapBank.tf/), designed to be less dependent on the project's bots and SteamKit-specific classes. 11 | It does not rely on any of Valve's public-facing API for inventory loading; there is no need for an API key. 12 | 13 | The library, if you're unfamiliar with Steam trading, also supports: 14 | * Posting to and reading messages from trade chat 15 | * Purely private backpacks ("foreign inventories" -- loaded when an item from the inventory is added) 16 | * Dynamic loading of inventories (just about any game, pretty much) 17 | * Knowing exactly what inventories you have (scrapes them from the page though, ewww.) 18 | * GZIPped responses when retrieving pages 19 | * Loading of large inventories as needed (Valve made it so you load 2500? items at a time, this makes it so it only loads up to whatever item the other user has put up) 20 | 21 | Additionally, the library has extendable support via: 22 | * Pluggable support for game-specific items: Now you can extend the item support to, say, add WebAPI schema / inventory connectivity and handle any other items yourself (mostly; you get access to the inventory data in the scope of the asset to be loaded). 23 | 24 | Potential additions in the future include support for: 25 | * ~~Stackable items and currencies~~ Getting there. Currencies are viewable now, though they will only show up by name. No amount; similar case with stackables. 26 | * Inventory caching? For card swap bots and possibly other traders, the assetid could be loaded from a previous inventory download. 27 | 28 | The three included projects are: 29 | * SteamTrade-Java: The core of the project, handling trade connectivity and all. 30 | * SteamTrade-Example: A non-functioning example to show what can be done with the library. 31 | * AssetBuilder-TF2: An example of pluggable support for Team Fortress 2 items, exposing its item definition index and quality from the available item data, plus comments on how to extend support via the official WebAPI. 32 | 33 | 34 | Prerequisites, Dependencies and How-To 35 | -------------------------------------- 36 | 37 | To use the library, one must have a valid Steam sessionId and Steam login token, and also know when a trade is initiated. The library tries to be as independent as possible (e.g., using long values instead of SteamKit-Java's SteamIDs), but ultimately, using SteamKit-Java or a similar Steam client library would be the current best option. 38 | 39 | (Though the project is forked off of Top-Cat's mentioned above, it is not my intention to use the similarities between the name of my `SteamTrade-Java` project and his other `SteamKit-Java` project to imply affiliation.) 40 | 41 | A small snippet of the library in example use is available as the SampleTrade project. 42 | 43 | This is a Maven project and is dependent only on a copy of the `org.json` reference JSON library. The library is bundled with the project as the Java package `bundled.steamtrade.org.json`, to avoid conflicts with existing installs of `org.json`. 44 | The library has been given a few minor changes to support Java 1.5+ features, mainly using `valueOf()` methods over `new [...]()` to take advantage of the cached values when possible. 45 | 46 | 47 | Just a Note 48 | ----------- 49 | 50 | This library, while fairly featured and fleshed out for most uses (read: trading of simple, non-stackable, non-currency Steam items), is still undergoing changes in structure, shedding off old stuff and rearranging and streamlining others; be sure to keep an eye on the methods and what various changes there may be. The example trade listener will be updated to reflect changes as they come. 51 | 52 | Probably not going to version this and just keep it as a running snapshot. 53 | 54 | Also, the code will be released under the MIT License once the code has been cleaned enough to ensure that copyright is not an issue. 55 | -------------------------------------------------------------------------------- /SteamTrade-Example/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | com.nosoop.steamtrade.example 6 | SteamTrade-Example 7 | 1.0-SNAPSHOT 8 | jar 9 | 10 | SteamTrade-Example 11 | http://maven.apache.org 12 | 13 | 14 | 15 | org.apache.maven.plugins 16 | maven-compiler-plugin 17 | 2.3.2 18 | 19 | 1.7 20 | 1.7 21 | 22 | 23 | 24 | 25 | 26 | UTF-8 27 | 28 | 29 | 30 | 31 | junit 32 | junit 33 | 3.8.1 34 | test 35 | 36 | 37 | com.nosoop.steamtrade 38 | SteamTrade-Java 39 | 1.0-SNAPSHOT 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /SteamTrade-Example/src/main/java/com/nosoop/steamtradeasset/ROT13F2AssetBuilder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this template, choose Tools | Templates 3 | * and open the template in the editor. 4 | */ 5 | package com.nosoop.steamtradeasset; 6 | 7 | import bundled.steamtrade.org.json.JSONException; 8 | import bundled.steamtrade.org.json.JSONObject; 9 | import com.nosoop.steamtrade.inventory.AppContextPair; 10 | import com.nosoop.steamtrade.inventory.AssetBuilder; 11 | import com.nosoop.steamtrade.inventory.TradeInternalItem; 12 | 13 | /** 14 | * Implementation of a silly little asset builder. 15 | * 16 | * @author nosoop < nosoop at users.noreply.github.com > 17 | */ 18 | public class ROT13F2AssetBuilder extends AssetBuilder { 19 | /** 20 | * Generates a TradeInternalItem, given a game's app-context pair, a 21 | * JSONObject representing the inventory data for the item, and a JSONObject 22 | * representing the description for the item. 23 | * 24 | * @param appContext 25 | * @param rgInventory 26 | * @param rgDescription 27 | * @return 28 | * @throws JSONException 29 | */ 30 | @Override 31 | public TradeInternalItem generateItem(AppContextPair appContext, JSONObject rgInventory, JSONObject rgDescription) throws JSONException { 32 | /** 33 | * JSON preprocessing (not that you'd ever need to): 34 | * 35 | * Uses the ROT13 cipher on the name and puts it back into the 36 | * description object to be used in constructing the TradeInternalItem 37 | * instance. 38 | */ 39 | rgDescription.put("name", rot13(rgDescription.getString("name"))); 40 | 41 | /** 42 | * One can also subclass TradeInternalItem and TradeInternalCurrency to 43 | * add new fields and call its constructor instead. 44 | */ 45 | return new TradeInternalItem(appContext, rgInventory, rgDescription); 46 | } 47 | 48 | @Override 49 | public boolean isSupported(AppContextPair appContext) { 50 | /** 51 | * Use this AssetBuilder for inventories 440/2 (Team Fortress 2). 52 | */ 53 | return appContext.getAppid() == 440 && appContext.getContextid() == 2; 54 | } 55 | 56 | public static String rot13(String input) { 57 | StringBuilder output = new StringBuilder(); 58 | for (int i = 0; i < input.length(); i++) { 59 | char c = input.charAt(i); 60 | if (c >= 'a' && c <= 'm') { 61 | c += 13; 62 | } else if (c >= 'A' && c <= 'M') { 63 | c += 13; 64 | } else if (c >= 'n' && c <= 'z') { 65 | c -= 13; 66 | } else if (c >= 'N' && c <= 'Z') { 67 | c -= 13; 68 | } 69 | output.append(c); 70 | } 71 | return output.toString(); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /SteamTrade-Example/src/main/java/com/nosoop/steamtradeasset/ROT13F2Item.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this template, choose Tools | Templates 3 | * and open the template in the editor. 4 | */ 5 | package com.nosoop.steamtradeasset; 6 | 7 | import bundled.steamtrade.org.json.JSONException; 8 | import bundled.steamtrade.org.json.JSONObject; 9 | import com.nosoop.steamtrade.inventory.AppContextPair; 10 | import com.nosoop.steamtrade.inventory.TradeInternalItem; 11 | 12 | /** 13 | * 14 | * @author nosoop < nosoop at users.noreply.github.com > 15 | */ 16 | public class ROT13F2Item extends TradeInternalItem { 17 | public ROT13F2Item(AppContextPair appContext, JSONObject rgInventoryItem, JSONObject rgDescriptionItem) throws JSONException { 18 | super(appContext, rgInventoryItem, rgDescriptionItem); 19 | 20 | // extend with item info 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /SteamTrade-Example/src/main/java/com/nosoop/steamtradeexample/SampleTradeListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2014 nosoop < nosoop at users.noreply.github.com >. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package com.nosoop.steamtradeexample; 25 | 26 | import bundled.steamtrade.org.json.JSONException; 27 | import com.nosoop.steamtrade.TradeListener; 28 | import com.nosoop.steamtrade.inventory.*; 29 | import java.util.List; 30 | 31 | /** 32 | * Example trade listener to show some of what is possible in a programming a 33 | * trade. 34 | * 35 | * @author nosoop < nosoop at users.noreply.github.com > 36 | */ 37 | public class SampleTradeListener extends TradeListener { 38 | /** 39 | * Event fired when the trade has encountered an error. 40 | * 41 | * @param errorCode The error code for the given error. Known values are 42 | * available as constants under TradeListener.TradeErrorCodes. 43 | */ 44 | @Override 45 | public void onError(int errorCode, String msg) { 46 | String errorMessage; 47 | switch (errorCode) { 48 | case TradeStatusCodes.STATUS_ERRORMESSAGE: 49 | errorMessage = msg; 50 | break; 51 | case TradeStatusCodes.TRADE_CANCELLED: 52 | errorMessage = "The trade has been canceled."; 53 | break; 54 | case TradeStatusCodes.STATUS_PARSE_ERROR: 55 | errorMessage = "We have timed out."; 56 | break; 57 | case TradeStatusCodes.PARTNER_TIMED_OUT: 58 | errorMessage = "Other user timed out."; 59 | break; 60 | case TradeStatusCodes.TRADE_FAILED: 61 | errorMessage = "Trade failed."; 62 | break; 63 | default: 64 | errorMessage = "Unhandled error code " + errorCode + "."; 65 | } 66 | 67 | if (!msg.equals(TradeStatusCodes.EMPTY_MESSAGE)) { 68 | errorMessage += " (" + msg + ")"; 69 | } 70 | 71 | System.out.println(errorMessage); 72 | } 73 | 74 | /** 75 | * Event fired every time the trade session is polled for updates to notify 76 | * the listener of how long we have been in the trade and how long it has 77 | * been since the trade partner's last input. 78 | * 79 | * Taking this approach over letting the software writer make their own 80 | * polling thread to keep things simple. 81 | * 82 | * @param secondsSinceAction 83 | * @param secondsSinceTrade 84 | */ 85 | @Override 86 | public void onTimer(int secondsSinceAction, int secondsSinceTrade) { 87 | String message = String.format( 88 | "We have been in the trade for %d seconds. " 89 | + "It has been %d seconds since your last action.", 90 | secondsSinceTrade, secondsSinceAction); 91 | 92 | trade.getCmds().sendMessage(message); 93 | System.out.println(message); 94 | } 95 | 96 | /** 97 | * Called once everything but inventories have been initialized. (Originally 98 | * had to wait until all inventories were loaded, might as well keep it in 99 | * case.) 100 | */ 101 | @Override 102 | public void onWelcome() { 103 | trade.getCmds().sendMessage("Hello! Please wait while I figure out what items I have."); 104 | } 105 | 106 | /** 107 | * Called once everything is set. Show our inventory at our frontend, etc. 108 | */ 109 | @Override 110 | public void onAfterInit() { 111 | trade.getCmds().sendMessage("Ready to trade!"); 112 | } 113 | 114 | /** 115 | * Called when our trading partner has added an item. 116 | * 117 | * @param asset 118 | */ 119 | @Override 120 | public void onUserAddItem(TradeInternalAsset asset) { 121 | trade.getCmds().sendMessage("You added a " + asset.getMarketName()); 122 | 123 | if (asset instanceof TradeInternalItem) { 124 | TradeInternalItem item = (TradeInternalItem) asset; 125 | System.out.println("User added " + item.getName()); 126 | } else if (asset instanceof TradeInternalCurrency) { 127 | // Grabbing added currency amount. 128 | TradeInternalCurrency currency = (TradeInternalCurrency) asset; 129 | System.out.printf("User added %d %s.%n", 130 | currency.getTradedAmount(), asset.getDisplayName()); 131 | } 132 | 133 | for (TradeInternalAsset.Description d : asset.getDescriptions()) { 134 | System.out.printf("%s / %d / %s%n", d.getType(), d.getColor().getRGB(), 135 | d.getValue()); 136 | } 137 | } 138 | 139 | /** 140 | * Called when our trading partner has removed an item. 141 | * 142 | * @param inventoryItem 143 | */ 144 | @Override 145 | public void onUserRemoveItem(TradeInternalAsset inventoryItem) { 146 | trade.getCmds().sendMessage("You removed a " + inventoryItem.getMarketName()); 147 | } 148 | 149 | /** 150 | * Called when our trading partner sent a message. In this example we will 151 | * add a random item whenever the other person says something. 152 | * 153 | * @param msg The message text. 154 | */ 155 | @Override 156 | public void onMessage(String msg) { 157 | TradeInternalInventories itemStorage = trade.getSelf().getInventories(); 158 | TradeInternalInventory tf2backpack = itemStorage.getInventory(440, 2); 159 | 160 | List tf2items = tf2backpack.getItemList(); 161 | 162 | // Pick a random item from our TF2 inventory. 163 | TradeInternalItem item = tf2items.get((int) (Math.random() * tf2items.size())); 164 | trade.getCmds().addItem(item, 1); 165 | 166 | System.out.printf("User said %s and we put up a %s.%n", msg, item.getMarketName()); 167 | } 168 | 169 | /** 170 | * Called when our trading partner ticked or unticked the "ready" checkbox. 171 | * In response, we will do the opposite of what they did so the trade never 172 | * happens, 50% of the time. 173 | * 174 | * @param ready Whether or not the checkbox is set. 175 | */ 176 | @Override 177 | public void onUserSetReadyState(boolean ready) { 178 | System.out.println("User is ready: " + ready); 179 | 180 | if (Math.random() < .5) { 181 | trade.getCmds().setReady(!ready); 182 | } 183 | } 184 | 185 | /** 186 | * Called when the other user accepts the trade, in case you want to do 187 | * something about it. 188 | */ 189 | @Override 190 | public void onUserAccept() { 191 | trade.getCmds().sendMessage("Hah. Nope. Cancelled."); 192 | 193 | // TODO Handle JSONException in the library. 194 | try { 195 | trade.getCmds().cancelTrade(); 196 | } catch (JSONException ex) { 197 | } 198 | } 199 | 200 | /** 201 | * An event occurred. Normally wouldn't have to do anything here, but go 202 | * ahead and do something if you want. 203 | */ 204 | @Override 205 | public void onNewVersion() { 206 | actionCount++; 207 | 208 | System.out.println(actionCount + " actions have been made during the trade."); 209 | } 210 | int actionCount = 0; 211 | 212 | /** 213 | * Called when the trade has been completed successfully. 214 | */ 215 | @Override 216 | public void onTradeSuccess() { 217 | System.out.println("Items traded."); 218 | 219 | 220 | for (TradeInternalAsset item : trade.getSelf().getOffer()) { 221 | // TODO Provide example code to display what items were traded. 222 | } 223 | } 224 | 225 | /** 226 | * Called when the trade is done and we should stop polling for updates. 227 | * Remember, you can only be in one trade at a time (?), so you should be 228 | * telling the client that we are ready for another trade. 229 | */ 230 | @Override 231 | public void onTradeClosed() { 232 | // Cleanup and whatnot. 233 | } 234 | } 235 | -------------------------------------------------------------------------------- /SteamTrade-Example/src/main/java/com/nosoop/steamtradeexample/SteamClientExcerpt.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2014 nosoop < nosoop at users.noreply.github.com >. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package com.nosoop.steamtradeexample; 25 | 26 | import com.nosoop.steamtrade.TradeListener; 27 | import com.nosoop.steamtrade.TradeSession; 28 | import com.nosoop.steamtrade.inventory.AssetBuilder; 29 | import com.nosoop.steamtradeasset.ROT13F2AssetBuilder; 30 | import java.util.ArrayList; 31 | import java.util.HashMap; 32 | import java.util.List; 33 | import java.util.Map; 34 | 35 | /** 36 | * Excerpt snippet to show use of SteamTrade-Java. In this case, we receive a 37 | * notification to start a trade from somewhere and we use the provided 38 | * TradeListener instance to start a trade. 39 | * 40 | * @author nosoop < nosoop at users.noreply.github.com > 41 | */ 42 | public class SteamClientExcerpt { 43 | /** 44 | * To access the trading page, we must know the following... -- The SteamID 45 | * of our currently signed-in user (in long value format) -- The 46 | * Base64-encoded current session identifier from Steam. -- The Steam login 47 | * token used for Steam Web services. 48 | * 49 | * Again, you'll probably want to use a reverse-engineered Steam library to 50 | * access this information and the notification to know when a trade session 51 | * is starting. It's probably the only way, actually, so. Eh. 52 | */ 53 | long ourSteamId; 54 | String sessionid; 55 | String token; 56 | 57 | /** 58 | * Receives a callback notifying us that a trade has started. 59 | * 60 | * @param callback Callback with trade 61 | */ 62 | public void onNotifiedTradeStart(MockSessionStartCallback callback) { 63 | TradeListener listener = new SampleTradeListener(); 64 | TradeSession currentTrade; // The current trade. 65 | 66 | // Opens a new trade session to be handled by the given TradeListener. 67 | currentTrade = new TradeSession( 68 | ourSteamId, callback.tradePartnerSteamId, 69 | sessionid, token, listener); 70 | 71 | // Start a new thread in the background that polls the thread for updates. 72 | (new Thread(new TradePoller(currentTrade))).start(); 73 | } 74 | 75 | /** 76 | * Receives a callback notifying us that a trade has started. We want to use 77 | * custom asset builders (see com.nosoop.steamtradeasset). 78 | * 79 | * @param callback Callback signaling trade. 80 | */ 81 | public void onNotifiedTradeStartWithModifiedAssetBuilder(MockSessionStartCallback callback) { 82 | List assetBuilds = new ArrayList<>(); 83 | 84 | /** 85 | * Items from appid:440 (Team Fortress 2) will be read using the 86 | * ROT13F2AssetBuilder, which is just a silly asset builder that shifts 87 | * every item name by 13 characters. 88 | * 89 | * Given you have access to the individual items' JSON data, you can get 90 | * whatever info you need, pretty much. 91 | */ 92 | assetBuilds.add(new ROT13F2AssetBuilder()); 93 | 94 | TradeListener listener = new SampleTradeListener(); 95 | TradeSession currentTrade; // The current trade. 96 | 97 | // Added map at the end of the list. 98 | currentTrade = new TradeSession( 99 | ourSteamId, callback.tradePartnerSteamId, 100 | sessionid, token, listener, assetBuilds); 101 | 102 | // Start a new thread in the background that polls the thread for updates. 103 | (new Thread(new TradePoller(currentTrade))).start(); 104 | } 105 | } 106 | 107 | /** 108 | * Mock callback data containing the minimum amount to start a trade session. 109 | */ 110 | class MockSessionStartCallback { 111 | long tradePartnerSteamId; 112 | } 113 | 114 | /** 115 | * Runnable that runs in an infinite loop to check updates. Continues to poll 116 | * even after the trade has closed. So don't use this. 117 | */ 118 | class TradePoller implements Runnable { 119 | TradeSession session; 120 | 121 | public TradePoller(TradeSession session) { 122 | this.session = session; 123 | } 124 | 125 | @Override 126 | public void run() { 127 | while (true) { 128 | session.run(); 129 | try { 130 | Thread.sleep(1000); 131 | } catch (InterruptedException ex) { 132 | ex.printStackTrace(); 133 | } 134 | } 135 | } 136 | } -------------------------------------------------------------------------------- /SteamTrade-Java/nb-configuration.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | referer 11 | 12 | 13 | -------------------------------------------------------------------------------- /SteamTrade-Java/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | com.nosoop.steamtrade 6 | SteamTrade-Java 7 | 1.0-SNAPSHOT 8 | jar 9 | 10 | SteamTrade-Java 11 | http://maven.apache.org 12 | 13 | 14 | 15 | org.apache.maven.plugins 16 | maven-compiler-plugin 17 | 2.3.2 18 | 19 | 1.7 20 | 1.7 21 | 22 | 23 | 24 | 25 | 26 | UTF-8 27 | 28 | 29 | 30 | 31 | junit 32 | junit 33 | 3.8.1 34 | test 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /SteamTrade-Java/src/main/java/bundled/steamtrade/org/json/JSONArray.java: -------------------------------------------------------------------------------- 1 | package bundled.steamtrade.org.json; 2 | 3 | /* 4 | * Copyright (c) 2002 JSON.org 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * The Software shall be used for Good, not Evil. 17 | * 18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | * SOFTWARE. 25 | */ 26 | import java.io.IOException; 27 | import java.io.StringWriter; 28 | import java.io.Writer; 29 | import java.lang.reflect.Array; 30 | import java.util.ArrayList; 31 | import java.util.Collection; 32 | import java.util.Iterator; 33 | import java.util.Map; 34 | 35 | /** 36 | * A JSONArray is an ordered sequence of values. Its external text form is a 37 | * string wrapped in square brackets with commas separating the values. The 38 | * internal form is an object having 39 | * get and 40 | * opt methods for accessing the values by index, and 41 | * put methods for adding or replacing values. The values can be 42 | * any of these types: 43 | * Boolean, 44 | * JSONArray, 45 | * JSONObject, 46 | * Number, 47 | * String, or the 48 | * JSONObject.NULL object. 49 | *

50 | * The constructor can convert a JSON text into a Java object. The 51 | * toString method converts to JSON text. 52 | *

53 | * A 54 | * get method returns a value if one can be found, and throws an 55 | * exception if one cannot be found. An 56 | * opt method returns a default value instead of throwing an 57 | * exception, and so is useful for obtaining optional values. 58 | *

59 | * The generic 60 | * get() and 61 | * opt() methods return an object which you can cast or query for 62 | * type. There are also typed 63 | * get and 64 | * opt methods that do type checking and type coercion for you. 65 | *

66 | * The texts produced by the 67 | * toString methods strictly conform to JSON syntax rules. The 68 | * constructors are more forgiving in the texts they will accept: 69 | *

90 | * 91 | * @author JSON.org 92 | * @version 2012-11-13 93 | */ 94 | public class JSONArray { 95 | 96 | /** 97 | * The arrayList where the JSONArray's properties are kept. 98 | */ 99 | private final ArrayList myArrayList; 100 | 101 | /** 102 | * Construct an empty JSONArray. 103 | */ 104 | public JSONArray() { 105 | this.myArrayList = new ArrayList(); 106 | } 107 | 108 | /** 109 | * Construct a JSONArray from a JSONTokener. 110 | * 111 | * @param x A JSONTokener 112 | * @throws JSONException If there is a syntax error. 113 | */ 114 | public JSONArray(JSONTokener x) throws JSONException { 115 | this(); 116 | if (x.nextClean() != '[') { 117 | throw x.syntaxError("A JSONArray text must start with '['"); 118 | } 119 | if (x.nextClean() != ']') { 120 | x.back(); 121 | for (;;) { 122 | if (x.nextClean() == ',') { 123 | x.back(); 124 | this.myArrayList.add(JSONObject.NULL); 125 | } else { 126 | x.back(); 127 | this.myArrayList.add(x.nextValue()); 128 | } 129 | switch (x.nextClean()) { 130 | case ';': 131 | case ',': 132 | if (x.nextClean() == ']') { 133 | return; 134 | } 135 | x.back(); 136 | break; 137 | case ']': 138 | return; 139 | default: 140 | throw x.syntaxError("Expected a ',' or ']'"); 141 | } 142 | } 143 | } 144 | } 145 | 146 | /** 147 | * Construct a JSONArray from a source JSON text. 148 | * 149 | * @param source A string that begins with [ (left 150 | * bracket) 151 | * and ends with ] (right bracket). 152 | * @throws JSONException If there is a syntax error. 153 | */ 154 | public JSONArray(String source) throws JSONException { 155 | this(new JSONTokener(source)); 156 | } 157 | 158 | /** 159 | * Construct a JSONArray from a Collection. 160 | * 161 | * @param collection A Collection. 162 | */ 163 | public JSONArray(Collection collection) { 164 | this.myArrayList = new ArrayList(); 165 | if (collection != null) { 166 | Iterator iter = collection.iterator(); 167 | while (iter.hasNext()) { 168 | this.myArrayList.add(JSONObject.wrap(iter.next())); 169 | } 170 | } 171 | } 172 | 173 | /** 174 | * Construct a JSONArray from an array 175 | * 176 | * @throws JSONException If not an array. 177 | */ 178 | @SuppressWarnings("OverridableMethodCallInConstructor") 179 | public JSONArray(Object array) throws JSONException { 180 | this(); 181 | if (array.getClass().isArray()) { 182 | int length = Array.getLength(array); 183 | for (int i = 0; i < length; i += 1) { 184 | this.put(JSONObject.wrap(Array.get(array, i))); 185 | } 186 | } else { 187 | throw new JSONException( 188 | "JSONArray initial value should be a string or collection or array."); 189 | } 190 | } 191 | 192 | /** 193 | * Get the object value associated with an index. 194 | * 195 | * @param index The index must be between 0 and length() - 1. 196 | * @return An object value. 197 | * @throws JSONException If there is no value for the index. 198 | */ 199 | public Object get(int index) throws JSONException { 200 | Object object = this.opt(index); 201 | if (object == null) { 202 | throw new JSONException("JSONArray[" + index + "] not found."); 203 | } 204 | return object; 205 | } 206 | 207 | /** 208 | * Get the boolean value associated with an index. The string values "true" 209 | * and "false" are converted to boolean. 210 | * 211 | * @param index The index must be between 0 and length() - 1. 212 | * @return The truth. 213 | * @throws JSONException If there is no value for the index or if the value 214 | * is not convertible to boolean. 215 | */ 216 | public boolean getBoolean(int index) throws JSONException { 217 | Object object = this.get(index); 218 | if (object.equals(Boolean.FALSE) 219 | || (object instanceof String 220 | && ((String) object).equalsIgnoreCase("false"))) { 221 | return false; 222 | } else if (object.equals(Boolean.TRUE) 223 | || (object instanceof String 224 | && ((String) object).equalsIgnoreCase("true"))) { 225 | return true; 226 | } 227 | throw new JSONException("JSONArray[" + index + "] is not a boolean."); 228 | } 229 | 230 | /** 231 | * Get the double value associated with an index. 232 | * 233 | * @param index The index must be between 0 and length() - 1. 234 | * @return The value. 235 | * @throws JSONException If the key is not found or if the value cannot be 236 | * converted to a number. 237 | */ 238 | public double getDouble(int index) throws JSONException { 239 | Object object = this.get(index); 240 | try { 241 | return object instanceof Number 242 | ? ((Number) object).doubleValue() 243 | : Double.parseDouble((String) object); 244 | } catch (Exception e) { 245 | throw new JSONException("JSONArray[" + index 246 | + "] is not a number."); 247 | } 248 | } 249 | 250 | /** 251 | * Get the int value associated with an index. 252 | * 253 | * @param index The index must be between 0 and length() - 1. 254 | * @return The value. 255 | * @throws JSONException If the key is not found or if the value is not a 256 | * number. 257 | */ 258 | public int getInt(int index) throws JSONException { 259 | Object object = this.get(index); 260 | try { 261 | return object instanceof Number 262 | ? ((Number) object).intValue() 263 | : Integer.parseInt((String) object); 264 | } catch (Exception e) { 265 | throw new JSONException("JSONArray[" + index 266 | + "] is not a number."); 267 | } 268 | } 269 | 270 | /** 271 | * Get the JSONArray associated with an index. 272 | * 273 | * @param index The index must be between 0 and length() - 1. 274 | * @return A JSONArray value. 275 | * @throws JSONException If there is no value for the index. or if the value 276 | * is not a JSONArray 277 | */ 278 | public JSONArray getJSONArray(int index) throws JSONException { 279 | Object object = this.get(index); 280 | if (object instanceof JSONArray) { 281 | return (JSONArray) object; 282 | } 283 | throw new JSONException("JSONArray[" + index 284 | + "] is not a JSONArray."); 285 | } 286 | 287 | /** 288 | * Get the JSONObject associated with an index. 289 | * 290 | * @param index subscript 291 | * @return A JSONObject value. 292 | * @throws JSONException If there is no value for the index or if the value 293 | * is not a JSONObject 294 | */ 295 | public JSONObject getJSONObject(int index) throws JSONException { 296 | Object object = this.get(index); 297 | if (object instanceof JSONObject) { 298 | return (JSONObject) object; 299 | } 300 | throw new JSONException("JSONArray[" + index 301 | + "] is not a JSONObject."); 302 | } 303 | 304 | /** 305 | * Get the long value associated with an index. 306 | * 307 | * @param index The index must be between 0 and length() - 1. 308 | * @return The value. 309 | * @throws JSONException If the key is not found or if the value cannot be 310 | * converted to a number. 311 | */ 312 | public long getLong(int index) throws JSONException { 313 | Object object = this.get(index); 314 | try { 315 | return object instanceof Number 316 | ? ((Number) object).longValue() 317 | : Long.parseLong((String) object); 318 | } catch (Exception e) { 319 | throw new JSONException("JSONArray[" + index 320 | + "] is not a number."); 321 | } 322 | } 323 | 324 | /** 325 | * Get the string associated with an index. 326 | * 327 | * @param index The index must be between 0 and length() - 1. 328 | * @return A string value. 329 | * @throws JSONException If there is no string value for the index. 330 | */ 331 | public String getString(int index) throws JSONException { 332 | Object object = this.get(index); 333 | if (object instanceof String) { 334 | return (String) object; 335 | } 336 | throw new JSONException("JSONArray[" + index + "] not a string."); 337 | } 338 | 339 | /** 340 | * Determine if the value is null. 341 | * 342 | * @param index The index must be between 0 and length() - 1. 343 | * @return true if the value at the index is null, or if there is no value. 344 | */ 345 | public boolean isNull(int index) { 346 | return JSONObject.NULL.equals(this.opt(index)); 347 | } 348 | 349 | /** 350 | * Make a string from the contents of this JSONArray. The 351 | * separator string is inserted between each element. Warning: 352 | * This method assumes that the data structure is acyclical. 353 | * 354 | * @param separator A string that will be inserted between the elements. 355 | * @return a string. 356 | * @throws JSONException If the array contains an invalid number. 357 | */ 358 | public String join(String separator) throws JSONException { 359 | int len = this.length(); 360 | StringBuilder sb = new StringBuilder(); 361 | 362 | for (int i = 0; i < len; i += 1) { 363 | if (i > 0) { 364 | sb.append(separator); 365 | } 366 | sb.append(JSONObject.valueToString(this.myArrayList.get(i))); 367 | } 368 | return sb.toString(); 369 | } 370 | 371 | /** 372 | * Get the number of elements in the JSONArray, included nulls. 373 | * 374 | * @return The length (or size). 375 | */ 376 | public int length() { 377 | return this.myArrayList.size(); 378 | } 379 | 380 | /** 381 | * Get the optional object value associated with an index. 382 | * 383 | * @param index The index must be between 0 and length() - 1. 384 | * @return An object value, or null if there is no object at that index. 385 | */ 386 | public Object opt(int index) { 387 | return (index < 0 || index >= this.length()) 388 | ? null 389 | : this.myArrayList.get(index); 390 | } 391 | 392 | /** 393 | * Get the optional boolean value associated with an index. It returns false 394 | * if there is no value at that index, or if the value is not Boolean.TRUE 395 | * or the String "true". 396 | * 397 | * @param index The index must be between 0 and length() - 1. 398 | * @return The truth. 399 | */ 400 | public boolean optBoolean(int index) { 401 | return this.optBoolean(index, false); 402 | } 403 | 404 | /** 405 | * Get the optional boolean value associated with an index. It returns the 406 | * defaultValue if there is no value at that index or if it is not a Boolean 407 | * or the String "true" or "false" (case insensitive). 408 | * 409 | * @param index The index must be between 0 and length() - 1. 410 | * @param defaultValue A boolean default. 411 | * @return The truth. 412 | */ 413 | public boolean optBoolean(int index, boolean defaultValue) { 414 | try { 415 | return this.getBoolean(index); 416 | } catch (Exception e) { 417 | return defaultValue; 418 | } 419 | } 420 | 421 | /** 422 | * Get the optional double value associated with an index. NaN is returned 423 | * if there is no value for the index, or if the value is not a number and 424 | * cannot be converted to a number. 425 | * 426 | * @param index The index must be between 0 and length() - 1. 427 | * @return The value. 428 | */ 429 | public double optDouble(int index) { 430 | return this.optDouble(index, Double.NaN); 431 | } 432 | 433 | /** 434 | * Get the optional double value associated with an index. The defaultValue 435 | * is returned if there is no value for the index, or if the value is not a 436 | * number and cannot be converted to a number. 437 | * 438 | * @param index subscript 439 | * @param defaultValue The default value. 440 | * @return The value. 441 | */ 442 | public double optDouble(int index, double defaultValue) { 443 | try { 444 | return this.getDouble(index); 445 | } catch (Exception e) { 446 | return defaultValue; 447 | } 448 | } 449 | 450 | /** 451 | * Get the optional int value associated with an index. Zero is returned if 452 | * there is no value for the index, or if the value is not a number and 453 | * cannot be converted to a number. 454 | * 455 | * @param index The index must be between 0 and length() - 1. 456 | * @return The value. 457 | */ 458 | public int optInt(int index) { 459 | return this.optInt(index, 0); 460 | } 461 | 462 | /** 463 | * Get the optional int value associated with an index. The defaultValue is 464 | * returned if there is no value for the index, or if the value is not a 465 | * number and cannot be converted to a number. 466 | * 467 | * @param index The index must be between 0 and length() - 1. 468 | * @param defaultValue The default value. 469 | * @return The value. 470 | */ 471 | public int optInt(int index, int defaultValue) { 472 | try { 473 | return this.getInt(index); 474 | } catch (Exception e) { 475 | return defaultValue; 476 | } 477 | } 478 | 479 | /** 480 | * Get the optional JSONArray associated with an index. 481 | * 482 | * @param index subscript 483 | * @return A JSONArray value, or null if the index has no value, or if the 484 | * value is not a JSONArray. 485 | */ 486 | public JSONArray optJSONArray(int index) { 487 | Object o = this.opt(index); 488 | return o instanceof JSONArray ? (JSONArray) o : null; 489 | } 490 | 491 | /** 492 | * Get the optional JSONObject associated with an index. Null is returned if 493 | * the key is not found, or null if the index has no value, or if the value 494 | * is not a JSONObject. 495 | * 496 | * @param index The index must be between 0 and length() - 1. 497 | * @return A JSONObject value. 498 | */ 499 | public JSONObject optJSONObject(int index) { 500 | Object o = this.opt(index); 501 | return o instanceof JSONObject ? (JSONObject) o : null; 502 | } 503 | 504 | /** 505 | * Get the optional long value associated with an index. Zero is returned if 506 | * there is no value for the index, or if the value is not a number and 507 | * cannot be converted to a number. 508 | * 509 | * @param index The index must be between 0 and length() - 1. 510 | * @return The value. 511 | */ 512 | public long optLong(int index) { 513 | return this.optLong(index, 0); 514 | } 515 | 516 | /** 517 | * Get the optional long value associated with an index. The defaultValue is 518 | * returned if there is no value for the index, or if the value is not a 519 | * number and cannot be converted to a number. 520 | * 521 | * @param index The index must be between 0 and length() - 1. 522 | * @param defaultValue The default value. 523 | * @return The value. 524 | */ 525 | public long optLong(int index, long defaultValue) { 526 | try { 527 | return this.getLong(index); 528 | } catch (Exception e) { 529 | return defaultValue; 530 | } 531 | } 532 | 533 | /** 534 | * Get the optional string value associated with an index. It returns an 535 | * empty string if there is no value at that index. If the value is not a 536 | * string and is not null, then it is coverted to a string. 537 | * 538 | * @param index The index must be between 0 and length() - 1. 539 | * @return A String value. 540 | */ 541 | public String optString(int index) { 542 | return this.optString(index, ""); 543 | } 544 | 545 | /** 546 | * Get the optional string associated with an index. The defaultValue is 547 | * returned if the key is not found. 548 | * 549 | * @param index The index must be between 0 and length() - 1. 550 | * @param defaultValue The default value. 551 | * @return A String value. 552 | */ 553 | public String optString(int index, String defaultValue) { 554 | Object object = this.opt(index); 555 | return JSONObject.NULL.equals(object) 556 | ? defaultValue 557 | : object.toString(); 558 | } 559 | 560 | /** 561 | * Append a boolean value. This increases the array's length by one. 562 | * 563 | * @param value A boolean value. 564 | * @return this. 565 | */ 566 | public JSONArray put(boolean value) { 567 | this.put(value ? Boolean.TRUE : Boolean.FALSE); 568 | return this; 569 | } 570 | 571 | /** 572 | * Put a value in the JSONArray, where the value will be a JSONArray which 573 | * is produced from a Collection. 574 | * 575 | * @param value A Collection value. 576 | * @return this. 577 | */ 578 | public JSONArray put(Collection value) { 579 | this.put(new JSONArray(value)); 580 | return this; 581 | } 582 | 583 | /** 584 | * Append a double value. This increases the array's length by one. 585 | * 586 | * @param value A double value. 587 | * @throws JSONException if the value is not finite. 588 | * @return this. 589 | */ 590 | public JSONArray put(double value) throws JSONException { 591 | Double d = Double.valueOf(value); 592 | JSONObject.testValidity(d); 593 | this.put(d); 594 | return this; 595 | } 596 | 597 | /** 598 | * Append an int value. This increases the array's length by one. 599 | * 600 | * @param value An int value. 601 | * @return this. 602 | */ 603 | public JSONArray put(int value) { 604 | this.put(Integer.valueOf(value)); 605 | return this; 606 | } 607 | 608 | /** 609 | * Append an long value. This increases the array's length by one. 610 | * 611 | * @param value A long value. 612 | * @return this. 613 | */ 614 | public JSONArray put(long value) { 615 | this.put(Long.valueOf(value)); 616 | return this; 617 | } 618 | 619 | /** 620 | * Put a value in the JSONArray, where the value will be a JSONObject which 621 | * is produced from a Map. 622 | * 623 | * @param value A Map value. 624 | * @return this. 625 | */ 626 | public JSONArray put(Map value) { 627 | this.put(new JSONObject(value)); 628 | return this; 629 | } 630 | 631 | /** 632 | * Append an object value. This increases the array's length by one. 633 | * 634 | * @param value An object value. The value should be a Boolean, Double, 635 | * Integer, JSONArray, JSONObject, Long, or String, or the JSONObject.NULL 636 | * object. 637 | * @return this. 638 | */ 639 | public JSONArray put(Object value) { 640 | this.myArrayList.add(value); 641 | return this; 642 | } 643 | 644 | /** 645 | * Put or replace a boolean value in the JSONArray. If the index is greater 646 | * than the length of the JSONArray, then null elements will be added as 647 | * necessary to pad it out. 648 | * 649 | * @param index The subscript. 650 | * @param value A boolean value. 651 | * @return this. 652 | * @throws JSONException If the index is negative. 653 | */ 654 | public JSONArray put(int index, boolean value) throws JSONException { 655 | this.put(index, value ? Boolean.TRUE : Boolean.FALSE); 656 | return this; 657 | } 658 | 659 | /** 660 | * Put a value in the JSONArray, where the value will be a JSONArray which 661 | * is produced from a Collection. 662 | * 663 | * @param index The subscript. 664 | * @param value A Collection value. 665 | * @return this. 666 | * @throws JSONException If the index is negative or if the value is not 667 | * finite. 668 | */ 669 | public JSONArray put(int index, Collection value) throws JSONException { 670 | this.put(index, new JSONArray(value)); 671 | return this; 672 | } 673 | 674 | /** 675 | * Put or replace a double value. If the index is greater than the length of 676 | * the JSONArray, then null elements will be added as necessary to pad it 677 | * out. 678 | * 679 | * @param index The subscript. 680 | * @param value A double value. 681 | * @return this. 682 | * @throws JSONException If the index is negative or if the value is not 683 | * finite. 684 | */ 685 | public JSONArray put(int index, double value) throws JSONException { 686 | this.put(index, Double.valueOf(value)); 687 | return this; 688 | } 689 | 690 | /** 691 | * Put or replace an int value. If the index is greater than the length of 692 | * the JSONArray, then null elements will be added as necessary to pad it 693 | * out. 694 | * 695 | * @param index The subscript. 696 | * @param value An int value. 697 | * @return this. 698 | * @throws JSONException If the index is negative. 699 | */ 700 | public JSONArray put(int index, int value) throws JSONException { 701 | this.put(index, Integer.valueOf(value)); 702 | return this; 703 | } 704 | 705 | /** 706 | * Put or replace a long value. If the index is greater than the length of 707 | * the JSONArray, then null elements will be added as necessary to pad it 708 | * out. 709 | * 710 | * @param index The subscript. 711 | * @param value A long value. 712 | * @return this. 713 | * @throws JSONException If the index is negative. 714 | */ 715 | public JSONArray put(int index, long value) throws JSONException { 716 | this.put(index, Long.valueOf(value)); 717 | return this; 718 | } 719 | 720 | /** 721 | * Put a value in the JSONArray, where the value will be a JSONObject that 722 | * is produced from a Map. 723 | * 724 | * @param index The subscript. 725 | * @param value The Map value. 726 | * @return this. 727 | * @throws JSONException If the index is negative or if the the value is an 728 | * invalid number. 729 | */ 730 | public JSONArray put(int index, Map value) throws JSONException { 731 | this.put(index, new JSONObject(value)); 732 | return this; 733 | } 734 | 735 | /** 736 | * Put or replace an object value in the JSONArray. If the index is greater 737 | * than the length of the JSONArray, then null elements will be added as 738 | * necessary to pad it out. 739 | * 740 | * @param index The subscript. 741 | * @param value The value to put into the array. The value should be a 742 | * Boolean, Double, Integer, JSONArray, JSONObject, Long, or String, or the 743 | * JSONObject.NULL object. 744 | * @return this. 745 | * @throws JSONException If the index is negative or if the the value is an 746 | * invalid number. 747 | */ 748 | public JSONArray put(int index, Object value) throws JSONException { 749 | JSONObject.testValidity(value); 750 | if (index < 0) { 751 | throw new JSONException("JSONArray[" + index + "] not found."); 752 | } 753 | if (index < this.length()) { 754 | this.myArrayList.set(index, value); 755 | } else { 756 | while (index != this.length()) { 757 | this.put(JSONObject.NULL); 758 | } 759 | this.put(value); 760 | } 761 | return this; 762 | } 763 | 764 | /** 765 | * Remove an index and close the hole. 766 | * 767 | * @param index The index of the element to be removed. 768 | * @return The value that was associated with the index, or null if there 769 | * was no value. 770 | */ 771 | public Object remove(int index) { 772 | Object o = this.opt(index); 773 | this.myArrayList.remove(index); 774 | return o; 775 | } 776 | 777 | /** 778 | * Produce a JSONObject by combining a JSONArray of names with the values of 779 | * this JSONArray. 780 | * 781 | * @param names A JSONArray containing a list of key strings. These will be 782 | * paired with the values. 783 | * @return A JSONObject, or null if there are no names or if this JSONArray 784 | * has no values. 785 | * @throws JSONException If any of the names are null. 786 | */ 787 | public JSONObject toJSONObject(JSONArray names) throws JSONException { 788 | if (names == null || names.length() == 0 || this.length() == 0) { 789 | return null; 790 | } 791 | JSONObject jo = new JSONObject(); 792 | for (int i = 0; i < names.length(); i += 1) { 793 | jo.put(names.getString(i), this.opt(i)); 794 | } 795 | return jo; 796 | } 797 | 798 | /** 799 | * Make a JSON text of this JSONArray. For compactness, no unnecessary 800 | * whitespace is added. If it is not possible to produce a syntactically 801 | * correct JSON text then null will be returned instead. This could occur if 802 | * the array contains an invalid number. 803 | *

804 | * Warning: This method assumes that the data structure is acyclical. 805 | * 806 | * @return a printable, displayable, transmittable representation of the 807 | * array. 808 | */ 809 | @Override 810 | public String toString() { 811 | try { 812 | return this.toString(0); 813 | } catch (Exception e) { 814 | return null; 815 | } 816 | } 817 | 818 | /** 819 | * Make a prettyprinted JSON text of this JSONArray. Warning: This method 820 | * assumes that the data structure is acyclical. 821 | * 822 | * @param indentFactor The number of spaces to add to each level of 823 | * indentation. 824 | * @return a printable, displayable, transmittable representation of the 825 | * object, beginning with [ (left bracket) 826 | * and ending with ] (right bracket). 827 | * @throws JSONException 828 | */ 829 | public String toString(int indentFactor) throws JSONException { 830 | StringWriter sw = new StringWriter(); 831 | synchronized (sw.getBuffer()) { 832 | return this.write(sw, indentFactor, 0).toString(); 833 | } 834 | } 835 | 836 | /** 837 | * Write the contents of the JSONArray as JSON text to a writer. For 838 | * compactness, no whitespace is added. 839 | *

840 | * Warning: This method assumes that the data structure is acyclical. 841 | * 842 | * @return The writer. 843 | * @throws JSONException 844 | */ 845 | public Writer write(Writer writer) throws JSONException { 846 | return this.write(writer, 0, 0); 847 | } 848 | 849 | /** 850 | * Write the contents of the JSONArray as JSON text to a writer. For 851 | * compactness, no whitespace is added. 852 | *

853 | * Warning: This method assumes that the data structure is acyclical. 854 | * 855 | * @param indentFactor The number of spaces to add to each level of 856 | * indentation. 857 | * @param indent The indention of the top level. 858 | * @return The writer. 859 | * @throws JSONException 860 | */ 861 | Writer write(Writer writer, int indentFactor, int indent) 862 | throws JSONException { 863 | try { 864 | boolean commanate = false; 865 | int length = this.length(); 866 | writer.write('['); 867 | 868 | if (length == 1) { 869 | JSONObject.writeValue(writer, this.myArrayList.get(0), 870 | indentFactor, indent); 871 | } else if (length != 0) { 872 | final int newindent = indent + indentFactor; 873 | 874 | for (int i = 0; i < length; i += 1) { 875 | if (commanate) { 876 | writer.write(','); 877 | } 878 | if (indentFactor > 0) { 879 | writer.write('\n'); 880 | } 881 | JSONObject.indent(writer, newindent); 882 | JSONObject.writeValue(writer, this.myArrayList.get(i), 883 | indentFactor, newindent); 884 | commanate = true; 885 | } 886 | if (indentFactor > 0) { 887 | writer.write('\n'); 888 | } 889 | JSONObject.indent(writer, indent); 890 | } 891 | writer.write(']'); 892 | return writer; 893 | } catch (IOException e) { 894 | throw new JSONException(e); 895 | } 896 | } 897 | } 898 | -------------------------------------------------------------------------------- /SteamTrade-Java/src/main/java/bundled/steamtrade/org/json/JSONException.java: -------------------------------------------------------------------------------- 1 | package bundled.steamtrade.org.json; 2 | 3 | /** 4 | * The JSONException is thrown by the JSON.org classes when things are amiss. 5 | * 6 | * @author JSON.org 7 | * @version 2010-12-24 8 | */ 9 | public class JSONException extends Exception { 10 | 11 | private static final long serialVersionUID = 0; 12 | private Throwable cause; 13 | 14 | /** 15 | * Constructs a JSONException with an explanatory message. 16 | * 17 | * @param message Detail about the reason for the exception. 18 | */ 19 | public JSONException(String message) { 20 | super(message); 21 | } 22 | 23 | public JSONException(Throwable cause) { 24 | super(cause.getMessage()); 25 | this.cause = cause; 26 | } 27 | 28 | @Override 29 | public Throwable getCause() { 30 | return this.cause; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /SteamTrade-Java/src/main/java/bundled/steamtrade/org/json/JSONString.java: -------------------------------------------------------------------------------- 1 | package bundled.steamtrade.org.json; 2 | 3 | /** 4 | * The 5 | * JSONString interface allows a 6 | * toJSONString() method so that a class can change the behavior of 7 | * JSONObject.toString(), 8 | * JSONArray.toString(), and 9 | * JSONWriter.value(Object). The 10 | * toJSONString method will be used instead of the default behavior 11 | * of using the Object's 12 | * toString() method and quoting the result. 13 | */ 14 | public interface JSONString { 15 | 16 | /** 17 | * The 18 | * toJSONString method allows a class to produce its own JSON 19 | * serialization. 20 | * 21 | * @return A strictly syntactically correct JSON text. 22 | */ 23 | public String toJSONString(); 24 | } 25 | -------------------------------------------------------------------------------- /SteamTrade-Java/src/main/java/bundled/steamtrade/org/json/JSONStringer.java: -------------------------------------------------------------------------------- 1 | package bundled.steamtrade.org.json; 2 | 3 | /* 4 | * Copyright (c) 2006 JSON.org 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * The Software shall be used for Good, not Evil. 17 | * 18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | * SOFTWARE. 25 | */ 26 | import java.io.StringWriter; 27 | 28 | /** 29 | * JSONStringer provides a quick and convenient way of producing JSON text. The 30 | * texts produced strictly conform to JSON syntax rules. No whitespace is added, 31 | * so the results are ready for transmission or storage. Each instance of 32 | * JSONStringer can produce one JSON text. 33 | *

34 | * A JSONStringer instance provides a 35 | * value method for appending values to the text, and a 36 | * key method for adding keys before values in objects. There are 37 | * array and 38 | * endArray methods that make and bound array values, and 39 | * object and 40 | * endObject methods which make and bound object values. All of 41 | * these methods return the JSONWriter instance, permitting cascade style. For 42 | * example, 43 | *

44 |  * myString = new JSONStringer()
45 |  *     .object()
46 |  *         .key("JSON")
47 |  *         .value("Hello, World!")
48 |  *     .endObject()
49 |  *     .toString();
which produces the string 50 | *
51 |  * {"JSON":"Hello, World!"}
52 | *

53 | * The first method called must be 54 | * array or 55 | * object. There are no methods for adding commas or colons. 56 | * JSONStringer adds them for you. Objects and arrays can be nested up to 20 57 | * levels deep. 58 | *

59 | * This can sometimes be easier than using a JSONObject to build a string. 60 | * 61 | * @author JSON.org 62 | * @version 2008-09-18 63 | */ 64 | public class JSONStringer extends JSONWriter { 65 | 66 | /** 67 | * Make a fresh JSONStringer. It can be used to build one JSON text. 68 | */ 69 | public JSONStringer() { 70 | super(new StringWriter()); 71 | } 72 | 73 | /** 74 | * Return the JSON text. This method is used to obtain the product of the 75 | * JSONStringer instance. It will return 76 | * null if there was a problem in the construction of the JSON 77 | * text (such as the calls to 78 | * array were not properly balanced with calls to 79 | * endArray). 80 | * 81 | * @return The JSON text. 82 | */ 83 | @Override 84 | public String toString() { 85 | return this.mode == 'd' ? this.writer.toString() : null; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /SteamTrade-Java/src/main/java/bundled/steamtrade/org/json/JSONTokener.java: -------------------------------------------------------------------------------- 1 | package bundled.steamtrade.org.json; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.IOException; 5 | import java.io.InputStream; 6 | import java.io.InputStreamReader; 7 | import java.io.Reader; 8 | import java.io.StringReader; 9 | 10 | /* 11 | * Copyright (c) 2002 JSON.org 12 | * 13 | * Permission is hereby granted, free of charge, to any person obtaining a copy 14 | * of this software and associated documentation files (the "Software"), to deal 15 | * in the Software without restriction, including without limitation the rights 16 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 17 | * copies of the Software, and to permit persons to whom the Software is 18 | * furnished to do so, subject to the following conditions: 19 | * 20 | * The above copyright notice and this permission notice shall be included in 21 | * all copies or substantial portions of the Software. 22 | * 23 | * The Software shall be used for Good, not Evil. 24 | * 25 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 26 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 27 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 28 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 29 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 30 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 31 | * SOFTWARE. 32 | */ 33 | /** 34 | * A JSONTokener takes a source string and extracts characters and tokens from 35 | * it. It is used by the JSONObject and JSONArray constructors to parse JSON 36 | * source strings. 37 | * 38 | * @author JSON.org 39 | * @version 2012-02-16 40 | */ 41 | public class JSONTokener { 42 | 43 | private long character; 44 | private boolean eof; 45 | private long index; 46 | private long line; 47 | private char previous; 48 | private Reader reader; 49 | private boolean usePrevious; 50 | 51 | /** 52 | * Construct a JSONTokener from a Reader. 53 | * 54 | * @param reader A reader. 55 | */ 56 | public JSONTokener(Reader reader) { 57 | this.reader = reader.markSupported() 58 | ? reader 59 | : new BufferedReader(reader); 60 | this.eof = false; 61 | this.usePrevious = false; 62 | this.previous = 0; 63 | this.index = 0; 64 | this.character = 1; 65 | this.line = 1; 66 | } 67 | 68 | /** 69 | * Construct a JSONTokener from an InputStream. 70 | */ 71 | public JSONTokener(InputStream inputStream) throws JSONException { 72 | this(new InputStreamReader(inputStream)); 73 | } 74 | 75 | /** 76 | * Construct a JSONTokener from a string. 77 | * 78 | * @param s A source string. 79 | */ 80 | public JSONTokener(String s) { 81 | this(new StringReader(s)); 82 | } 83 | 84 | /** 85 | * Back up one character. This provides a sort of lookahead capability, so 86 | * that you can test for a digit or letter before attempting to parse the 87 | * next number or identifier. 88 | */ 89 | public void back() throws JSONException { 90 | if (this.usePrevious || this.index <= 0) { 91 | throw new JSONException("Stepping back two steps is not supported"); 92 | } 93 | this.index -= 1; 94 | this.character -= 1; 95 | this.usePrevious = true; 96 | this.eof = false; 97 | } 98 | 99 | /** 100 | * Get the hex value of a character (base16). 101 | * 102 | * @param c A character between '0' and '9' or between 'A' and 'F' or 103 | * between 'a' and 'f'. 104 | * @return An int between 0 and 15, or -1 if c was not a hex digit. 105 | */ 106 | public static int dehexchar(char c) { 107 | if (c >= '0' && c <= '9') { 108 | return c - '0'; 109 | } 110 | if (c >= 'A' && c <= 'F') { 111 | return c - ('A' - 10); 112 | } 113 | if (c >= 'a' && c <= 'f') { 114 | return c - ('a' - 10); 115 | } 116 | return -1; 117 | } 118 | 119 | public boolean end() { 120 | return this.eof && !this.usePrevious; 121 | } 122 | 123 | /** 124 | * Determine if the source string still contains characters that next() can 125 | * consume. 126 | * 127 | * @return true if not yet at the end of the source. 128 | */ 129 | public boolean more() throws JSONException { 130 | this.next(); 131 | if (this.end()) { 132 | return false; 133 | } 134 | this.back(); 135 | return true; 136 | } 137 | 138 | /** 139 | * Get the next character in the source string. 140 | * 141 | * @return The next character, or 0 if past the end of the source string. 142 | */ 143 | public char next() throws JSONException { 144 | int c; 145 | if (this.usePrevious) { 146 | this.usePrevious = false; 147 | c = this.previous; 148 | } else { 149 | try { 150 | c = this.reader.read(); 151 | } catch (IOException exception) { 152 | throw new JSONException(exception); 153 | } 154 | 155 | if (c <= 0) { // End of stream 156 | this.eof = true; 157 | c = 0; 158 | } 159 | } 160 | this.index += 1; 161 | if (this.previous == '\r') { 162 | this.line += 1; 163 | this.character = c == '\n' ? 0 : 1; 164 | } else if (c == '\n') { 165 | this.line += 1; 166 | this.character = 0; 167 | } else { 168 | this.character += 1; 169 | } 170 | this.previous = (char) c; 171 | return this.previous; 172 | } 173 | 174 | /** 175 | * Consume the next character, and check that it matches a specified 176 | * character. 177 | * 178 | * @param c The character to match. 179 | * @return The character. 180 | * @throws JSONException if the character does not match. 181 | */ 182 | public char next(char c) throws JSONException { 183 | char n = this.next(); 184 | if (n != c) { 185 | throw this.syntaxError("Expected '" + c + "' and instead saw '" 186 | + n + "'"); 187 | } 188 | return n; 189 | } 190 | 191 | /** 192 | * Get the next n characters. 193 | * 194 | * @param n The number of characters to take. 195 | * @return A string of n characters. 196 | * @throws JSONException Substring bounds error if there are not n 197 | * characters remaining in the source string. 198 | */ 199 | public String next(int n) throws JSONException { 200 | if (n == 0) { 201 | return ""; 202 | } 203 | 204 | char[] chars = new char[n]; 205 | int pos = 0; 206 | 207 | while (pos < n) { 208 | chars[pos] = this.next(); 209 | if (this.end()) { 210 | throw this.syntaxError("Substring bounds error"); 211 | } 212 | pos += 1; 213 | } 214 | return new String(chars); 215 | } 216 | 217 | /** 218 | * Get the next char in the string, skipping whitespace. 219 | * 220 | * @throws JSONException 221 | * @return A character, or 0 if there are no more characters. 222 | */ 223 | public char nextClean() throws JSONException { 224 | for (;;) { 225 | char c = this.next(); 226 | if (c == 0 || c > ' ') { 227 | return c; 228 | } 229 | } 230 | } 231 | 232 | /** 233 | * Return the characters up to the next close quote character. Backslash 234 | * processing is done. The formal JSON format does not allow strings in 235 | * single quotes, but an implementation is allowed to accept them. 236 | * 237 | * @param quote The quoting character, either 238 | * " (double quote) or 239 | * ' (single quote). 240 | * @return A String. 241 | * @throws JSONException Unterminated string. 242 | */ 243 | public String nextString(char quote) throws JSONException { 244 | char c; 245 | StringBuilder sb = new StringBuilder(); 246 | for (;;) { 247 | c = this.next(); 248 | switch (c) { 249 | case 0: 250 | case '\n': 251 | case '\r': 252 | throw this.syntaxError("Unterminated string"); 253 | case '\\': 254 | c = this.next(); 255 | switch (c) { 256 | case 'b': 257 | sb.append('\b'); 258 | break; 259 | case 't': 260 | sb.append('\t'); 261 | break; 262 | case 'n': 263 | sb.append('\n'); 264 | break; 265 | case 'f': 266 | sb.append('\f'); 267 | break; 268 | case 'r': 269 | sb.append('\r'); 270 | break; 271 | case 'u': 272 | sb.append((char) Integer.parseInt(this.next(4), 16)); 273 | break; 274 | case '"': 275 | case '\'': 276 | case '\\': 277 | case '/': 278 | sb.append(c); 279 | break; 280 | default: 281 | throw this.syntaxError("Illegal escape."); 282 | } 283 | break; 284 | default: 285 | if (c == quote) { 286 | return sb.toString(); 287 | } 288 | sb.append(c); 289 | } 290 | } 291 | } 292 | 293 | /** 294 | * Get the text up but not including the specified character or the end of 295 | * line, whichever comes first. 296 | * 297 | * @param delimiter A delimiter character. 298 | * @return A string. 299 | */ 300 | public String nextTo(char delimiter) throws JSONException { 301 | StringBuilder sb = new StringBuilder(); 302 | for (;;) { 303 | char c = this.next(); 304 | if (c == delimiter || c == 0 || c == '\n' || c == '\r') { 305 | if (c != 0) { 306 | this.back(); 307 | } 308 | return sb.toString().trim(); 309 | } 310 | sb.append(c); 311 | } 312 | } 313 | 314 | /** 315 | * Get the text up but not including one of the specified delimiter 316 | * characters or the end of line, whichever comes first. 317 | * 318 | * @param delimiters A set of delimiter characters. 319 | * @return A string, trimmed. 320 | */ 321 | public String nextTo(String delimiters) throws JSONException { 322 | char c; 323 | StringBuilder sb = new StringBuilder(); 324 | for (;;) { 325 | c = this.next(); 326 | if (delimiters.indexOf(c) >= 0 || c == 0 327 | || c == '\n' || c == '\r') { 328 | if (c != 0) { 329 | this.back(); 330 | } 331 | return sb.toString().trim(); 332 | } 333 | sb.append(c); 334 | } 335 | } 336 | 337 | /** 338 | * Get the next value. The value can be a Boolean, Double, Integer, 339 | * JSONArray, JSONObject, Long, or String, or the JSONObject.NULL object. 340 | * 341 | * @throws JSONException If syntax error. 342 | * 343 | * @return An object. 344 | */ 345 | public Object nextValue() throws JSONException { 346 | char c = this.nextClean(); 347 | String string; 348 | 349 | switch (c) { 350 | case '"': 351 | case '\'': 352 | return this.nextString(c); 353 | case '{': 354 | this.back(); 355 | return new JSONObject(this); 356 | case '[': 357 | this.back(); 358 | return new JSONArray(this); 359 | } 360 | 361 | /* 362 | * Handle unquoted text. This could be the values true, false, or null, 363 | * or it can be a number. An implementation (such as this one) is 364 | * allowed to also accept non-standard forms. 365 | * 366 | * Accumulate characters until we reach the end of the text or a 367 | * formatting character. 368 | */ 369 | 370 | StringBuilder sb = new StringBuilder(); 371 | while (c >= ' ' && ",:]}/\\\"[{;=#".indexOf(c) < 0) { 372 | sb.append(c); 373 | c = this.next(); 374 | } 375 | this.back(); 376 | 377 | string = sb.toString().trim(); 378 | if ("".equals(string)) { 379 | throw this.syntaxError("Missing value"); 380 | } 381 | return JSONObject.stringToValue(string); 382 | } 383 | 384 | /** 385 | * Skip characters until the next character is the requested character. If 386 | * the requested character is not found, no characters are skipped. 387 | * 388 | * @param to A character to skip to. 389 | * @return The requested character, or zero if the requested character is 390 | * not found. 391 | */ 392 | public char skipTo(char to) throws JSONException { 393 | char c; 394 | try { 395 | long startIndex = this.index; 396 | long startCharacter = this.character; 397 | long startLine = this.line; 398 | this.reader.mark(1000000); 399 | do { 400 | c = this.next(); 401 | if (c == 0) { 402 | this.reader.reset(); 403 | this.index = startIndex; 404 | this.character = startCharacter; 405 | this.line = startLine; 406 | return c; 407 | } 408 | } while (c != to); 409 | } catch (IOException exc) { 410 | throw new JSONException(exc); 411 | } 412 | 413 | this.back(); 414 | return c; 415 | } 416 | 417 | /** 418 | * Make a JSONException to signal a syntax error. 419 | * 420 | * @param message The error message. 421 | * @return A JSONException object, suitable for throwing 422 | */ 423 | public JSONException syntaxError(String message) { 424 | return new JSONException(message + this.toString()); 425 | } 426 | 427 | /** 428 | * Make a printable string of this JSONTokener. 429 | * 430 | * @return " at {index} [character {character} line {line}]" 431 | */ 432 | @Override 433 | public String toString() { 434 | return " at " + this.index + " [character " + this.character + " line " 435 | + this.line + "]"; 436 | } 437 | } 438 | -------------------------------------------------------------------------------- /SteamTrade-Java/src/main/java/bundled/steamtrade/org/json/JSONWriter.java: -------------------------------------------------------------------------------- 1 | package bundled.steamtrade.org.json; 2 | 3 | import java.io.IOException; 4 | import java.io.Writer; 5 | 6 | /* 7 | * Copyright (c) 2006 JSON.org 8 | * 9 | * Permission is hereby granted, free of charge, to any person obtaining a copy 10 | * of this software and associated documentation files (the "Software"), to deal 11 | * in the Software without restriction, including without limitation the rights 12 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | * copies of the Software, and to permit persons to whom the Software is 14 | * furnished to do so, subject to the following conditions: 15 | * 16 | * The above copyright notice and this permission notice shall be included in 17 | * all copies or substantial portions of the Software. 18 | * 19 | * The Software shall be used for Good, not Evil. 20 | * 21 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 27 | * SOFTWARE. 28 | */ 29 | /** 30 | * JSONWriter provides a quick and convenient way of producing JSON text. The 31 | * texts produced strictly conform to JSON syntax rules. No whitespace is added, 32 | * so the results are ready for transmission or storage. Each instance of 33 | * JSONWriter can produce one JSON text. 34 | *

35 | * A JSONWriter instance provides a 36 | * value method for appending values to the text, and a 37 | * key method for adding keys before values in objects. There are 38 | * array and 39 | * endArray methods that make and bound array values, and 40 | * object and 41 | * endObject methods which make and bound object values. All of 42 | * these methods return the JSONWriter instance, permitting a cascade style. For 43 | * example, 44 | *

 45 |  * new JSONWriter(myWriter)
 46 |  *     .object()
 47 |  *         .key("JSON")
 48 |  *         .value("Hello, World!")
 49 |  *     .endObject();
which writes 50 | *
 51 |  * {"JSON":"Hello, World!"}
52 | *

53 | * The first method called must be 54 | * array or 55 | * object. There are no methods for adding commas or colons. 56 | * JSONWriter adds them for you. Objects and arrays can be nested up to 20 57 | * levels deep. 58 | *

59 | * This can sometimes be easier than using a JSONObject to build a string. 60 | * 61 | * @author JSON.org 62 | * @version 2011-11-24 63 | */ 64 | public class JSONWriter { 65 | 66 | private static final int maxdepth = 200; 67 | /** 68 | * The comma flag determines if a comma should be output before the next 69 | * value. 70 | */ 71 | private boolean comma; 72 | /** 73 | * The current mode. Values: 'a' (array), 'd' (done), 'i' (initial), 'k' 74 | * (key), 'o' (object). 75 | */ 76 | protected char mode; 77 | /** 78 | * The object/array stack. 79 | */ 80 | private final JSONObject stack[]; 81 | /** 82 | * The stack top index. A value of 0 indicates that the stack is empty. 83 | */ 84 | private int top; 85 | /** 86 | * The writer that will receive the output. 87 | */ 88 | protected Writer writer; 89 | 90 | /** 91 | * Make a fresh JSONWriter. It can be used to build one JSON text. 92 | */ 93 | public JSONWriter(Writer w) { 94 | this.comma = false; 95 | this.mode = 'i'; 96 | this.stack = new JSONObject[maxdepth]; 97 | this.top = 0; 98 | this.writer = w; 99 | } 100 | 101 | /** 102 | * Append a value. 103 | * 104 | * @param string A string value. 105 | * @return this 106 | * @throws JSONException If the value is out of sequence. 107 | */ 108 | private JSONWriter append(String string) throws JSONException { 109 | if (string == null) { 110 | throw new JSONException("Null pointer"); 111 | } 112 | if (this.mode == 'o' || this.mode == 'a') { 113 | try { 114 | if (this.comma && this.mode == 'a') { 115 | this.writer.write(','); 116 | } 117 | this.writer.write(string); 118 | } catch (IOException e) { 119 | throw new JSONException(e); 120 | } 121 | if (this.mode == 'o') { 122 | this.mode = 'k'; 123 | } 124 | this.comma = true; 125 | return this; 126 | } 127 | throw new JSONException("Value out of sequence."); 128 | } 129 | 130 | /** 131 | * Begin appending a new array. All values until the balancing 132 | * endArray will be appended to this array. The 133 | * endArray method must be called to mark the array's end. 134 | * 135 | * @return this 136 | * @throws JSONException If the nesting is too deep, or if the object is 137 | * started in the wrong place (for example as a key or after the end of the 138 | * outermost array or object). 139 | */ 140 | public JSONWriter array() throws JSONException { 141 | if (this.mode == 'i' || this.mode == 'o' || this.mode == 'a') { 142 | this.push(null); 143 | this.append("["); 144 | this.comma = false; 145 | return this; 146 | } 147 | throw new JSONException("Misplaced array."); 148 | } 149 | 150 | /** 151 | * End something. 152 | * 153 | * @param mode Mode 154 | * @param c Closing character 155 | * @return this 156 | * @throws JSONException If unbalanced. 157 | */ 158 | private JSONWriter end(char mode, char c) throws JSONException { 159 | if (this.mode != mode) { 160 | throw new JSONException(mode == 'a' 161 | ? "Misplaced endArray." 162 | : "Misplaced endObject."); 163 | } 164 | this.pop(mode); 165 | try { 166 | this.writer.write(c); 167 | } catch (IOException e) { 168 | throw new JSONException(e); 169 | } 170 | this.comma = true; 171 | return this; 172 | } 173 | 174 | /** 175 | * End an array. This method most be called to balance calls to 176 | * array. 177 | * 178 | * @return this 179 | * @throws JSONException If incorrectly nested. 180 | */ 181 | public JSONWriter endArray() throws JSONException { 182 | return this.end('a', ']'); 183 | } 184 | 185 | /** 186 | * End an object. This method most be called to balance calls to 187 | * object. 188 | * 189 | * @return this 190 | * @throws JSONException If incorrectly nested. 191 | */ 192 | public JSONWriter endObject() throws JSONException { 193 | return this.end('k', '}'); 194 | } 195 | 196 | /** 197 | * Append a key. The key will be associated with the next value. In an 198 | * object, every value must be preceded by a key. 199 | * 200 | * @param string A key string. 201 | * @return this 202 | * @throws JSONException If the key is out of place. For example, keys do 203 | * not belong in arrays or if the key is null. 204 | */ 205 | public JSONWriter key(String string) throws JSONException { 206 | if (string == null) { 207 | throw new JSONException("Null key."); 208 | } 209 | if (this.mode == 'k') { 210 | try { 211 | this.stack[this.top - 1].putOnce(string, Boolean.TRUE); 212 | if (this.comma) { 213 | this.writer.write(','); 214 | } 215 | this.writer.write(JSONObject.quote(string)); 216 | this.writer.write(':'); 217 | this.comma = false; 218 | this.mode = 'o'; 219 | return this; 220 | } catch (IOException e) { 221 | throw new JSONException(e); 222 | } 223 | } 224 | throw new JSONException("Misplaced key."); 225 | } 226 | 227 | /** 228 | * Begin appending a new object. All keys and values until the balancing 229 | * endObject will be appended to this object. The 230 | * endObject method must be called to mark the object's end. 231 | * 232 | * @return this 233 | * @throws JSONException If the nesting is too deep, or if the object is 234 | * started in the wrong place (for example as a key or after the end of the 235 | * outermost array or object). 236 | */ 237 | public JSONWriter object() throws JSONException { 238 | if (this.mode == 'i') { 239 | this.mode = 'o'; 240 | } 241 | if (this.mode == 'o' || this.mode == 'a') { 242 | this.append("{"); 243 | this.push(new JSONObject()); 244 | this.comma = false; 245 | return this; 246 | } 247 | throw new JSONException("Misplaced object."); 248 | 249 | } 250 | 251 | /** 252 | * Pop an array or object scope. 253 | * 254 | * @param c The scope to close. 255 | * @throws JSONException If nesting is wrong. 256 | */ 257 | private void pop(char c) throws JSONException { 258 | if (this.top <= 0) { 259 | throw new JSONException("Nesting error."); 260 | } 261 | char m = this.stack[this.top - 1] == null ? 'a' : 'k'; 262 | if (m != c) { 263 | throw new JSONException("Nesting error."); 264 | } 265 | this.top -= 1; 266 | this.mode = this.top == 0 267 | ? 'd' 268 | : this.stack[this.top - 1] == null 269 | ? 'a' 270 | : 'k'; 271 | } 272 | 273 | /** 274 | * Push an array or object scope. 275 | * 276 | * @param c The scope to open. 277 | * @throws JSONException If nesting is too deep. 278 | */ 279 | private void push(JSONObject jo) throws JSONException { 280 | if (this.top >= maxdepth) { 281 | throw new JSONException("Nesting too deep."); 282 | } 283 | this.stack[this.top] = jo; 284 | this.mode = jo == null ? 'a' : 'k'; 285 | this.top += 1; 286 | } 287 | 288 | /** 289 | * Append either the value 290 | * true or the value 291 | * false. 292 | * 293 | * @param b A boolean. 294 | * @return this 295 | * @throws JSONException 296 | */ 297 | public JSONWriter value(boolean b) throws JSONException { 298 | return this.append(b ? "true" : "false"); 299 | } 300 | 301 | /** 302 | * Append a double value. 303 | * 304 | * @param d A double. 305 | * @return this 306 | * @throws JSONException If the number is not finite. 307 | */ 308 | public JSONWriter value(double d) throws JSONException { 309 | return this.value(Double.valueOf(d)); 310 | } 311 | 312 | /** 313 | * Append a long value. 314 | * 315 | * @param l A long. 316 | * @return this 317 | * @throws JSONException 318 | */ 319 | public JSONWriter value(long l) throws JSONException { 320 | return this.append(Long.toString(l)); 321 | } 322 | 323 | /** 324 | * Append an object value. 325 | * 326 | * @param object The object to append. It can be null, or a Boolean, Number, 327 | * String, JSONObject, or JSONArray, or an object that implements 328 | * JSONString. 329 | * @return this 330 | * @throws JSONException If the value is out of sequence. 331 | */ 332 | public JSONWriter value(Object object) throws JSONException { 333 | return this.append(JSONObject.valueToString(object)); 334 | } 335 | } 336 | -------------------------------------------------------------------------------- /SteamTrade-Java/src/main/java/com/nosoop/steamtrade/TradeListener.java: -------------------------------------------------------------------------------- 1 | package com.nosoop.steamtrade; 2 | 3 | import com.nosoop.steamtrade.inventory.TradeInternalAsset; 4 | import com.nosoop.steamtrade.status.TradeEvent; 5 | 6 | /** 7 | * Receives trade events from the TradeSession that it should be attached to. 8 | * 9 | * @author nosoop < nosoop at users.noreply.github.com > 10 | */ 11 | public abstract class TradeListener { 12 | public TradeSession trade; 13 | 14 | /** 15 | * Defines trade status codes to be interpreted by the onError() method. 16 | */ 17 | public static class TradeStatusCodes { 18 | public final static int // 19 | /** 20 | * Non-error statuses. Everything is okay according to Steam. 21 | * Something weird is going on if onError() is called with these 22 | * values. 23 | */ 24 | // We are polling for updates. 25 | STATUS_OK = 0, 26 | // Both users have decided to make the trade. 27 | TRADE_COMPLETED = 1, 28 | /** 29 | * Steam web errors. Something funky happened on Steam's side. 30 | * The error codes are defined by Steam. 31 | */ 32 | // Why this would happen, I don't know. 33 | TRADE_NOT_FOUND = 2, 34 | // One user cancelled. 35 | TRADE_CANCELLED = 3, 36 | // The other user timed out. 37 | PARTNER_TIMED_OUT = 4, 38 | // The trade failed in general. (?????) 39 | TRADE_FAILED = 5, 40 | /** 41 | * SteamTrade-Java errors. Something in this library bugged out. 42 | * The following error values are defined and used within the 43 | * library. 44 | */ 45 | // There was a JSONException caught when parsing the status. 46 | STATUS_PARSE_ERROR = 1001, 47 | // The trade session was unable to fetch your inventories. 48 | BACKPACK_SCRAPE_ERROR = 1002, 49 | // Unknown status -- message provided by the Status instance. 50 | STATUS_ERRORMESSAGE = 1003, 51 | // Something happened with the foreign inventory loading. 52 | FOREIGN_INVENTORY_LOAD_ERROR = 1004, 53 | // Something happened with our own inventory loading. 54 | OWN_INVENTORY_LOAD_ERROR = 1005, 55 | // The item specified could not be found in the inventory. 56 | USER_ITEM_NOT_FOUND = 1006, 57 | // The event action code is missing. 58 | TRADEEVENT_ACTION_MISSING = 1007; 59 | public final static String EMPTY_MESSAGE = ""; 60 | } 61 | 62 | /** 63 | * Called when an error occurs during the trade such that the trade is 64 | * closed. 65 | * 66 | * @param errorCode The error code. Known values are defined in 67 | * TradeListener.TradeErrorCodes. 68 | * @param errorMessage An error message, if available. If not available, 69 | * will default to TradeStatusCodes.EMPTY_MESSAGE. 70 | */ 71 | public abstract void onError(int errorCode, String errorMessage); 72 | 73 | // TODO implement onException, create TradeException? 74 | /** 75 | * Called when the client polls the trade. If you want to warn the other 76 | * person for taking too long, implement this method and add a cancel. 77 | * Otherwise, just do nothing. 78 | * 79 | * @param secondsSinceAction Number of seconds since the trading partner has 80 | * done something. 81 | * @param secondsSinceTrade Number of seconds since the trade has started. 82 | */ 83 | public abstract void onTimer(int secondsSinceAction, int secondsSinceTrade); 84 | 85 | /** 86 | * Called when the listener is connected to a trade. Things that depend on 87 | * trade session information can be assigned here. Or say a welcome message. 88 | */ 89 | public abstract void onWelcome(); 90 | 91 | /** 92 | * Called after backpacks are loaded and trading can start. If you need to 93 | * store inventories in your own listener, handle that here. 94 | */ 95 | public abstract void onAfterInit(); 96 | 97 | /** 98 | * Called when the other person adds an item. If this is an item from a new 99 | * inventory, that inventory is loaded before this event is called. 100 | * 101 | * @param inventoryItem The item added to the trade. 102 | */ 103 | public abstract void onUserAddItem(TradeInternalAsset inventoryItem); 104 | 105 | /** 106 | * Called when the other person removes an item. 107 | * 108 | * @param inventoryItem The item removed from the trade. 109 | */ 110 | public abstract void onUserRemoveItem(TradeInternalAsset inventoryItem); 111 | 112 | /** 113 | * Called when the other client send a message through Steam Trade. 114 | * 115 | * @param msg The received message. 116 | */ 117 | public abstract void onMessage(String msg); 118 | 119 | /* 120 | * Called when the trading partner checks / unchecks the 'ready' box. 121 | */ 122 | public abstract void onUserSetReadyState(boolean ready); 123 | 124 | /** 125 | * Called once the trading partner has accepted the trade and is waiting for 126 | * us to accept. 127 | */ 128 | public abstract void onUserAccept(); 129 | 130 | /** 131 | * Called when something has happened in the trade. 132 | */ 133 | public abstract void onNewVersion(); 134 | 135 | /** 136 | * Called once a trade has been made. 137 | */ 138 | public abstract void onTradeSuccess(); 139 | 140 | /** 141 | * Called once the trade has been closed for the client to begin cleaning 142 | * up. Called immediately after a successful trade or trade error. 143 | */ 144 | public abstract void onTradeClosed(); 145 | 146 | /** 147 | * Called when the client receives a TradeEvent that it has no idea how to 148 | * handle. In this case, a subclass of TradeListener can override this 149 | * method to handle the event a bit without having to recompile the library. 150 | * 151 | * @param event A trade event to be handled manually. 152 | */ 153 | public void onUnknownAction(TradeEvent event) { 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /SteamTrade-Java/src/main/java/com/nosoop/steamtrade/TradeSession.java: -------------------------------------------------------------------------------- 1 | package com.nosoop.steamtrade; 2 | 3 | import bundled.steamtrade.org.json.JSONObject; 4 | import bundled.steamtrade.org.json.JSONException; 5 | import com.nosoop.steamtrade.status.*; 6 | import com.nosoop.steamtrade.TradeListener.TradeStatusCodes; 7 | import com.nosoop.steamtrade.inventory.*; 8 | import com.nosoop.steamtrade.status.TradeEvent.TradeAction; 9 | import java.io.UnsupportedEncodingException; 10 | import java.io.BufferedReader; 11 | import java.io.IOException; 12 | import java.io.InputStreamReader; 13 | import java.io.OutputStreamWriter; 14 | import java.io.StringReader; 15 | import java.net.HttpURLConnection; 16 | import java.net.URL; 17 | import java.net.URLEncoder; 18 | import java.net.URLDecoder; 19 | import java.util.HashMap; 20 | import java.util.HashSet; 21 | import java.util.Map; 22 | import java.util.Set; 23 | import java.util.ArrayList; 24 | import java.util.List; 25 | 26 | /** 27 | * Represents a session of a trade. 28 | * 29 | * @author Top-Cat, nosoop 30 | */ 31 | public class TradeSession implements Runnable { 32 | /** 33 | * Static URL properties. 34 | */ 35 | public final static String STEAM_COMMUNITY_DOMAIN = "steamcommunity.com", 36 | STEAM_TRADE_URL = "http://steamcommunity.com/trade/%s/"; 37 | /** 38 | * Object to lock while polling and handling updates. 39 | */ 40 | protected final Object POLL_LOCK = new Object(); 41 | /** 42 | * Object representation of the users in the trade. 43 | */ 44 | private final TradeUser TRADE_USER_SELF, TRADE_USER_PARTNER; 45 | /** 46 | * List of app-context pairs for the active client's inventory. (A list of 47 | * the inventories we have, basically.) 48 | */ 49 | public List myAppContextData; 50 | /** 51 | * Collection of methods to interact with the current trade session. 52 | */ 53 | private final TradeCommands API; 54 | /** 55 | * String values needed for the trade. 56 | */ 57 | private final String TRADE_URL, STEAM_LOGIN, SESSION_ID; 58 | /** 59 | * Status values. 60 | */ 61 | public Status status = null; 62 | protected int version = 1, logpos; 63 | int lastEvent = 0; 64 | /** 65 | * A TradeListener instance that listens for events fired by this session. 66 | */ 67 | private TradeListener tradeListener; 68 | /** 69 | * Timing variables to check for idle state. 70 | */ 71 | private final long TIME_TRADE_START; 72 | private long timeLastPartnerAction; 73 | 74 | /** 75 | * Creates a new trading session. 76 | * 77 | * @param steamidSelf Long representation of our own SteamID. 78 | * @param steamidPartner Long representation of our trading partner's 79 | * SteamID. 80 | * @param sessionId String value of the Base64-encoded session token. 81 | * @param token String value of Steam's login token. 82 | * @param listener Trade listener to respond to trade actions. 83 | */ 84 | @SuppressWarnings("LeakingThisInConstructor") 85 | public TradeSession(long steamidSelf, long steamidPartner, String sessionId, 86 | String token, TradeListener listener) { 87 | this(steamidSelf, steamidPartner, sessionId, token, listener, 88 | new ArrayList()); 89 | } 90 | 91 | /** 92 | * Creates a new trading session. 93 | * 94 | * @param steamidSelf Long representation of our own SteamID. 95 | * @param steamidPartner Long representation of our trading partner's 96 | * SteamID. 97 | * @param sessionId String value of the Base64-encoded session token. 98 | * @param token String value of Steam's login token. 99 | * @param listener Trade listener to respond to trade actions. 100 | * @param assetBuilders An integer-to-assetbuilder map. The integers refer 101 | * to the appid of the inventory to be modified. 102 | */ 103 | @SuppressWarnings("LeakingThisInConstructor") 104 | public TradeSession(long steamidSelf, long steamidPartner, String sessionId, 105 | String token, TradeListener listener, 106 | final List assetBuilders) { 107 | SESSION_ID = sessionId; 108 | STEAM_LOGIN = token; 109 | 110 | tradeListener = listener; 111 | tradeListener.trade = this; 112 | 113 | TRADE_USER_SELF = new TradeUser(steamidSelf, assetBuilders); 114 | TRADE_USER_PARTNER = new TradeUser(steamidPartner, assetBuilders); 115 | 116 | TRADE_URL = String.format(STEAM_TRADE_URL, steamidPartner); 117 | API = new TradeCommands(); 118 | 119 | tradeListener.onWelcome(); 120 | scrapeBackpackContexts(); 121 | 122 | tradeListener.onAfterInit(); 123 | 124 | timeLastPartnerAction = TIME_TRADE_START = System.currentTimeMillis(); 125 | } 126 | 127 | /** 128 | * Polls the TradeSession for updates. Suggested poll rate is once every 129 | * second. 130 | */ 131 | @SuppressWarnings("unchecked") 132 | @Override 133 | public void run() { 134 | synchronized (POLL_LOCK) { 135 | try { 136 | status = API.getStatus(); 137 | } catch (final JSONException e) { 138 | tradeListener.onError( 139 | TradeStatusCodes.STATUS_PARSE_ERROR, e.getMessage()); 140 | 141 | try { 142 | API.cancelTrade(); 143 | } catch (JSONException ex) { 144 | ex.printStackTrace(); 145 | } 146 | tradeListener.onTradeClosed(); 147 | } 148 | 149 | if (status.trade_status == TradeStatusCodes.TRADE_COMPLETED) { 150 | // Trade successful. 151 | tradeListener.onTradeSuccess(); 152 | tradeListener.onTradeClosed(); 153 | } else if (status.trade_status 154 | == TradeStatusCodes.STATUS_ERRORMESSAGE) { 155 | tradeListener.onError(status.trade_status, status.error); 156 | tradeListener.onTradeClosed(); 157 | } else if (status.trade_status > 1) { 158 | // Refer to TradeListener.TradeStatusCodes for known values. 159 | tradeListener.onError(status.trade_status, 160 | TradeStatusCodes.EMPTY_MESSAGE); 161 | tradeListener.onTradeClosed(); 162 | } 163 | 164 | if (status.trade_status != TradeStatusCodes.STATUS_OK) { 165 | return; 166 | } 167 | 168 | // Update version 169 | if (status.newversion) { 170 | version = status.version; 171 | } 172 | 173 | if (lastEvent < status.events.size()) { 174 | // Process all new, unhandled events. 175 | for (; lastEvent < status.events.size(); lastEvent++) { 176 | handleTradeEvent(status.events.get(lastEvent)); 177 | } 178 | } else { 179 | // If there was no new action during this poll, update timer. 180 | final long timeCurrent = System.currentTimeMillis(); 181 | 182 | final int secondsSinceLastAction = 183 | (int) (timeCurrent - timeLastPartnerAction) / 1000; 184 | final int secondsSinceTradeStart = 185 | (int) (timeCurrent - TIME_TRADE_START) / 1000; 186 | 187 | tradeListener.onTimer( 188 | secondsSinceLastAction, secondsSinceTradeStart); 189 | } 190 | 191 | // Update Local Variables 192 | if (status.them != null) { 193 | TRADE_USER_PARTNER.ready = status.them.ready; 194 | TRADE_USER_SELF.ready = status.me.ready; 195 | } 196 | 197 | // Update version 198 | if (status.newversion) { 199 | tradeListener.onNewVersion(); 200 | } 201 | 202 | if (status.logpos != 0) { 203 | // ... no idea. 204 | // DebugPrint.println("WAT"); 205 | logpos = status.logpos; 206 | } 207 | } 208 | } 209 | 210 | /** 211 | * Handles received trade events and fires the appropriate event at the 212 | * given TradeListener, defined in the constructor. 213 | * 214 | * @param evt Trade event being handled. 215 | */ 216 | private void handleTradeEvent(final TradeEvent evt) { 217 | // Drop the event if the event's steamid is not theirs. 218 | boolean isBot = !evt.steamid.equals( 219 | String.valueOf(TRADE_USER_PARTNER.STEAM_ID)); 220 | 221 | // TODO Link their asset to variable item count. 222 | if (status.them.assets != null) { 223 | /* 224 | * System.out.println( 225 | * java.util.Arrays.toString(status.them.assets.toArray())); 226 | */ 227 | } 228 | 229 | switch (evt.action) { 230 | case TradeAction.ITEM_ADDED: 231 | eventUserAddedItem(evt); 232 | break; 233 | case TradeAction.ITEM_REMOVED: 234 | eventUserRemovedItem(evt); 235 | break; 236 | case TradeAction.READY_TOGGLED: 237 | if (!isBot) { 238 | TRADE_USER_PARTNER.ready = true; 239 | tradeListener.onUserSetReadyState(true); 240 | } else { 241 | TRADE_USER_SELF.ready = true; 242 | } 243 | break; 244 | case TradeAction.READY_UNTOGGLED: 245 | if (!isBot) { 246 | TRADE_USER_PARTNER.ready = false; 247 | tradeListener.onUserSetReadyState(false); 248 | } else { 249 | TRADE_USER_SELF.ready = false; 250 | } 251 | break; 252 | case TradeAction.TRADE_ACCEPTED: 253 | if (!isBot) { 254 | tradeListener.onUserAccept(); 255 | } 256 | break; 257 | case TradeAction.MESSAGE_ADDED: 258 | if (!isBot) { 259 | tradeListener.onMessage(evt.text); 260 | } 261 | break; 262 | case 6: 263 | eventUserSetCurrencyAmount(evt); 264 | break; 265 | case 8: 266 | // TODO Add support for stackable items. 267 | default: 268 | // Let the trade listener handle it. 269 | tradeListener.onUnknownAction(evt); 270 | break; 271 | } 272 | 273 | if (!isBot) { 274 | timeLastPartnerAction = System.currentTimeMillis(); 275 | } 276 | } 277 | 278 | private void eventUserAddedItem(TradeEvent evt) { 279 | boolean isBot = !evt.steamid.equals( 280 | String.valueOf(TRADE_USER_PARTNER.STEAM_ID)); 281 | 282 | if (!isBot) { 283 | /** 284 | * If this is the other user and we don't have their inventory yet, 285 | * then we will load it. 286 | */ 287 | TradeInternalInventory userInv; 288 | TradeInternalItem item; 289 | do { 290 | userInv = API.loadForeignInventory( 291 | new AppContextPair(evt.appid, evt.contextid)); 292 | item = userInv.getItem(evt.assetid); 293 | } while (item == null && userInv.hasMore()); 294 | 295 | if (item != null) { 296 | tradeListener.onUserAddItem(item); 297 | } else { 298 | // If null after loading the inventory, something's fishy. 299 | String errorMsg = "Could not load item asset %d in inventory " 300 | + "with appid %d and contextid %d."; 301 | 302 | tradeListener.onError(TradeStatusCodes.USER_ITEM_NOT_FOUND, 303 | String.format(errorMsg, evt.assetid, evt.appid, 304 | evt.contextid)); 305 | } 306 | } 307 | 308 | // Add to internal tracking. 309 | final TradeInternalInventories inv = (isBot 310 | ? TRADE_USER_SELF : TRADE_USER_PARTNER).getInventories(); 311 | 312 | final TradeInternalItem item = 313 | inv.getInventory(evt.appid, evt.contextid).getItem(evt.assetid); 314 | 315 | (isBot ? TRADE_USER_SELF : TRADE_USER_PARTNER).getOffer().add(item); 316 | } 317 | 318 | private void eventUserRemovedItem(TradeEvent evt) { 319 | boolean isBot = !evt.steamid.equals( 320 | String.valueOf(TRADE_USER_PARTNER.STEAM_ID)); 321 | 322 | if (!isBot) { 323 | final TradeInternalItem item = TRADE_USER_PARTNER.getInventories() 324 | .getInventory(evt.appid, evt.contextid) 325 | .getItem(evt.assetid); 326 | tradeListener.onUserRemoveItem(item); 327 | } 328 | 329 | // Get the item from one of our inventories and remove. 330 | final TradeInternalItem item = 331 | (isBot ? TRADE_USER_SELF : TRADE_USER_PARTNER).getInventories() 332 | .getInventory(evt.appid, evt.contextid).getItem(evt.assetid); 333 | 334 | (isBot ? TRADE_USER_SELF : TRADE_USER_PARTNER).getOffer().remove(item); 335 | } 336 | 337 | private void eventUserSetCurrencyAmount(TradeEvent evt) { 338 | boolean isBot = !evt.steamid.equals( 339 | String.valueOf(TRADE_USER_PARTNER.STEAM_ID)); 340 | 341 | if (!isBot) { 342 | /** 343 | * If this is the other user and we don't have their inventory yet, 344 | * then we will load it and set the amount of stuff added.. 345 | * 346 | * Keep loading the rest of the partial inventory until the item is 347 | * found. 348 | */ 349 | TradeInternalInventory userInv; 350 | TradeInternalCurrency item; 351 | do { 352 | userInv = API.loadForeignInventory( 353 | new AppContextPair(evt.appid, evt.contextid)); 354 | item = userInv.getCurrency(evt.currencyid); 355 | } while (item == null && userInv.hasMore()); 356 | 357 | if (item != null) { 358 | int previousAmount = item.getTradedAmount(); 359 | item.setTradedAmount(evt.amount); 360 | 361 | if (previousAmount > 0 && evt.amount == 0) { 362 | tradeListener.onUserRemoveItem(item); 363 | } else { 364 | tradeListener.onUserAddItem(item); 365 | } 366 | } 367 | } 368 | 369 | // Add to internal tracking. 370 | final TradeInternalInventories inv = (isBot 371 | ? TRADE_USER_SELF : TRADE_USER_PARTNER).getInventories(); 372 | 373 | final TradeInternalCurrency item = 374 | inv.getInventory(evt.appid, evt.contextid) 375 | .getCurrency(evt.currencyid); 376 | 377 | item.setTradedAmount(evt.amount); 378 | 379 | (isBot ? TRADE_USER_SELF : TRADE_USER_PARTNER).getOffer().add(item); 380 | } 381 | 382 | /** 383 | * Loads a copy of the trade screen, passing the data to ContextScraper to 384 | * generate a list of AppContextPairs to load our inventories with. 385 | */ 386 | private void scrapeBackpackContexts() { 387 | // I guess we're scraping the trade page. 388 | final Map data = new HashMap<>(); 389 | 390 | String pageData = API.fetch(TRADE_URL, "GET", data); 391 | 392 | try { 393 | List contexts = 394 | ContextScraper.scrapeContextData(pageData); 395 | myAppContextData = contexts; 396 | } catch (JSONException e) { 397 | // Notify the trade listener if we can't get our backpack data. 398 | myAppContextData = new ArrayList<>(); 399 | tradeListener.onError(TradeStatusCodes.BACKPACK_SCRAPE_ERROR, 400 | TradeStatusCodes.EMPTY_MESSAGE); 401 | 402 | } 403 | } 404 | 405 | /** 406 | * Loads one of our game inventories, storing it in a 407 | * TradeInternalInventories object. 408 | * 409 | * @param appContext An AppContextPair representing the inventory to be 410 | * loaded. 411 | */ 412 | public void loadOwnInventory(AppContextPair appContext) { 413 | final String url, response; 414 | 415 | if (TRADE_USER_SELF.getInventories().hasInventory(appContext)) { 416 | return; 417 | } 418 | 419 | // TODO Add support for large inventories ourselves. 420 | url = String.format(TradeCommands.LOCAL_INVENTORY_FORMAT_URL, 421 | TRADE_USER_SELF.STEAM_ID, 422 | appContext.getAppid(), appContext.getContextid()); 423 | 424 | response = API.fetch(url, "GET", null, true); 425 | 426 | try { 427 | JSONObject repsonseObject = new JSONObject(response); 428 | TRADE_USER_SELF.getInventories() 429 | .addInventory(appContext, repsonseObject); 430 | } catch (JSONException e) { 431 | tradeListener.onError(TradeStatusCodes.OWN_INVENTORY_LOAD_ERROR, 432 | e.getMessage()); 433 | } 434 | } 435 | 436 | /** 437 | * Returns our client's Steam ID. 438 | * 439 | * @return The client's Steam ID as a 64-bit long value. 440 | */ 441 | public long getOwnSteamId() { 442 | return TRADE_USER_SELF.STEAM_ID; 443 | } 444 | 445 | /** 446 | * Returns our trading partner's Steam ID. 447 | * 448 | * @return The trading partner's Steam ID as a 64-bit long value. 449 | */ 450 | public long getPartnerSteamId() { 451 | return TRADE_USER_PARTNER.STEAM_ID; 452 | } 453 | 454 | /** 455 | * Returns a TradeUser instance containing our Steam ID, loaded inventories, 456 | * offer, and whether or not we are ready. 457 | * 458 | * @return A TradeUser instance containing data for the running client. 459 | */ 460 | public TradeUser getSelf() { 461 | return TRADE_USER_SELF; 462 | } 463 | 464 | /** 465 | * Returns a TradeUser instance containing our trading partner's Steam ID, 466 | * loaded inventories, offer, and whether or not they are ready. 467 | * 468 | * @return A TradeUser instance containing data for the trading partner. 469 | */ 470 | public TradeUser getPartner() { 471 | return TRADE_USER_PARTNER; 472 | } 473 | 474 | /** 475 | * Gets the commands associated with this trade session. 476 | * 477 | * @return TradeCommands object that handles the user-trade actions. 478 | */ 479 | public TradeCommands getCmds() { 480 | return API; 481 | } 482 | 483 | /** 484 | * A utility class to hold all web-based 'fetch' actions when dealing with 485 | * Steam Trade in the current trading session. 486 | * 487 | * @author nosoop 488 | */ 489 | public class TradeCommands { 490 | /** 491 | * The user-agent string to use when making requests. 492 | */ 493 | final static String USER_AGENT = "SteamTrade-Java/1.0 " 494 | + "(Windows; U; Windows NT 6.1; en-US; " 495 | + "Valve Steam Client/1392853084; SteamTrade-Java Client; ) " 496 | + "AppleWebKit/535.19 (KHTML, like Gecko) " 497 | + "Chrome/18.0.1025.166 Safari/535.19"; 498 | /** 499 | * The format string representing a URL to load our client's inventory. 500 | * The three placeholders are for STEAMID, APPID, and CONTEXTID. 501 | */ 502 | final static String LOCAL_INVENTORY_FORMAT_URL = 503 | "http://steamcommunity.com/profiles/%d/" 504 | + "inventory/json/%d/%d/?trading=1"; 505 | /** 506 | * A URL-decoded copy of SESSION_ID. Needed to make requests. 507 | */ 508 | final String DECODED_SESSION_ID; 509 | /** 510 | * Quantity for an item with no transfer amount. 511 | */ 512 | private static final int NO_TRANSFER_AMOUNT = -1; 513 | 514 | /** 515 | * Initializes the instance and attempts to create a URL-decoded copy of 516 | * the SESSION_ID. Fails if the system does not support UTF-8. 517 | */ 518 | TradeCommands() { 519 | try { 520 | DECODED_SESSION_ID = URLDecoder.decode(SESSION_ID, "UTF-8"); 521 | } catch (UnsupportedEncodingException e) { 522 | /** 523 | * If you can't decode to UTF-8, well, you're kinda boned. No 524 | * way to get around the issue, I think, so we'll just throw an 525 | * error. 526 | */ 527 | throw new Error(e); 528 | } 529 | } 530 | 531 | /** 532 | * Tell the trading service to add one of our own items into the trade. 533 | * 534 | * @param item The item, represented by an TradeInternalItem instance. 535 | * @param slot The offer slot to place the item in (0~255). 536 | */ 537 | public void addItem(TradeInternalItem item, int slot) { 538 | addItem(item.getAppid(), item.getContextid(), item.getAssetid(), 539 | slot, NO_TRANSFER_AMOUNT); 540 | } 541 | 542 | public void addItem(TradeInternalItem item, int slot, int amount) { 543 | // Assert that the item is stackable and we're using a valid amount. 544 | assert (item.isStackable() && amount >= 0); 545 | 546 | addItem(item.getAppid(), item.getContextid(), item.getAssetid(), 547 | slot, amount); 548 | } 549 | 550 | /** 551 | * Adds an item to the trade directly, as opposed to using a 552 | * TradeInternalItem instance. 553 | * 554 | * @param appid The game inventory for the item. 555 | * @param contextid The inventory "context" for the item. 556 | * @param assetid The inventory "asset", the item id. 557 | * @param slot The offer slot to place the item in (0~255). 558 | */ 559 | public void addItem(int appid, long contextid, long assetid, int slot, 560 | int amount) { 561 | final Map data = new HashMap<>(); 562 | data.put("sessionid", DECODED_SESSION_ID); 563 | data.put("appid", "" + appid); 564 | data.put("contextid", "" + contextid); 565 | data.put("itemid", "" + assetid); 566 | data.put("slot", "" + slot); 567 | 568 | if (amount != NO_TRANSFER_AMOUNT) { 569 | data.put("amount", "" + amount); 570 | } 571 | 572 | fetch(TRADE_URL + "additem", "POST", data); 573 | } 574 | 575 | /** 576 | * Removes an item from the trade. 577 | * 578 | * @param item The item, represented by an TradeInternalItem instance. 579 | */ 580 | public void removeItem(TradeInternalItem item) { 581 | removeItem(item.getAppid(), item.getContextid(), item.getAssetid()); 582 | } 583 | 584 | /** 585 | * Removes an item from the trade directly, as opposed to using a 586 | * TradeInternalItem instance. 587 | * 588 | * @param appid The game inventory for the item. 589 | * @param contextid The inventory "context" for the item. 590 | * @param assetid The inventory "asset", the item id. 591 | */ 592 | public void removeItem(int appid, long contextid, long assetid) { 593 | final Map data = new HashMap<>(); 594 | data.put("sessionid", DECODED_SESSION_ID); 595 | data.put("appid", "" + appid); 596 | data.put("contextid", "" + contextid); 597 | data.put("itemid", "" + assetid); 598 | 599 | fetch(TRADE_URL + "removeitem", "POST", data); 600 | } 601 | 602 | /** 603 | * Tick / untick the checkbox signaling that we are ready to complete 604 | * the trade. 605 | * 606 | * @param ready Whether the client is ready to trade or not 607 | * @return True on success, false otherwise. 608 | */ 609 | public boolean setReady(boolean ready) { 610 | final Map data = new HashMap<>(); 611 | data.put("sessionid", DECODED_SESSION_ID); 612 | data.put("ready", ready ? "true" : "false"); 613 | data.put("version", "" + version); 614 | 615 | final String response = 616 | fetch(TRADE_URL + "toggleready", "POST", data); 617 | 618 | try { 619 | Status readyStatus = new Status(new JSONObject(response)); 620 | if (readyStatus.success) { 621 | if (readyStatus.trade_status 622 | == TradeStatusCodes.STATUS_OK) { 623 | TRADE_USER_PARTNER.ready = readyStatus.them.ready; 624 | TRADE_USER_SELF.ready = readyStatus.me.ready; 625 | } else { 626 | TRADE_USER_SELF.ready = true; 627 | } 628 | return TRADE_USER_SELF.ready; 629 | } 630 | } catch (final JSONException e) { 631 | e.printStackTrace(); 632 | } 633 | return false; 634 | } 635 | 636 | /** 637 | * Hits the "Make Trade" button, finalizing the trade. Not sure what the 638 | * response is for. 639 | * 640 | * @return JSONObject representing trade status. 641 | * @throws JSONException if the response is unexpected. 642 | */ 643 | public JSONObject acceptTrade() throws JSONException { 644 | final Map data = new HashMap<>(); 645 | data.put("sessionid", DECODED_SESSION_ID); 646 | data.put("version", "" + version); 647 | 648 | final String response = fetch(TRADE_URL + "confirm", "POST", data); 649 | 650 | return new JSONObject(response); 651 | } 652 | 653 | /** 654 | * Cancels the trade session as if we clicked the "Cancel Trade" button. 655 | * Expect a call of onError(TradeErrorCodes.TRADE_CANCELLED). 656 | * 657 | * @return True if server responded as successful, false otherwise. 658 | * @throws JSONException when there is an error in parsing the response. 659 | */ 660 | public boolean cancelTrade() throws JSONException { 661 | final Map data = new HashMap(); 662 | data.put("sessionid", DECODED_SESSION_ID); 663 | final String response = fetch(TRADE_URL + "cancel", "POST", data); 664 | 665 | return (new JSONObject(response)).getBoolean("success"); 666 | } 667 | 668 | /** 669 | * Adds a message to trade chat. 670 | * 671 | * @param message The message to add to trade chat. 672 | * @return String representing server response 673 | */ 674 | public String sendMessage(String message) { 675 | final Map data = new HashMap<>(); 676 | data.put("sessionid", DECODED_SESSION_ID); 677 | data.put("message", message); 678 | data.put("logpos", "" + logpos); 679 | data.put("version", "" + version); 680 | 681 | return fetch(TRADE_URL + "chat", "POST", data); 682 | } 683 | 684 | /** 685 | * Fetches status updates to the current trade. 686 | * 687 | * @return Status object to be processed. 688 | * @throws JSONException Malformed / invalid response data. 689 | */ 690 | private Status getStatus() throws JSONException { 691 | final Map data = new HashMap<>(); 692 | data.put("sessionid", DECODED_SESSION_ID); 693 | data.put("logpos", "" + logpos); 694 | data.put("version", "" + version); 695 | 696 | final String response = fetch(TRADE_URL + "tradestatus/", "POST", 697 | data); 698 | 699 | return new Status(new JSONObject(response)); 700 | } 701 | 702 | /** 703 | * Loads a foreign inventory. If the inventory already exists partially 704 | * loaded, continue loading the inventory. 705 | * 706 | * @param appContext 707 | * @return 708 | */ 709 | public synchronized TradeInternalInventory loadForeignInventory( 710 | AppContextPair appContext) { 711 | final Map data = new HashMap<>(); 712 | data.put("sessionid", DECODED_SESSION_ID); 713 | data.put("steamid", TRADE_USER_PARTNER.STEAM_ID + ""); 714 | data.put("appid", appContext.getAppid() + ""); 715 | data.put("contextid", appContext.getContextid() + ""); 716 | 717 | 718 | if (!TRADE_USER_PARTNER.INVENTORIES.hasInventory(appContext)) { 719 | /** 720 | * Make a nonexistent inventory if needed. 721 | */ 722 | TRADE_USER_PARTNER.INVENTORIES.addInventory(appContext); 723 | } 724 | 725 | TradeInternalInventory inventory = 726 | TRADE_USER_PARTNER.INVENTORIES.getInventory(appContext); 727 | 728 | if (inventory.getMoreStartPosition() != 0 || inventory.hasMore()) { 729 | data.put("start", inventory.getMoreStartPosition() + ""); 730 | } 731 | 732 | 733 | try { 734 | String feed = fetch(TRADE_URL + "foreigninventory/", "GET", 735 | data); 736 | 737 | JSONObject jsonData = new JSONObject(feed); 738 | 739 | inventory.loadMore(jsonData); 740 | return inventory; 741 | } catch (JSONException e) { 742 | // Something wrong happened... 743 | return inventory; 744 | } 745 | } 746 | 747 | /** 748 | * Requests a String representation of an online file. 749 | * 750 | * @param url Location to fetch. 751 | * @param method "GET" or "POST" 752 | * @param data The data to be added to the data stream or request 753 | * params. 754 | * @return The server's String response to the request. 755 | */ 756 | String fetch(String url, String method, Map data) { 757 | return fetch(url, method, data, true); 758 | } 759 | 760 | /** 761 | * Requests a String representation of an online file (for Steam). 762 | * 763 | * @param url Location to fetch. 764 | * @param method "GET" or "POST" 765 | * @param data The data to be added to the data stream or request 766 | * params. 767 | * @param sendLoginData Whether or not to send login session data. 768 | * @return The server's String response to the request. 769 | */ 770 | String fetch(String url, String method, Map data, 771 | boolean sendLoginData) { 772 | String cookies = ""; 773 | if (sendLoginData) { 774 | cookies = "sessionid=" + DECODED_SESSION_ID + "; " 775 | + "steamLogin=" + STEAM_LOGIN + ";"; 776 | } 777 | final String response = request(url, method, data, cookies); 778 | return response; 779 | } 780 | 781 | /** 782 | * Requests a String representation of an online file (for Steam). 783 | * 784 | * @param url Location to fetch. 785 | * @param method "GET" or "POST" 786 | * @param data The data to be added to the data stream or request 787 | * params. 788 | * @param cookies A string of cookie data to be added to the request 789 | * headers. 790 | * @return The server's String response to the request. 791 | */ 792 | String request(String url, String method, Map data, 793 | String cookies) { 794 | boolean ajax = true; 795 | StringBuilder out = new StringBuilder(); 796 | try { 797 | String dataString; 798 | StringBuilder dataStringBuffer = new StringBuilder(); 799 | if (data != null) { 800 | for (Map.Entry entry : data.entrySet()) { 801 | dataStringBuffer.append( 802 | URLEncoder.encode(entry.getKey(), "UTF-8")) 803 | .append("=").append( 804 | URLEncoder.encode(entry.getValue(), "UTF-8")) 805 | .append("&"); 806 | } 807 | } 808 | dataString = dataStringBuffer.toString(); 809 | if (!method.equals("POST")) { 810 | url += "?" + dataString; 811 | } 812 | final URL url2 = new URL(url); 813 | final HttpURLConnection conn = 814 | (HttpURLConnection) url2.openConnection(); 815 | conn.setRequestProperty("Cookie", cookies); 816 | conn.setRequestMethod(method); 817 | System.setProperty("http.agent", ""); 818 | 819 | conn.setRequestProperty("User-Agent", USER_AGENT); 820 | 821 | conn.setRequestProperty("Host", "steamcommunity.com"); 822 | conn.setRequestProperty("Content-type", 823 | "application/x-www-form-urlencoded; charset=UTF-8"); 824 | conn.setRequestProperty("Accept", 825 | "text/javascript, text/hml, " 826 | + "application/xml, text/xml, */*"); 827 | 828 | /** 829 | * Turns out we need a referer, otherwise we get an error from 830 | * the server. Just use the trade URL as one since we have it on 831 | * hand, and it's been known to work. 832 | * 833 | * http://steamcommunity.com/trade/1 was used for other 834 | * libraries, but having a hardcoded thing like that is gross. 835 | */ 836 | conn.setRequestProperty("Referer", TRADE_URL); 837 | 838 | // Accept compressed responses. (We can decompress it.) 839 | conn.setRequestProperty("Accept-Encoding", "gzip,deflate"); 840 | 841 | if (ajax) { 842 | conn.setRequestProperty("X-Requested-With", 843 | "XMLHttpRequest"); 844 | conn.setRequestProperty("X-Prototype-Version", "1.7"); 845 | } 846 | 847 | if (method.equals("POST")) { 848 | conn.setDoOutput(true); 849 | try (OutputStreamWriter os = 850 | new OutputStreamWriter(conn.getOutputStream())) { 851 | os.write(dataString.substring(0, 852 | dataString.length() - 1)); 853 | os.flush(); 854 | } 855 | } 856 | 857 | java.io.InputStream netStream = conn.getInputStream(); 858 | 859 | // If GZIPped response, then use the gzip decoder. 860 | if (conn.getContentEncoding().contains("gzip")) { 861 | netStream = new java.util.zip.GZIPInputStream(netStream); 862 | } 863 | 864 | try (BufferedReader reader = new BufferedReader( 865 | new InputStreamReader(netStream))) { 866 | String line; // Stores the currently read line. 867 | while ((line = reader.readLine()) != null) { 868 | if (out.length() > 0) { 869 | out.append('\n'); 870 | } 871 | out.append(line); 872 | } 873 | reader.close(); 874 | } 875 | } catch (final IOException e) { 876 | e.printStackTrace(); 877 | } 878 | return out.toString(); 879 | } 880 | } 881 | 882 | /** 883 | * A set of values associated with one of the two users currently in the 884 | * trade. 885 | * 886 | * @author nosoop 887 | */ 888 | public static class TradeUser { 889 | final long STEAM_ID; 890 | final Set TRADE_OFFER; 891 | final TradeInternalInventories INVENTORIES; 892 | boolean ready; 893 | 894 | TradeUser(long steamid, List assetBuilders) { 895 | this.STEAM_ID = steamid; 896 | this.TRADE_OFFER = new HashSet<>(); 897 | this.INVENTORIES = new TradeInternalInventories(assetBuilders); 898 | this.ready = false; 899 | } 900 | 901 | /** 902 | * @return A set of TradeInternalAsset instances displaying the offer, 903 | * containing TradeInternalItem and TradeInternalCurrency instances. 904 | */ 905 | public Set getOffer() { 906 | return TRADE_OFFER; 907 | } 908 | 909 | /** 910 | * @return A 64-bit long representation of the instance Steam ID. 911 | */ 912 | public long getSteamID() { 913 | return STEAM_ID; 914 | } 915 | 916 | /** 917 | * @return The selected user's TradeInternalInventories instance. 918 | */ 919 | public TradeInternalInventories getInventories() { 920 | return INVENTORIES; 921 | } 922 | 923 | /** 924 | * @return Whether or not the selected user is ready. 925 | */ 926 | public boolean isReady() { 927 | return ready; 928 | } 929 | } 930 | 931 | } 932 | 933 | /** 934 | * Private class that brutally scrapes the AppContextData JavaScript object from 935 | * the trade page. Without this, we would not know what inventories we have. 936 | * 937 | * @author nosoop 938 | */ 939 | class ContextScraper { 940 | // TODO Uncouple this from the TradeSession class? 941 | static final List DEFAULT_APPCONTEXTDATA = 942 | new ArrayList<>(); 943 | 944 | /** 945 | * Initialize default AppContextPairs. 946 | */ 947 | static { 948 | DEFAULT_APPCONTEXTDATA.add( 949 | new AppContextPair(440, 2, "Team Fortress 2")); 950 | } 951 | 952 | /** 953 | * Scrapes the page for the g_rgAppContextData variable and passes it to a 954 | * private method for parsing, returning the list of named AppContextPair 955 | * objects it generates. It's a bit of a hack... 956 | * 957 | * @param pageResult The page data fetched by the TradeSession object. 958 | * @return A list of named AppContextPair objects representing the known 959 | * inventories, or an empty list if not found. 960 | */ 961 | static List scrapeContextData(String pageResult) 962 | throws JSONException { 963 | try { 964 | BufferedReader read; 965 | read = new BufferedReader(new StringReader(pageResult)); 966 | 967 | String buffer; 968 | while ((buffer = read.readLine()) != null) { 969 | String input; 970 | input = buffer.trim(); 971 | 972 | if (input.startsWith("var g_rgAppContextData")) { 973 | // Extract the JSON string from the JavaScript source. Bleh 974 | return parseContextData(input.substring(input.indexOf('{'), 975 | input.length() - 1)); 976 | } 977 | } 978 | } catch (IOException ex) { 979 | ex.printStackTrace(); 980 | } 981 | 982 | // If we can't find it, return an empty one, I guess... 983 | return DEFAULT_APPCONTEXTDATA; 984 | } 985 | 986 | /** 987 | * Parses the context data JSON feed and makes a bunch of AppContextPair 988 | * instances. 989 | * 990 | * @param json The JSON String representing g_rgAppContextData. 991 | * @return A list of named AppContextPair objects representing the available 992 | * inventories. 993 | */ 994 | private static List parseContextData(String json) 995 | throws JSONException { 996 | List result = new ArrayList<>(); 997 | 998 | JSONObject feedData = new JSONObject(json); 999 | 1000 | for (String on : (Set) feedData.keySet()) { 1001 | JSONObject o = feedData.getJSONObject(on); 1002 | if (o != null) { 1003 | String gameName = o.getString("name"); 1004 | int appid = o.getInt("appid"); 1005 | 1006 | JSONObject contextData = o.getJSONObject("rgContexts"); 1007 | 1008 | for (String bn : (Set) contextData.keySet()) { 1009 | JSONObject b = contextData.getJSONObject(bn); 1010 | String contextName = b.getString("name"); 1011 | long contextid = Long.parseLong(b.getString("id")); 1012 | int assetCount = b.getInt("asset_count"); 1013 | 1014 | // "Team Fortress 2 - Backpack (226)" 1015 | String invNameFormat = String.format("%s - %s (%d)", 1016 | gameName, contextName, assetCount); 1017 | 1018 | // Only include the inventory if it's not empty. 1019 | if (assetCount > 0) { 1020 | result.add(new AppContextPair( 1021 | appid, contextid, invNameFormat)); 1022 | } 1023 | } 1024 | } 1025 | } 1026 | return result; 1027 | } 1028 | } 1029 | -------------------------------------------------------------------------------- /SteamTrade-Java/src/main/java/com/nosoop/steamtrade/inventory/AppContextPair.java: -------------------------------------------------------------------------------- 1 | package com.nosoop.steamtrade.inventory; 2 | 3 | /** 4 | * Represents an integer/long id pair to identify a player's inventory. 5 | * Optionally allows a name. 6 | * 7 | * @author nosoop 8 | */ 9 | public class AppContextPair { 10 | 11 | int appid; 12 | long contextid; 13 | String name; 14 | 15 | /** 16 | * Creates an app-context pair. 17 | * 18 | * @param appid 19 | * @param contextid 20 | */ 21 | public AppContextPair(int appid, long contextid) { 22 | this.appid = appid; 23 | this.contextid = contextid; 24 | this.name = null; 25 | } 26 | 27 | /** 28 | * Creates an app-context pair with a name. 29 | * 30 | * @param appid 31 | * @param contextid 32 | * @param name 33 | */ 34 | public AppContextPair(int appid, long contextid, String name) { 35 | this(appid, contextid); 36 | this.name = name; 37 | } 38 | 39 | /** 40 | * @return Integer containing the game's inventory-specific contextid. 41 | */ 42 | public long getContextid() { 43 | return contextid; 44 | } 45 | 46 | /** 47 | * @return Integer containing the game-specific appid. 48 | */ 49 | public int getAppid() { 50 | return appid; 51 | } 52 | 53 | /** 54 | * @return Name of the AppContextPair, or null if there is none. 55 | */ 56 | public String getName() { 57 | return name; 58 | } 59 | 60 | /** 61 | * Returns a String representing the AppContextPair object. 62 | * 63 | * @return String with the game name (ex: "Team Fortress 2"). If null, 64 | * returns a generic identifier (ex: "[APPCONTEXT] 440_2") instead. 65 | */ 66 | @Override 67 | public String toString() { 68 | if (name != null) { 69 | return name; 70 | } else { 71 | return String.format("[APPCONTEXT] %d_%d", appid, contextid); 72 | } 73 | } 74 | 75 | @Override 76 | public int hashCode() { 77 | return 38 * appid + Long.valueOf(contextid).hashCode(); 78 | } 79 | 80 | @Override 81 | public boolean equals(Object obj) { 82 | if (obj == null) { 83 | return false; 84 | } 85 | if (getClass() != obj.getClass()) { 86 | return false; 87 | } 88 | final AppContextPair other = (AppContextPair) obj; 89 | if (this.appid != other.appid) { 90 | return false; 91 | } 92 | if (this.contextid != other.contextid) { 93 | return false; 94 | } 95 | return true; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /SteamTrade-Java/src/main/java/com/nosoop/steamtrade/inventory/AssetBuilder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this template, choose Tools | Templates 3 | * and open the template in the editor. 4 | */ 5 | package com.nosoop.steamtrade.inventory; 6 | 7 | import bundled.steamtrade.org.json.JSONException; 8 | import bundled.steamtrade.org.json.JSONObject; 9 | 10 | /** 11 | * Extendable class that builds TradeInternalItem and TradeInternalCurrency 12 | * instances. 13 | * 14 | * @author nosoop < nosoop at users.noreply.github.com > 15 | */ 16 | public abstract class AssetBuilder { 17 | /** 18 | * Determines whether or not this AssetBuilder instance should handle 19 | * loading a given inventory. 20 | * 21 | * @param appContext An appid-contextid pair for an inventory. 22 | * @return Whether or not this AssetBuilder instance should handle the 23 | * inventory. 24 | */ 25 | public abstract boolean isSupported(AppContextPair appContext); 26 | 27 | public TradeInternalItem generateItem(AppContextPair appContext, 28 | JSONObject rgInventory, JSONObject rgDescription) 29 | throws JSONException { 30 | return new TradeInternalItem(appContext, rgInventory, rgDescription); 31 | } 32 | 33 | public TradeInternalCurrency generateCurrency(AppContextPair appContext, 34 | JSONObject rgInventory, JSONObject rgDescription) 35 | throws JSONException { 36 | return new TradeInternalCurrency(appContext, rgInventory, rgDescription); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /SteamTrade-Java/src/main/java/com/nosoop/steamtrade/inventory/ItemAttribute.java: -------------------------------------------------------------------------------- 1 | package com.nosoop.steamtrade.inventory; 2 | 3 | import bundled.steamtrade.org.json.JSONException; 4 | import bundled.steamtrade.org.json.JSONObject; 5 | 6 | /** 7 | * Class that holds individual item attributes. Modified to support the 8 | * "float_value" value, which is required to determine which crate series is 9 | * what, among other things. 10 | * 11 | * Effectively deprecated and will probably be removed soon. 12 | * 13 | * @author Top-Cat, nosoop 14 | */ 15 | public class ItemAttribute { 16 | public short defIndex; 17 | public float floatValue; 18 | public String value; 19 | 20 | @Deprecated 21 | ItemAttribute(JSONObject obj) throws JSONException { 22 | defIndex = (short) obj.getInt("defindex"); 23 | value = obj.getString("value"); 24 | 25 | if (obj.has("float_value")) { 26 | floatValue = (float) obj.getDouble("float_value"); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /SteamTrade-Java/src/main/java/com/nosoop/steamtrade/inventory/TradeInternalAsset.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this template, choose Tools | Templates 3 | * and open the template in the editor. 4 | */ 5 | package com.nosoop.steamtrade.inventory; 6 | 7 | import bundled.steamtrade.org.json.JSONArray; 8 | import bundled.steamtrade.org.json.JSONException; 9 | import bundled.steamtrade.org.json.JSONObject; 10 | import java.awt.Color; 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | 14 | /** 15 | * Represents a generic, tradable asset and its basic characteristics. 16 | * 17 | * @author nosoop < nosoop at users.noreply.github.com > 18 | */ 19 | public abstract class TradeInternalAsset { 20 | /** 21 | * The inventory this item resides in, as defined by its appid and contextid 22 | * pair. 23 | */ 24 | AppContextPair appContext; 25 | /** 26 | * The display name of the item. If the item was renamed (as it could be in 27 | * TF2, it will be that name. 28 | */ 29 | String name; 30 | /** 31 | * The name it would be grouped under in the Steam Community Market. Is an 32 | * empty string if not in the Market. 33 | */ 34 | String marketName; 35 | /** 36 | * The item's type. 37 | */ 38 | String type; 39 | /** 40 | * The class number of this object. Two similar items (e.g., a pair of Loose 41 | * Cannons) will have the same class number. 42 | */ 43 | int classid; 44 | /** 45 | * The number of items of this object has. 46 | */ 47 | int amount; 48 | /** 49 | * The item id number. 50 | */ 51 | long assetid; 52 | /** 53 | * List of description instances. 54 | */ 55 | List descriptions; 56 | 57 | /** 58 | * Creates a new TradeInternalAsset instance. 59 | * 60 | * @param rgInventoryItem This asset's member JSONObject of "rgInventory" or 61 | * "rgCurrency". 62 | * @param rgDescriptionItem This asset's member JSONObject of 63 | * "rgDescription". 64 | * @throws JSONException when the values with names "name", "market_name", 65 | * "type", "classid", or "appid" are not found in the rgDescriptionItem 66 | * member object and/or when "classid", "amount", or "id" are not found in 67 | * the rgInventory or rgCurrency member object. 68 | */ 69 | TradeInternalAsset(AppContextPair appContext, JSONObject rgInventoryItem, 70 | JSONObject rgDescriptionItem) throws JSONException { 71 | String classidString; 72 | this.appContext = appContext; 73 | 74 | this.name = rgDescriptionItem.getString("name"); 75 | this.marketName = rgDescriptionItem.getString("market_name"); 76 | this.type = rgDescriptionItem.getString("type"); 77 | 78 | classidString = rgInventoryItem.getString("classid"); 79 | this.classid = Integer.parseInt(classidString); 80 | 81 | this.amount = Integer.parseInt( 82 | rgInventoryItem.optString("amount", "1")); 83 | this.assetid = Long.parseLong(rgInventoryItem.getString("id")); 84 | 85 | this.descriptions = new ArrayList<>(); 86 | 87 | JSONArray dsArr = rgDescriptionItem.optJSONArray("descriptions"); 88 | if (dsArr != null) { 89 | for (int i = 0; i < dsArr.length(); i++) { 90 | this.descriptions.add(new Description(dsArr.getJSONObject(i))); 91 | } 92 | } 93 | 94 | /** 95 | * Verify that the input appid is the same appid passed in the 96 | * constructor. 97 | */ 98 | int descriptionAppid = Integer.parseInt( 99 | rgDescriptionItem.getString("appid")); 100 | assert (descriptionAppid == this.appContext.appid); 101 | 102 | /** 103 | * Assert that the classid matches rgDescription and rgCurrency or 104 | * rgInventory by hash. 105 | */ 106 | String descriptionClassidString = 107 | rgDescriptionItem.getString("classid"); 108 | assert (descriptionClassidString.equals(classidString)); 109 | } 110 | 111 | /** 112 | * Returns the display name of this asset, defaulting to its name. Allowed 113 | * to be overridden by subclasses. 114 | * 115 | * @return String representing the name of this asset. 116 | */ 117 | public String getDisplayName() { 118 | return getName(); 119 | } 120 | 121 | /** 122 | * Returns the name of this asset. 123 | * 124 | * @return The name of this asset, as defined by the "name"-named name-value 125 | * pair in the item's "rgDescriptions" JSONObject member entry. 126 | */ 127 | public final String getName() { 128 | return name; 129 | } 130 | 131 | /** 132 | * Returns the market name of this asset. 133 | * 134 | * @return The market name of this asset. If it does not have a market name, 135 | * it returns an empty string. 136 | */ 137 | public final String getMarketName() { 138 | return marketName; 139 | } 140 | 141 | /** 142 | * Returns the appid of this asset. 143 | * 144 | * @return The appid of this asset. Defined by the AppContextPair instance 145 | * passed to it, this is asserted to be the same value as the one defined in 146 | * the "appid"-named name-value pair in the item's "rgDescriptions" 147 | * JSONObject member entry. 148 | */ 149 | public final int getAppid() { 150 | return appContext.appid; 151 | } 152 | 153 | /** 154 | * Returns the contextid of this asset. 155 | * 156 | * @return The contextid of this asset. Defined by the AppContextPair 157 | * instance passed to it, unlike the appid, there is no way to verify that 158 | * the contextid is correctly defined. 159 | */ 160 | public final long getContextid() { 161 | return appContext.contextid; 162 | } 163 | 164 | /** 165 | * Returns the assetid of this asset. 166 | * 167 | * @return The assetid of this asset, defined by the "id"-named name-value 168 | * pair of this asset's "rgInventory" JSONObject member entry. 169 | */ 170 | public final long getAssetid() { 171 | return assetid; 172 | } 173 | 174 | /** 175 | * Returns the classid of this asset. 176 | * 177 | * @return The classid of this asset, defined by the "classid"-named 178 | * name-value pair of the asset's "rgDescription" JSONObject member entry 179 | * and asserted to be equal to the similar value in the asset's 180 | * "rgInventory" entry. 181 | */ 182 | public final int getClassid() { 183 | return classid; 184 | } 185 | 186 | /** 187 | * Returns the amount of this asset. 188 | * 189 | * @return The amount of this asset, defined by the "amount"-named 190 | * name-value pair of the asset's "rgInventory" JSONObject member entry 191 | */ 192 | public final int getAmount() { 193 | return amount; 194 | } 195 | 196 | /** 197 | * Returns the type of this asset. 198 | * 199 | * @return The amount of this asset, defined by the "type"-named name-value 200 | * pair of the asset's "rgDescription" JSONObject member entry 201 | */ 202 | public final String getType() { 203 | return type; 204 | } 205 | 206 | public final List getDescriptions() { 207 | return descriptions; 208 | } 209 | 210 | public static class Description { 211 | final String type; 212 | final String value; 213 | final Color color; 214 | 215 | private Description(JSONObject descriptions) throws JSONException { 216 | type = descriptions.optString("type", "text"); 217 | value = descriptions.getString("value"); 218 | 219 | String hexColor = descriptions.optString("color", "000000"); 220 | 221 | color = new Color(Integer.parseInt(hexColor, 16)); 222 | } 223 | 224 | public Color getColor() { 225 | return color; 226 | } 227 | 228 | public String getValue() { 229 | return value; 230 | } 231 | 232 | public String getType() { 233 | return type; 234 | } 235 | } 236 | 237 | } 238 | -------------------------------------------------------------------------------- /SteamTrade-Java/src/main/java/com/nosoop/steamtrade/inventory/TradeInternalCurrency.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this template, choose Tools | Templates 3 | * and open the template in the editor. 4 | */ 5 | package com.nosoop.steamtrade.inventory; 6 | 7 | import bundled.steamtrade.org.json.JSONException; 8 | import bundled.steamtrade.org.json.JSONObject; 9 | 10 | /** 11 | * Class representation of a currency item in a user's inventory. 12 | * 13 | * @author nosoop 14 | */ 15 | public class TradeInternalCurrency extends TradeInternalAsset { 16 | long currencyid; 17 | int tradedAmount; 18 | 19 | public TradeInternalCurrency(AppContextPair appContext, 20 | JSONObject rgCurrencyItem, JSONObject rgDescriptionItem) 21 | throws JSONException { 22 | super(appContext, rgCurrencyItem, rgDescriptionItem); 23 | 24 | currencyid = Long.parseLong(rgCurrencyItem.getString("id")); 25 | tradedAmount = 0; 26 | } 27 | 28 | public long getCurrencyId() { 29 | return currencyid; 30 | } 31 | 32 | /** 33 | * Sets the amount of currency added to the trade. 34 | */ 35 | public void setTradedAmount(int amount) { 36 | tradedAmount = amount; 37 | } 38 | 39 | /** 40 | * Returns the amount of currency added to the trade. 41 | */ 42 | public int getTradedAmount() { 43 | return tradedAmount; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /SteamTrade-Java/src/main/java/com/nosoop/steamtrade/inventory/TradeInternalInventories.java: -------------------------------------------------------------------------------- 1 | package com.nosoop.steamtrade.inventory; 2 | 3 | import bundled.steamtrade.org.json.JSONObject; 4 | import java.util.ArrayList; 5 | import java.util.HashMap; 6 | import java.util.List; 7 | import java.util.Map; 8 | 9 | /** 10 | * Represents a user's collection of game inventories, retrievable by the 11 | * inventory-specific appid and contextid. Also holds a varied list of 12 | * AssetBuilders to be used to load certain inventories. 13 | * 14 | * @author nosoop 15 | */ 16 | public class TradeInternalInventories { 17 | Map gameInventories; 18 | /** 19 | * A list of AssetBuilder instances that can load inventories. First 20 | * AssetBuilder that accepts an inventory takes precedence over other 21 | * AssetBuilders that accept the inventory that are lower in the list. 22 | */ 23 | final List inventoryLoaders; 24 | /** 25 | * A default AssetBuilder instance. Accepts all inventories. 26 | */ 27 | final static AssetBuilder DEFAULT_ASSET_BUILDER = new AssetBuilder() { 28 | @Override 29 | public boolean isSupported(AppContextPair appContext) { 30 | return true; 31 | } 32 | }; 33 | 34 | /** 35 | * Creates a new TradeInternalInventories instance with an empty 36 | * AssetBuilder list. 37 | */ 38 | public TradeInternalInventories() { 39 | this(new ArrayList()); 40 | } 41 | 42 | /** 43 | * Creates a new TradeInternalInventories instance with a given list of 44 | * AssetBuilders to use. 45 | * 46 | * @param assetBuild A list of AssetBuilders to test inventory loading. 47 | */ 48 | public TradeInternalInventories(List assetBuild) { 49 | this.inventoryLoaders = assetBuild; 50 | this.gameInventories = new HashMap<>(); 51 | } 52 | 53 | public void addCachedInventory(AppContextPair appContext, JSONObject feed) { 54 | AssetBuilder asset = DEFAULT_ASSET_BUILDER; 55 | 56 | // Load the first supported AssetBuilder if any, else use default. 57 | for (AssetBuilder build : inventoryLoaders) { 58 | if (build.isSupported(appContext)) { 59 | asset = build; 60 | break; 61 | } 62 | } 63 | 64 | TradeInternalInventory inv = 65 | new TradeInternalInventory(feed, appContext, asset); 66 | inv.wasCached = true; 67 | 68 | gameInventories.put(appContext, inv); 69 | } 70 | 71 | /** 72 | * Adds an inventory to the collection using a given AppContextPair and 73 | * loads it using the supplied inventory data JSONObject. 74 | * 75 | * @param appContext 76 | * @param feed 77 | */ 78 | public void addInventory(AppContextPair appContext, JSONObject feed) { 79 | AssetBuilder asset = DEFAULT_ASSET_BUILDER; 80 | 81 | // Load the first supported AssetBuilder if any, else use default. 82 | for (AssetBuilder build : inventoryLoaders) { 83 | if (build.isSupported(appContext)) { 84 | asset = build; 85 | break; 86 | } 87 | } 88 | 89 | gameInventories.put(appContext, 90 | new TradeInternalInventory(feed, appContext, asset)); 91 | } 92 | 93 | /** 94 | * Adds a new, empty inventory to the collection with just an 95 | * AppContextPair. 96 | * 97 | * @param appContext 98 | */ 99 | public void addInventory(AppContextPair appContext) { 100 | AssetBuilder asset = DEFAULT_ASSET_BUILDER; 101 | 102 | // Load the first supported AssetBuilder if any, else use default. 103 | for (AssetBuilder build : inventoryLoaders) { 104 | if (build.isSupported(appContext)) { 105 | asset = build; 106 | break; 107 | } 108 | } 109 | 110 | gameInventories.put(appContext, 111 | new TradeInternalInventory(appContext, asset)); 112 | } 113 | 114 | /** 115 | * Returns a boolean value stating if the inventory collection contains a 116 | * specific inventory. 117 | * 118 | * @param appid A game's appid. 119 | * @param contextid An game's inventory contextid. 120 | * @return Whether or not the inventory map contains a key value 121 | * AppContextPair represented by the given appid and contextid. 122 | */ 123 | public boolean hasInventory(int appid, long contextid) { 124 | return hasInventory(getInventoryKey(appid, contextid)); 125 | } 126 | 127 | /** 128 | * Returns a boolean value stating if the inventory collection contains a 129 | * specific inventory. 130 | * 131 | * @param contextdata An AppContextPair representing a game inventory to 132 | * check for. 133 | * @return Whether or not the inventory map contains a key value of the 134 | * given AppContextPair. 135 | */ 136 | public boolean hasInventory(AppContextPair contextdata) { 137 | return gameInventories.containsKey(contextdata); 138 | } 139 | 140 | /** 141 | * @param appid 142 | * @param contextid 143 | * @return TradeInternalInventory for the given appid and contextid. 144 | */ 145 | public TradeInternalInventory getInventory(int appid, long contextid) { 146 | return gameInventories.get(getInventoryKey(appid, contextid)); 147 | } 148 | 149 | /** 150 | * @param contextdata 151 | * @return TradeInternalInventory for the given AppContextPair. 152 | */ 153 | public TradeInternalInventory getInventory(AppContextPair contextdata) { 154 | return gameInventories.get(contextdata); 155 | } 156 | 157 | /** 158 | * @param appid 159 | * @param contextid 160 | * @return An unnamed AppContextPair. 161 | */ 162 | private AppContextPair getInventoryKey(int appid, long contextid) { 163 | return new AppContextPair(appid, contextid); 164 | } 165 | 166 | /** 167 | * @return A list of all available / known inventories held by this object. 168 | */ 169 | public List getInventories() { 170 | return new ArrayList<>(gameInventories.values()); 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /SteamTrade-Java/src/main/java/com/nosoop/steamtrade/inventory/TradeInternalInventory.java: -------------------------------------------------------------------------------- 1 | package com.nosoop.steamtrade.inventory; 2 | 3 | import bundled.steamtrade.org.json.JSONObject; 4 | import bundled.steamtrade.org.json.JSONException; 5 | import com.nosoop.steamtrade.TradeListener.TradeStatusCodes; 6 | import java.util.ArrayList; 7 | import java.util.HashMap; 8 | import java.util.List; 9 | import java.util.Map; 10 | import java.util.Set; 11 | 12 | /** 13 | * Represents a Steam user's inventory as displayed in trading and from viewing 14 | * an inventory online. 15 | * 16 | * @author nosoop (ported and adapted from ForeignInventory in the SteamBot 17 | * project by Jessecar96) 18 | */ 19 | public class TradeInternalInventory { 20 | boolean inventoryValid; 21 | String errorMessage; 22 | /** 23 | * List of items in the inventory, mapped to its assetid. 24 | * 25 | * Opted for a map for performance benefits when looking up an item by its 26 | * assetid in large inventories. 27 | */ 28 | Map inventoryItems; 29 | /** 30 | * List of currency items in the inventory, mapped to its currencyid. 31 | * 32 | * Should be fine as a list, but for now. 33 | */ 34 | Map currencyItems; 35 | /** 36 | * The appid-contextid pair this inventory represents. 37 | */ 38 | final AppContextPair appContext; 39 | /** 40 | * The AssetBuilder instance used to load this inventory. 41 | */ 42 | final AssetBuilder assetBuilder; 43 | /** 44 | * Whether or not there is more to load in the inventory. 45 | */ 46 | boolean hasMore; 47 | /** 48 | * The start position to load from. 49 | */ 50 | int moreStartPosition; 51 | /** 52 | * Whether or not this inventory was cached. 53 | */ 54 | boolean wasCached; 55 | 56 | /** 57 | * Creates a new, empty inventory. 58 | * 59 | * @param appContext The appid-contextid pair this inventory represents. 60 | * @param assetBuilder An AssetBuilder instance to load the inventory data. 61 | */ 62 | public TradeInternalInventory(AppContextPair appContext, 63 | AssetBuilder assetBuilder) { 64 | this.appContext = appContext; 65 | this.assetBuilder = assetBuilder; 66 | 67 | inventoryValid = false; 68 | 69 | inventoryItems = new HashMap<>(); 70 | currencyItems = new HashMap<>(); 71 | 72 | hasMore = false; 73 | moreStartPosition = 0; 74 | } 75 | 76 | /** 77 | * Takes a JSON data feed received from loading our inventory and creates a 78 | * representation of. 79 | * 80 | * @param json A JSONObject representation of the inventory to load. 81 | * @param appContext The appid-contextid pair this inventory represents. 82 | * @param assetBuilder An AssetBuilder instance to load the inventory data. 83 | */ 84 | public TradeInternalInventory(JSONObject json, AppContextPair appContext, 85 | AssetBuilder assetBuilder) { 86 | this.appContext = appContext; 87 | this.assetBuilder = assetBuilder; 88 | 89 | inventoryValid = false; 90 | 91 | inventoryItems = new HashMap<>(); 92 | currencyItems = new HashMap<>(); 93 | 94 | hasMore = false; 95 | moreStartPosition = 0; 96 | 97 | try { 98 | if (json.getBoolean("success")) { 99 | parseInventory(json); 100 | } 101 | } catch (JSONException e) { 102 | e.printStackTrace(); 103 | } 104 | } 105 | 106 | /** 107 | * For large inventories, load additional inventory data. 108 | * 109 | * @param json 110 | */ 111 | public void loadMore(JSONObject json) { 112 | try { 113 | if (json.getBoolean("success")) { 114 | parseInventory(json); 115 | } else { 116 | inventoryValid = false; 117 | errorMessage = json.optString("error", 118 | TradeStatusCodes.EMPTY_MESSAGE); 119 | } 120 | } catch (JSONException e) { 121 | e.printStackTrace(); 122 | } 123 | } 124 | 125 | /** 126 | * Gets the AppContextPair associated with the inventory. Example: A Team 127 | * Fortress 2 inventory would return an AppContextPair equal to 128 | * new AppContextPair(440, 2); 129 | * 130 | * @return An AppContextPair "key" representing this instance. 131 | */ 132 | public AppContextPair getAppContextPair() { 133 | return appContext; 134 | } 135 | 136 | /** 137 | * Gets the user's available trading inventory. 138 | * 139 | * @return A List containing all the available TradeInternalItem instances. 140 | */ 141 | public List getItemList() { 142 | return new ArrayList<>(inventoryItems.values()); 143 | } 144 | 145 | /** 146 | * Gets the user's available currency items for the game. 147 | * 148 | * @return A List containing all available TradeInternalCurrency instances. 149 | */ 150 | public List getCurrencyList() { 151 | return new ArrayList<>(currencyItems.values()); 152 | } 153 | 154 | /** 155 | * Returns whether or not the inventory loading was successful. 156 | * 157 | * @return 158 | */ 159 | public boolean isValid() { 160 | return inventoryValid; 161 | } 162 | 163 | /** 164 | * Returns the error message associated with the error response. 165 | * 166 | * @return The JSON error message from the response if the inventory loading 167 | * was unsuccessful, or an empty string if it was. 168 | */ 169 | public String getErrorMessage() { 170 | if (!inventoryValid) { 171 | return errorMessage; 172 | } else { 173 | return ""; 174 | } 175 | } 176 | 177 | /** 178 | * Retrieves an item by its assetid. 179 | * 180 | * @param assetid The assetid of the TradeInternalItem to get. 181 | * @return A TradeInternalItem instance if available, or an instance of null 182 | * if not. 183 | */ 184 | public TradeInternalItem getItem(long assetid) { 185 | if (inventoryItems.containsKey(assetid)) { 186 | return inventoryItems.get(assetid); 187 | } 188 | return TradeInternalItem.UNAVAILABLE; 189 | } 190 | 191 | /** 192 | * Retrieves a currency item by its currencyid 193 | * 194 | * @param currencyid The currencyid of the TradeInternalCurrency to get. 195 | * @return A TradeInternalCurrency instance if available, or an instance of 196 | * null if not. 197 | */ 198 | public TradeInternalCurrency getCurrency(long currencyid) { 199 | if (currencyItems.containsKey(currencyid)) { 200 | return currencyItems.get(currencyid); 201 | } 202 | return null; 203 | } 204 | 205 | /** 206 | * Helper method to parse out the JSON inventory format. 207 | * 208 | * @param json JSONObject representing inventory to be parsed. 209 | * @throws JSONException 210 | */ 211 | private void parseInventory(final JSONObject json) throws JSONException { 212 | inventoryValid = true; 213 | 214 | // Convenience map to associate class/instance to description. 215 | Map descriptions = new HashMap<>(); 216 | JSONObject rgDescriptions = json.optJSONObject("rgDescriptions"); 217 | for (final String rgDescriptionKey 218 | : (Set) rgDescriptions.keySet()) { 219 | JSONObject rgDescriptionItem = 220 | rgDescriptions.getJSONObject(rgDescriptionKey); 221 | 222 | int classid = rgDescriptionItem.getInt("classid"); 223 | long instanceid = rgDescriptionItem.getLong("instanceid"); 224 | 225 | descriptions.put(new ClassInstancePair(classid, instanceid), 226 | rgDescriptionItem); 227 | } 228 | 229 | // Add non-currency items. 230 | JSONObject rgInventory = json.optJSONObject("rgInventory"); 231 | if (rgInventory != null) { 232 | for (String rgInventoryItem : (Set) rgInventory.keySet()) { 233 | JSONObject invInstance = 234 | rgInventory.getJSONObject(rgInventoryItem); 235 | 236 | ClassInstancePair itemCI = new ClassInstancePair( 237 | Integer.parseInt(invInstance.getString("classid")), 238 | Long.parseLong( 239 | invInstance.optString("instanceid", "0"))); 240 | 241 | try { 242 | TradeInternalItem generatedItem = 243 | assetBuilder.generateItem(appContext, invInstance, 244 | descriptions.get(itemCI)); 245 | 246 | inventoryItems.put(generatedItem.assetid, generatedItem); 247 | } catch (JSONException e) { 248 | e.printStackTrace(); 249 | } 250 | } 251 | } 252 | 253 | // Add currency items 254 | JSONObject rgCurrency = json.optJSONObject("rgCurrency"); 255 | if (rgCurrency != null) { 256 | for (String rgCurrencyItem : (Set) rgCurrency.keySet()) { 257 | JSONObject invInstance = 258 | rgCurrency.getJSONObject(rgCurrencyItem); 259 | 260 | ClassInstancePair itemCI = new ClassInstancePair( 261 | Integer.parseInt(invInstance.getString("classid")), 262 | Long.parseLong(invInstance.optString("instanceid", "0"))); 263 | 264 | try { 265 | TradeInternalCurrency generatedItem = 266 | assetBuilder.generateCurrency(appContext, 267 | invInstance, descriptions.get(itemCI)); 268 | 269 | currencyItems.put(generatedItem.assetid, generatedItem); 270 | } catch (JSONException e) { 271 | e.printStackTrace(); 272 | } 273 | } 274 | } 275 | 276 | // Check if there is more to the inventory. 277 | hasMore = json.optBoolean("more"); 278 | 279 | if (hasMore) { 280 | // Shift the start position. 281 | moreStartPosition = json.getInt("more_start"); 282 | } 283 | } 284 | 285 | public boolean hasMore() { 286 | return this.hasMore; 287 | } 288 | 289 | public int getMoreStartPosition() { 290 | return this.moreStartPosition; 291 | } 292 | 293 | // TODO Write toJSONObject() method to support inventory saving? 294 | /** 295 | * Utility class to identify class-instance pairs. 296 | * 297 | * @author nosoop < nosoop at users.noreply.github.com > 298 | */ 299 | protected static class ClassInstancePair { 300 | int classid; 301 | long instanceid; 302 | 303 | /** 304 | * Creates a class-instance pair. 305 | * 306 | * @param classid 307 | * @param instanceid 308 | */ 309 | ClassInstancePair(int classid, long instanceid) { 310 | this.classid = classid; 311 | this.instanceid = instanceid; 312 | } 313 | 314 | @Override 315 | public int hashCode() { 316 | return 497 * classid + (int) instanceid; 317 | } 318 | 319 | @Override 320 | public boolean equals(Object obj) { 321 | if (obj == null) { 322 | return false; 323 | } 324 | if (getClass() != obj.getClass()) { 325 | return false; 326 | } 327 | final ClassInstancePair other = (ClassInstancePair) obj; 328 | if (this.classid != other.classid) { 329 | return false; 330 | } 331 | if (this.instanceid != other.instanceid) { 332 | return false; 333 | } 334 | return true; 335 | } 336 | } 337 | 338 | } -------------------------------------------------------------------------------- /SteamTrade-Java/src/main/java/com/nosoop/steamtrade/inventory/TradeInternalItem.java: -------------------------------------------------------------------------------- 1 | package com.nosoop.steamtrade.inventory; 2 | 3 | import bundled.steamtrade.org.json.JSONException; 4 | import bundled.steamtrade.org.json.JSONObject; 5 | 6 | /** 7 | * Class representation of an item in a user's inventory. 8 | * 9 | * @author nosoop 10 | */ 11 | public class TradeInternalItem extends TradeInternalAsset { 12 | public static final TradeInternalItem UNAVAILABLE = null; 13 | long instanceid; 14 | boolean stackable; 15 | 16 | public TradeInternalItem(AppContextPair appContext, 17 | JSONObject rgInventoryItem, JSONObject rgDescriptionItem) 18 | throws JSONException { 19 | super(appContext, rgInventoryItem, rgDescriptionItem); 20 | 21 | this.instanceid = 22 | Long.parseLong(rgDescriptionItem.optString("instanceid", "0")); 23 | 24 | this.stackable = (amount > 1); 25 | } 26 | 27 | public long getInstanceid() { 28 | return instanceid; 29 | } 30 | 31 | public boolean isStackable() { 32 | return stackable; 33 | } 34 | 35 | @Override 36 | public String getDisplayName() { 37 | if (getType().isEmpty()) { 38 | return super.getDisplayName(); 39 | } 40 | return String.format("%s (%s)", super.getDisplayName(), this.getType()); 41 | } 42 | } -------------------------------------------------------------------------------- /SteamTrade-Java/src/main/java/com/nosoop/steamtrade/status/Status.java: -------------------------------------------------------------------------------- 1 | package com.nosoop.steamtrade.status; 2 | 3 | import com.nosoop.steamtrade.TradeListener.TradeStatusCodes; 4 | import java.util.ArrayList; 5 | import java.util.List; 6 | import bundled.steamtrade.org.json.JSONArray; 7 | import bundled.steamtrade.org.json.JSONException; 8 | import bundled.steamtrade.org.json.JSONObject; 9 | 10 | /** 11 | * Object representing current trade state. (No modifications.) 12 | * 13 | * @author Top-Cat 14 | */ 15 | public class Status { 16 | public String error; 17 | public boolean newversion; 18 | public boolean success; 19 | public int trade_status = -1; 20 | public int version; 21 | public int logpos; 22 | public TradeUserStatus me, them; 23 | public List events = new ArrayList<>(); 24 | 25 | @SuppressWarnings("unchecked") 26 | public Status(JSONObject obj) throws JSONException { 27 | success = obj.getBoolean("success"); 28 | 29 | if (success) { 30 | error = "None"; 31 | trade_status = obj.getInt("trade_status"); 32 | 33 | if (trade_status == TradeStatusCodes.STATUS_OK) { 34 | newversion = obj.getBoolean("newversion"); 35 | version = obj.getInt("version"); 36 | 37 | logpos = obj.optInt("logpos"); 38 | 39 | me = new TradeUserStatus(obj.getJSONObject("me")); 40 | them = new TradeUserStatus(obj.getJSONObject("them")); 41 | 42 | JSONArray statusEvents = obj.optJSONArray("events"); 43 | 44 | if (statusEvents != null) { 45 | for (int i = 0; i < statusEvents.length(); i++) { 46 | events.add( 47 | new TradeEvent(statusEvents.getJSONObject(i))); 48 | } 49 | } 50 | } 51 | } else { 52 | error = obj.optString("error", "No error message."); 53 | 54 | /** 55 | * If there is an error we should know about that isn't defined, we 56 | * should add a custom status code to pick it up. 57 | */ 58 | trade_status = obj.optInt("trade_status", 59 | TradeStatusCodes.STATUS_ERRORMESSAGE); 60 | } 61 | } 62 | } -------------------------------------------------------------------------------- /SteamTrade-Java/src/main/java/com/nosoop/steamtrade/status/TradeAssetsObj.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this template, choose Tools | Templates 3 | * and open the template in the editor. 4 | */ 5 | package com.nosoop.steamtrade.status; 6 | 7 | import bundled.steamtrade.org.json.JSONException; 8 | import bundled.steamtrade.org.json.JSONObject; 9 | 10 | /** 11 | * 12 | * 13 | * @author nosoop 14 | */ 15 | public class TradeAssetsObj { 16 | 17 | int appid; 18 | long contextid; 19 | long assetid; 20 | 21 | long amount; 22 | 23 | TradeAssetsObj(JSONObject obj) throws JSONException { 24 | this.appid = Integer.parseInt(obj.getString("appid")); 25 | this.contextid = Long.parseLong(obj.getString("contextid")); 26 | this.assetid = Long.parseLong(obj.getString("assetid")); 27 | 28 | if (obj.has("amount")) { 29 | this.amount = Long.parseLong(obj.getString("amount")); 30 | } 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /SteamTrade-Java/src/main/java/com/nosoop/steamtrade/status/TradeEvent.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this template, choose Tools | Templates 3 | * and open the template in the editor. 4 | */ 5 | package com.nosoop.steamtrade.status; 6 | 7 | import bundled.steamtrade.org.json.JSONException; 8 | import bundled.steamtrade.org.json.JSONObject; 9 | import com.nosoop.steamtrade.TradeListener; 10 | 11 | /** 12 | * @author nosoop 13 | */ 14 | public class TradeEvent { 15 | public String steamid; 16 | public int action; 17 | public long timestamp; 18 | public int appid; 19 | public String text; 20 | public long contextid; 21 | public long assetid; 22 | // Currency-based. 23 | public int amount; 24 | public long currencyid; 25 | JSONObject jsonObject; 26 | 27 | public static class TradeAction { 28 | public static final int // Reference to trade action IDs 29 | // Item added (itemid = "assetid") 30 | ITEM_ADDED = 0, 31 | // Item removed (itemid = "assetid") 32 | ITEM_REMOVED = 1, 33 | // Toggle ready 34 | READY_TOGGLED = 2, 35 | // Toggle not ready 36 | READY_UNTOGGLED = 3, 37 | // Trade accepted 38 | TRADE_ACCEPTED = 4, 39 | // ? - maybe some sort of cancel 40 | UNKNOWN_EVENT_5 = 5, 41 | // Add / remove currency. 42 | // (SK Crowns / Energy are, other stackables are not.) 43 | CURRENCY_CHANGED = 6, 44 | // Chat (message = "text") 45 | MESSAGE_ADDED = 7, 46 | // Update stackable item count? Initial add uses ITEM_ADDED. 47 | STACKABLE_CHANGED = 8; 48 | } 49 | 50 | TradeEvent(JSONObject event) throws JSONException { 51 | jsonObject = event; 52 | 53 | steamid = event.getString("steamid"); 54 | action = event.optInt("action", 55 | TradeListener.TradeStatusCodes.TRADEEVENT_ACTION_MISSING); 56 | timestamp = event.getLong("timestamp"); 57 | appid = event.getInt("appid"); 58 | text = event.optString("text"); 59 | 60 | // contextid required for private inventory only. 61 | contextid = event.optLong("contextid"); 62 | 63 | // assetid required for getting item info from public inventory. 64 | assetid = event.optLong("assetid"); 65 | 66 | // Amount is required when dealing in currency 67 | amount = event.optInt("amount", 1); 68 | 69 | // Currency ID is also required when dealing in currency. 70 | // Might not be available. 71 | currencyid = event.optLong("currencyid"); 72 | } 73 | 74 | public JSONObject getJSONObject() { 75 | return jsonObject; 76 | } 77 | } -------------------------------------------------------------------------------- /SteamTrade-Java/src/main/java/com/nosoop/steamtrade/status/TradeUserStatus.java: -------------------------------------------------------------------------------- 1 | package com.nosoop.steamtrade.status; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | import bundled.steamtrade.org.json.JSONException; 6 | import bundled.steamtrade.org.json.JSONArray; 7 | import bundled.steamtrade.org.json.JSONObject; 8 | 9 | /** 10 | * Represents user status in trade. 11 | * 12 | * @author nosoop 13 | */ 14 | public class TradeUserStatus { 15 | 16 | public boolean ready; 17 | public boolean confirmed; 18 | public int sec_since_touch; 19 | public List assets; 20 | 21 | TradeUserStatus(JSONObject obj) throws JSONException { 22 | ready = obj.getLong("ready") == 1; 23 | confirmed = obj.getLong("confirmed") == 1; 24 | sec_since_touch = obj.getInt("sec_since_touch"); 25 | 26 | // TODO Add asset support to update variable-item quantities. 27 | JSONArray assetsRef = obj.optJSONArray("assets"); 28 | 29 | if (assetsRef != null) { 30 | assets = new ArrayList<>(); 31 | 32 | for (int i = 0; i < assetsRef.length(); i++) { 33 | JSONObject asset = assetsRef.optJSONObject(i); 34 | assets.add(new TradeAssetsObj((JSONObject) asset)); 35 | } 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /SteamTrade-Java/src/test/java/com/nosoop/steamtrade/inventory/TradeInternalAssetTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this template, choose Tools | Templates 3 | * and open the template in the editor. 4 | */ 5 | package com.nosoop.steamtrade.inventory; 6 | 7 | import bundled.steamtrade.org.json.JSONException; 8 | import bundled.steamtrade.org.json.JSONObject; 9 | import bundled.steamtrade.org.json.JSONTokener; 10 | import junit.framework.TestCase; 11 | 12 | /** 13 | * A test for the TradeInternalAssetClass. 14 | * 15 | * @author nosoop < nosoop at users.noreply.github.com > 16 | */ 17 | public class TradeInternalAssetTest extends TestCase { 18 | final TradeInternalInventory INVENTORY; 19 | final TradeInternalAsset ASSET_CURRENCY, ASSET_ITEM; 20 | 21 | public TradeInternalAssetTest(String testName) throws JSONException { 22 | super(testName); 23 | 24 | /** 25 | * Load inventory data from test resource. 26 | */ 27 | final JSONObject INVENTORY_DATA = 28 | new JSONObject(new JSONTokener( 29 | TradeInternalAssetTest.class 30 | .getResourceAsStream("/inventorytest_99900.json"))); 31 | /** 32 | * Use original AppContextPair data. (This is the same one used in my 33 | * personal inventory.) 34 | */ 35 | final AppContextPair APP_CONTEXT = new AppContextPair(99900, 4062807L); 36 | /** 37 | * Use DEFAULT_ASSET_BUILDER as defined from TradeInternalInventories. 38 | */ 39 | final AssetBuilder DEFAULT_ASSET_BUILDER = 40 | TradeInternalInventories.DEFAULT_ASSET_BUILDER; 41 | 42 | INVENTORY = new TradeInternalInventory(INVENTORY_DATA, APP_CONTEXT, 43 | DEFAULT_ASSET_BUILDER); 44 | 45 | ASSET_CURRENCY = INVENTORY.getCurrency(1L); 46 | ASSET_ITEM = INVENTORY.getItem(101220214L); 47 | } 48 | 49 | /** 50 | * Sets up the test, called before every test case method. 51 | * 52 | * @throws Exception 53 | */ 54 | @Override 55 | protected void setUp() throws Exception { 56 | super.setUp(); 57 | } 58 | 59 | /** 60 | * Tears down the test, called after every test case method. 61 | * 62 | * @throws Exception 63 | */ 64 | @Override 65 | protected void tearDown() throws Exception { 66 | super.tearDown(); 67 | } 68 | 69 | /** 70 | * Test of getName method, of class TradeInternalAsset. 71 | */ 72 | public void testGetName() { 73 | System.out.println("getName"); 74 | assertEquals("Crowns", ASSET_CURRENCY.getName()); 75 | assertEquals("Blue Shard", ASSET_ITEM.getName()); 76 | } 77 | 78 | /** 79 | * Test of getMarketName method, of class TradeInternalAsset. 80 | */ 81 | public void testGetMarketName() { 82 | System.out.println("getMarketName"); 83 | assertEquals("", ASSET_CURRENCY.getMarketName()); 84 | assertEquals("", ASSET_ITEM.getMarketName()); 85 | } 86 | 87 | /** 88 | * Test of getAppid method, of class TradeInternalAsset. 89 | */ 90 | public void testGetAppid() { 91 | System.out.println("getAppid"); 92 | assertEquals(99900, ASSET_CURRENCY.getAppid()); 93 | assertEquals(99900, ASSET_ITEM.getAppid()); 94 | } 95 | 96 | /** 97 | * Test of getContextid method, of class TradeInternalAsset. 98 | */ 99 | public void testGetContextid() { 100 | System.out.println("getContextid"); 101 | assertEquals(4062807L, ASSET_CURRENCY.getContextid()); 102 | assertEquals(4062807L, ASSET_ITEM.getContextid()); 103 | } 104 | 105 | /** 106 | * Test of getAssetid method, of class TradeInternalAsset. 107 | */ 108 | public void testGetAssetid() { 109 | System.out.println("getAssetid"); 110 | assertEquals(1L, ASSET_CURRENCY.getAssetid()); 111 | assertEquals(101220214L, ASSET_ITEM.getAssetid()); 112 | } 113 | 114 | /** 115 | * Test of getClassid method, of class TradeInternalAsset. 116 | */ 117 | public void testGetClassid() { 118 | System.out.println("getClassid"); 119 | assertEquals(2407624, ASSET_CURRENCY.getClassid()); 120 | assertEquals(4994733, ASSET_ITEM.getClassid()); 121 | } 122 | 123 | /** 124 | * Test of getAmount method, of class TradeInternalAsset. 125 | */ 126 | public void testGetAmount() { 127 | System.out.println("getAmount"); 128 | assertEquals(550328, ASSET_CURRENCY.getAmount()); 129 | assertEquals(281, ASSET_ITEM.getAmount()); 130 | } 131 | 132 | /** 133 | * Test of getType method, of class TradeInternalAsset. 134 | */ 135 | public void testGetType() { 136 | System.out.println("getType"); 137 | assertEquals("Currency", ASSET_CURRENCY.getType()); 138 | assertEquals("Material", ASSET_ITEM.getType()); 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /SteamTrade-Java/src/test/resources/TEST_README.md: -------------------------------------------------------------------------------- 1 | Test Files 2 | ========== 3 | `inventorytest_99900.json` is a sample file to test the functionality of the TradeInternalAsset class. Sourced from Spiral Knights, it includes one currency item (Crowns), one stackable item (Blue Shard), and one non-stackable item (Heart Pendant). 4 | 5 | Hopefully more test files will be added later for other stuff, like maybe to help finish up the TradeListener implementation to support currencies and stackables events. 6 | 7 | While we're here, I wonder how easy it'd be to download updated inventory data and run tests against that? 8 | -------------------------------------------------------------------------------- /SteamTrade-Java/src/test/resources/inventorytest_99900.json: -------------------------------------------------------------------------------- 1 | { 2 | "more" : false, 3 | "more_start" : false, 4 | "rgCurrency" : { 5 | "1" : { 6 | "amount" : "550328", 7 | "classid" : "2407624", 8 | "id" : "1", 9 | "is_currency" : true, 10 | "pos" : 1 11 | } 12 | }, 13 | "rgDescriptions" : { 14 | "2407624_0" : { 15 | "actions" : [ 16 | { 17 | "link" : "http://wiki.spiralknights.com/Crowns", 18 | "name" : "COMMUNITY COMMENTS..." 19 | } 20 | ], 21 | "appid" : "99900", 22 | "background_color" : "273744", 23 | "classid" : "2407624", 24 | "descriptions" : [ 25 | { 26 | "value" : "Crowns are the currency of the Clockworks. You can use them to purchase all kinds of things like new weapons and armor. While playing with others, every party member gets crowns whenever anyone picks some up." 27 | } 28 | ], 29 | "icon_drag_url" : "", 30 | "icon_url" : "y01HvpaEKX_7Z19mJaVdyHMBjQ-b4Sd9LayIeMFDEWMRSmxB1pR6YEvjDpQBh1oCL0HKUJuvbTMn4oxwjlcEJRZCL0PbknMnXOJPgAC1UgMtW4kRj-RgMzml2CSLCFJrUxg4E46YKjsL8ULECtlVUiJDmE_Y4jhrLP_cKN1SVzNRSmIM2pJvbFriHcdA2lUOKkbAStvs", 31 | "instanceid" : "0", 32 | "market_name" : "", 33 | "marketable" : 0, 34 | "name" : "Crowns", 35 | "name_color" : "FAD59D", 36 | "owner_actions" : "", 37 | "owner_descriptions" : "", 38 | "tradable" : 1, 39 | "type" : "Currency" 40 | }, 41 | "4994733_0" : { 42 | "actions" : [ 43 | { 44 | "link" : "http://wiki.spiralknights.com/Blue_Shard", 45 | "name" : "COMMUNITY COMMENTS..." 46 | } 47 | ], 48 | "appid" : "99900", 49 | "background_color" : "273744", 50 | "classid" : "4994733", 51 | "descriptions" : [ 52 | { 53 | "value" : "A shard of Moonstone that is too small to be used in powering gates but highly useful in alchemy." 54 | }, 55 | { 56 | "label" : "Rating", 57 | "type" : "html", 58 | "value" : "☆☆☆☆☆" 59 | } 60 | ], 61 | "icon_drag_url" : "", 62 | "icon_url" : "vQGl2-QAWpbpFle-bXCQcAVNb2rpZVSUP92AoImW3NtnBo4kpBAJiVmSBkxJUpe6WQ0oNekrHto1k4SoxoLJnWAOzSapFgDORI9eSkhLwvwUFnJ59TEk1jnIsqLCiILCewDdJ6sKBtwZ0B9OQgqV7VkNKyn8axrebtnaoJ-Gyot0Vtt9-ktW1BTZEUwQDproS1p0dvUtRo5ricumyInDwChQ23_sGgGNQpMVHhYLlagPVml-_y1Gi3aM2P2W1pWHJlA=", 63 | "instanceid" : "0", 64 | "market_name" : "", 65 | "marketable" : 0, 66 | "name" : "Blue Shard", 67 | "name_color" : "B2B2B2", 68 | "owner_actions" : "", 69 | "owner_descriptions" : "", 70 | "tradable" : 1, 71 | "type" : "Material" 72 | }, 73 | "5016765_0" : { 74 | "actions" : [ 75 | { 76 | "link" : "http://wiki.spiralknights.com/Heart_Pendant", 77 | "name" : "COMMUNITY COMMENTS..." 78 | } 79 | ], 80 | "appid" : "99900", 81 | "background_color" : "273744", 82 | "classid" : "5016765", 83 | "descriptions" : [ 84 | { 85 | "value" : "Level: 1 (complete)" 86 | }, 87 | { 88 | "value" : "This item will bind to you when equipped.\n\n" 89 | }, 90 | { 91 | "label" : "Rating", 92 | "type" : "html", 93 | "value" : "★☆☆☆☆" 94 | } 95 | ], 96 | "icon_drag_url" : "", 97 | "icon_url" : "KiuJSYZV3vxPXrH9zbdxfJJnQ_iLMND-mZVm4ylRPdfwLKK2xkWN4__a4A_plXa2zicEp4t-mrCT22LrZkUokfck4bTLQ4Sk4se4CeiMI_CDPF7rl2SgsJOBZ-NzDD3Q5XKmvNtE172-nqpY48B95JklBeqaP8_kx5Bu52YaLIngK_e-ykiIubmRqgrnz3qkmXxb54o3yuXJ0mjpa00_g7V6-vvLQ4bk-ZT_XLPPauCVYVPtijfP_87BM7c0G3iNtQ==", 98 | "instanceid" : "0", 99 | "market_name" : "", 100 | "marketable" : 0, 101 | "name" : "Heart Pendant", 102 | "name_color" : "3C64A9", 103 | "owner_actions" : "", 104 | "owner_descriptions" : "", 105 | "tradable" : 1, 106 | "type" : "Trinket" 107 | } 108 | }, 109 | "rgInventory" : { 110 | "101220214" : { 111 | "amount" : "281", 112 | "classid" : "4994733", 113 | "id" : "101220214", 114 | "instanceid" : "0", 115 | "pos" : 37 116 | }, 117 | "103993828" : { 118 | "amount" : "1", 119 | "classid" : "5016765", 120 | "id" : "103993828", 121 | "instanceid" : "0", 122 | "pos" : 72 123 | } 124 | }, 125 | "success" : true 126 | } 127 | --------------------------------------------------------------------------------