├── LICENSE ├── README.md └── steam.trade.offer.enhancer.user.js /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Michael Scholtz 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This script is no longer maintained. 2 | 3 | I recommend checking out this fork: [https://github.com/juliarose/steam-trade-offer-enhancer](https://github.com/juliarose/steam-trade-offer-enhancer) 4 | 5 | # Steam Trade Offer Enhancer 6 | 7 | Steam Trade Offer Enhancer is a nifty browser script to enhance trade offers available via steamcommunity.com. The script offers features such as auto item adder or quick summaries. 8 | 9 | ### Features 10 | 11 | * item auto adder - add multiple items with a single click 12 | * summaries - no need to count those 150 keys anymore 13 | * supports any game or inventory type 14 | * detects item quality, paints 15 | * works with all steamcommunity pages that display trade offers 16 | * automatically adds item if `for_item` URL parameter is present 17 | * warns user about uncraftable, gifted, restricted items 18 | * warns user about [rare TF2 keys](http://forums.backpack.tf/index.php?/topic/28864-just-unboxed/) 19 | * allows user to remove pending invalid trade offers 20 | * warns user about Escrow period 21 | 22 | ### Installation 23 | 24 | 1. Install [Greasemonkey (Firefox)](http://www.greasespot.net/) or [Tampermonkey (Chrome)](https://chrome.google.com/webstore/detail/tampermonkey/dhdgffkkebhmkfjojejmpbldmpobfkfo). 25 | 2. Open the [script in raw view](https://github.com/scholtzm/steam-trade-offer-enhancer/raw/master/steam.trade.offer.enhancer.user.js). 26 | 3. Install. 27 | 28 | You can also do the whole `git clone` procedure if you wish. 29 | 30 | ### Screenshots 31 | 32 | ![Trade Offers Page](http://i.imgur.com/JLUb7De.png) 33 | ![Trade Offer Window](http://i.imgur.com/6nhF69X.png) 34 | -------------------------------------------------------------------------------- /steam.trade.offer.enhancer.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name Steam Trade Offer Enhancer 3 | // @namespace http://steamcommunity.com/id/H_s_K/ 4 | // @description Browser script to enhance Steam trade offers. 5 | // @include /^https?:\/\/steamcommunity\.com\/(id|profiles)\/.*\/tradeoffers.*/ 6 | // @include /^https?:\/\/steamcommunity\.com\/tradeoffer.*/ 7 | // @version 1.4.2 8 | // @author HusKy 9 | // ==/UserScript== 10 | 11 | function getUrlParam(paramName) { 12 | var params = window.location.search.split(/\?|\&/); 13 | for(i = 0; i < params.length; i++) { 14 | var currentParam = params[i].split("="); 15 | if(currentParam[0] === paramName) { 16 | return currentParam[1]; 17 | } 18 | } 19 | } 20 | 21 | // array of dangerous descriptions 22 | var dangerous_descriptions = [ 23 | { 24 | tag: "uncraftable", 25 | description: "Not Usable in Crafting" 26 | }, 27 | { 28 | tag: "gifted", 29 | description: "Gift from:" 30 | } 31 | ]; 32 | 33 | // array of rare TF2 keys (defindexes) 34 | var rare_TF2_keys = [ 35 | "5049", "5067", "5072", "5073", 36 | "5079", "5081", "5628", "5631", 37 | "5632", "5713", "5716", "5717", 38 | "5762" 39 | ]; 40 | 41 | var tradeOfferPage = { 42 | evaluate_items: function(items) { 43 | var result = {}; 44 | 45 | result._total = items.find("div.trade_item").length; 46 | 47 | items.find("div.trade_item").each(function() { 48 | var img = jQuery(this).find("img").attr("src"); 49 | var quality = jQuery(this).css("border-top-color"); 50 | 51 | if(result[img] === undefined) 52 | result[img] = {}; 53 | 54 | if(result[img][quality] === undefined) { 55 | result[img][quality] = 1; 56 | } else { 57 | result[img][quality]++; 58 | } 59 | }); 60 | 61 | return result; 62 | }, 63 | 64 | dump_summary: function(tradeoffer, type, items) { 65 | if(items._total <= 0) return; 66 | 67 | var htmlstring = "Summary (" + items._total + " " + (items._total === 1 ? "item" : "items") + "):
"; 68 | 69 | for(var prop in items) { 70 | if(prop === "_total") continue; 71 | 72 | var item_type = items[prop]; 73 | for(var quality in item_type) { 74 | htmlstring += "" + item_type[quality] + ""; 75 | } 76 | } 77 | 78 | htmlstring += "

Items:
"; 79 | tradeoffer.find("div." + type + " > div.tradeoffer_items_header") 80 | .after("
" + htmlstring + "
"); 81 | }, 82 | 83 | attach_links: function(tradeoffer) { 84 | var avatar = tradeoffer.find("div.tradeoffer_items.primary").find("a.tradeoffer_avatar"); 85 | 86 | if(avatar.length > 0) { 87 | var profileUrl = avatar.attr("href").match(/^https?:\/\/steamcommunity\.com\/(id|profiles)\/(.*)/); 88 | if(profileUrl) { 89 | tradeoffer.find("div.tradeoffer_footer_actions").append(" | rep.tf"); 90 | } 91 | } 92 | } 93 | }; 94 | 95 | var tradeOfferWindow = { 96 | evaluate_items: function(items) { 97 | var result = {}; 98 | result._total = 0; 99 | result._warnings = []; 100 | 101 | var slot_inner = items.find("div.slot_inner"); 102 | 103 | slot_inner.each(function() { 104 | if(jQuery(this).html() !== "" && jQuery(this).html() !== null) { 105 | result._total++; 106 | var item = jQuery(this).find("div.item"); 107 | 108 | var img = item.find("img").attr("src"); 109 | var quality = item.css("border-top-color"); 110 | 111 | if(result[img] === undefined) 112 | result[img] = {}; 113 | 114 | if(result[img][quality] === undefined) { 115 | result[img][quality] = 1; 116 | } else { 117 | result[img][quality]++; 118 | } 119 | 120 | // let's check item's info 121 | var appid = item[0].id.split("_")[0].replace("item", ""); 122 | var contextid = item[0].id.split("_")[1]; 123 | var assetid = item[0].id.split("_")[2]; 124 | 125 | var inventory_item; 126 | if(items[0].id === "your_slots") 127 | inventory_item = unsafeWindow.g_rgAppContextData[appid].rgContexts[contextid] 128 | .inventory.rgInventory[assetid]; 129 | else 130 | inventory_item = unsafeWindow.g_rgPartnerAppContextData[appid].rgContexts[contextid] 131 | .inventory.rgInventory[assetid]; 132 | 133 | var descriptions = inventory_item.descriptions; 134 | var appdata = inventory_item.app_data; 135 | var fraudwarnings = inventory_item.fraudwarnings; 136 | 137 | var warning_text; 138 | 139 | if(typeof descriptions === "object") { 140 | descriptions.forEach(function(d1) { 141 | dangerous_descriptions.forEach(function(d2) { 142 | if(d1.value.indexOf(d2.description) > -1) { 143 | var warning_text = "Offer contains " + d2.tag + " item(s)."; 144 | if(result._warnings.indexOf(warning_text) === -1) 145 | result._warnings.push(warning_text); 146 | } 147 | }); 148 | }); 149 | } 150 | 151 | if(typeof appdata === "object" && typeof appdata.def_index === "string") { 152 | if(rare_TF2_keys.indexOf(appdata.def_index) > -1) { 153 | warning_text = "Offer contains rare TF2 key(s)."; 154 | if(result._warnings.indexOf(warning_text) === -1) 155 | result._warnings.push(warning_text); 156 | } 157 | } 158 | 159 | if(typeof fraudwarnings === "object") { 160 | fraudwarnings.forEach(function(text) { 161 | if(text.indexOf("restricted gift") > -1) { 162 | warning_text = "Offer contains restricted gift(s)."; 163 | if(result._warnings.indexOf(warning_text) === -1) 164 | result._warnings.push(warning_text); 165 | } 166 | }); 167 | } 168 | 169 | } 170 | }); 171 | 172 | return result; 173 | }, 174 | 175 | dump_summary: function(target, type, items) { 176 | if(items._total <= 0) return; 177 | 178 | var htmlstring = type + " summary (" + items._total + " " + (items._total === 1 ? "item" : "items") + "):
"; 179 | 180 | // item counts 181 | for(var prop in items) { 182 | if(prop.indexOf("_") === 0) continue; 183 | 184 | var item_type = items[prop]; 185 | for(var quality in item_type) { 186 | htmlstring += "" + item_type[quality] + ""; 187 | } 188 | } 189 | 190 | // warnings 191 | if(items._warnings.length > 0) { 192 | htmlstring += "
Warning:
"; 193 | items._warnings.forEach(function(warning, index) { 194 | htmlstring += warning; 195 | 196 | if(index < items._warnings.length - 1) { 197 | htmlstring += "
"; 198 | } 199 | }); 200 | htmlstring += "
"; 201 | } 202 | 203 | target.append(htmlstring); 204 | }, 205 | 206 | summarise: function() { 207 | var target = jQuery("div.tradeoffer_items_summary"); 208 | target.html(""); 209 | 210 | var mine = jQuery("div#your_slots"); 211 | var other = jQuery("div#their_slots"); 212 | 213 | var my_items = this.evaluate_items(mine); 214 | var other_items = this.evaluate_items(other); 215 | 216 | this.dump_summary(target, "My", my_items); 217 | if(other_items._total > 0) target.append("

"); 218 | this.dump_summary(target, "Their", other_items); 219 | }, 220 | 221 | init: function() { 222 | var self = this; 223 | 224 | // something is loading 225 | var isReady = jQuery("img[src$='throbber.gif']:visible").length <= 0; 226 | 227 | // our partner's inventory is also loading at this point 228 | var itemParamExists = getUrlParam("for_item") !== undefined; 229 | var hasBeenLoaded = true; 230 | 231 | if(itemParamExists) { 232 | // format: for_item=__ 233 | var item = getUrlParam("for_item").split("_"); 234 | hasBeenLoaded = jQuery("div#inventory_" + UserThem.strSteamId + "_" + item[0] + "_" + item[1]).length > 0; 235 | } 236 | 237 | if(isReady && (!itemParamExists || hasBeenLoaded)) { 238 | setTimeout(function() { 239 | self.summarise(); 240 | }, 500); 241 | 242 | return; 243 | } 244 | 245 | if(itemParamExists && hasBeenLoaded) { 246 | setTimeout(self.deadItem.bind(self), 5000); 247 | return; 248 | } 249 | 250 | setTimeout(function() { 251 | self.init(); 252 | }, 250); 253 | }, 254 | 255 | deadItem: function() { 256 | var deadItemExists = jQuery("a[href$='_undefined']").length > 0; 257 | var item = getUrlParam("for_item").split("_"); 258 | 259 | if(deadItemExists) { 260 | unsafeWindow.g_rgCurrentTradeStatus.them.assets = []; 261 | RefreshTradeStatus(g_rgCurrentTradeStatus, true); 262 | alert("Seems like the item you are looking to buy (ID: " + item[2] + ") is no longer available. You should check other user's backpack and see if it's still there."); 263 | } else { 264 | // Something was loading very slowly, restart init... 265 | this.init(); 266 | } 267 | }, 268 | 269 | clear: function(slots) { 270 | var timeout = 100; 271 | 272 | var added_items = jQuery(slots); 273 | var items = added_items.find("div.itemHolder").find("div.item"); 274 | 275 | for(i = 0; i < items.length; i++) { 276 | setTimeout(MoveItemToInventory, i * timeout, items[i]); 277 | } 278 | 279 | setTimeout(function() { 280 | tradeOfferWindow.summarise(); 281 | }, items.length * timeout + 500); 282 | } 283 | }; 284 | 285 | jQuery(function() { 286 | 287 | var location = window.location.pathname; 288 | 289 | // Append CSS style. 290 | var style = ""; 298 | jQuery(style).appendTo("head"); 299 | 300 | // Trade offer page with multiple trade offers ... 301 | if(location.indexOf("tradeoffers") > -1) { 302 | 303 | // Retrieve all trade offers. 304 | var trade_offers = jQuery("div.tradeoffer"); 305 | 306 | if(trade_offers.length > 0) { 307 | trade_offers.each(function() { 308 | var others = jQuery(this).find("div.primary > div.tradeoffer_item_list"); 309 | var mine = jQuery(this).find("div.secondary > div.tradeoffer_item_list"); 310 | 311 | // Evaluate both sides. 312 | var other_items = tradeOfferPage.evaluate_items(others); 313 | var my_items = tradeOfferPage.evaluate_items(mine); 314 | 315 | // Dump the summaries somewhere ... 316 | tradeOfferPage.dump_summary(jQuery(this), "primary", other_items); 317 | tradeOfferPage.dump_summary(jQuery(this), "secondary", my_items); 318 | 319 | // Check if trade offer is "unavailable" 320 | // Do this only for /tradeoffers page and nothing else 321 | var is_ok = location.indexOf("tradeoffers", location.length - "tradeoffers".length) !== -1; 322 | is_ok = is_ok || location.indexOf("tradeoffers/", location.length - "tradeoffers/".length) !== -1; 323 | 324 | if(is_ok) { 325 | // Attach links 326 | tradeOfferPage.attach_links(jQuery(this)); 327 | 328 | var is_unavailable = jQuery(this).find("div.tradeoffer_items_banner").text().indexOf("Items Now Unavailable For Trade") > -1; 329 | if(is_unavailable) { 330 | var trade_offer_id = jQuery(this).attr("id").split("_")[1]; 331 | var footer = jQuery(this).find("div.tradeoffer_footer"); 332 | 333 | var text = "This trade offer is stuck and invalid, but you can still decline it."; 334 | footer.prepend(""); 335 | } 336 | } 337 | }); 338 | } 339 | 340 | // Single trade offer window ... 341 | } else if(location.indexOf("tradeoffer") > -1) { 342 | 343 | // Append new divs ... 344 | jQuery("div.trade_left div.trade_box_contents").append("
"); 345 | jQuery("div.item_adder").append("
Add multiple items:
"); 346 | jQuery("div.item_adder").append(" "); 347 | jQuery("div.item_adder").append("

"); 348 | jQuery("div.item_adder").append(""); 349 | jQuery("div.item_adder").append(" "); 350 | 351 | jQuery("div.trade_left div.trade_box_contents").append("
"); 352 | 353 | // Refresh summaries whenever ... 354 | jQuery("body").click(function() { 355 | setTimeout(function() { 356 | tradeOfferWindow.summarise(); 357 | }, 500); 358 | }); 359 | 360 | // hack to fix empty space under inventory 361 | // TODO get rid of this if they ever fix it 362 | setInterval(function() { 363 | if(jQuery("#inventory_displaycontrols").height() > 50) { 364 | if(jQuery("div#inventories").css("marginBottom") === "8px") { 365 | jQuery("div#inventories").css("marginBottom", "7px"); 366 | } else { 367 | jQuery("div#inventories").css("marginBottom", "8px"); 368 | } 369 | } 370 | }, 500); 371 | 372 | // Handle item auto adder 373 | jQuery("button#btn_additems").click(function() { 374 | // Do not add items if the offer cannot be modified 375 | if(jQuery("div.modify_trade_offer:visible").length > 0) return; 376 | 377 | // Collect all items 378 | var inventory = jQuery("div.inventory_ctn:visible"); 379 | var items = inventory.find("div.itemHolder").filter(function() { 380 | return jQuery(this).css("display") !== "none"; 381 | }).find("div.item").filter(function() { 382 | return jQuery(this).css("display") !== "none"; 383 | }); 384 | 385 | // Get amount value 386 | var amount = parseInt(jQuery("input#amount_control").val()); 387 | if(isNaN(amount)) amount = 16; 388 | if(items.length < amount) amount = items.length; 389 | 390 | // Add all items 391 | for(i = 0; i < amount; i++) { 392 | setTimeout(MoveItemToTrade, i * 50, items[i]); 393 | } 394 | 395 | // Refresh summaries 396 | setTimeout(function() { 397 | tradeOfferWindow.summarise(); 398 | }, amount * 50 + 500); 399 | }); 400 | 401 | jQuery("button#btn_clearmyitems").click(function() { 402 | tradeOfferWindow.clear("div#your_slots"); 403 | }); 404 | 405 | jQuery("button#btn_cleartheiritems").click(function() { 406 | tradeOfferWindow.clear("div#their_slots"); 407 | }); 408 | 409 | tradeOfferWindow.init(); 410 | 411 | var itemParam = getUrlParam("for_item"); 412 | if(itemParam !== undefined) { 413 | var item = itemParam.split("_"); 414 | 415 | unsafeWindow.g_rgCurrentTradeStatus.them.assets.push({ 416 | "appid":item[0], 417 | "contextid":item[1], 418 | "assetid":item[2], 419 | "amount":1 420 | }); 421 | 422 | RefreshTradeStatus(g_rgCurrentTradeStatus, true); 423 | } 424 | 425 | if(unsafeWindow.g_daysMyEscrow > 0) { 426 | var hours = unsafeWindow.g_daysMyEscrow * 24; 427 | jQuery("div.trade_partner_headline").append("
(You do not have mobile confirmations enabled. Items will be held for " + hours + " hours.)
") 428 | } 429 | 430 | if(unsafeWindow.g_daysTheirEscrow > 0) { 431 | var hours = unsafeWindow.g_daysTheirEscrow * 24; 432 | jQuery("div.trade_partner_headline").append("
(Other user does not have mobile confirmations enabled. Items will be held for " + hours + " hours.)
") 433 | } 434 | } 435 | 436 | }); 437 | --------------------------------------------------------------------------------