您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Presents more useful info in Steam UI
// ==UserScript== // @name Steam Badger // @namespace capitalw_steam // @description Presents more useful info in Steam UI // @include http://steamcommunity.com/id/*/badges/* // @include http://steamcommunity.com/profiles/*/badges/* // @include http://steamcommunity.com/id/*/games*?tab=all* // @include http://steamcommunity.com/profiles/*/games*?tab=all* // @include http://steamcommunity.com/id/*/games*?tab=all&games_in_common=1 // @include http://steamcommunity.com/profiles/*/games*?tab=all&games_in_common=1 // @include http://steamcommunity.com/id/*/wishlist* // @include http://steamcommunity.com/profiles/*/wishlist* // @include http://steamcommunity.com/id/*/inventory/* // @include http://steamcommunity.com/profiles/*/inventory/* // @include http://steamcommunity.com/groups/* // @include http://steamcommunity.com/id/*/friends/ // @include http://steamcommunity.com/profiles/*/friends/ // @include http://steamcommunity.com/id/*/friendsthatplay/* // @include http://steamcommunity.com/profiles/*/friendsthatplay/* // @include http://store.steampowered.com/app/* // @require http://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js // @require http://ajax.googleapis.com/ajax/libs/jqueryui/1.10.3/jquery-ui.min.js // @version 1.0.18 // @grant unsafeWindow // @grant GM_getValue // @grant GM_setValue // @grant GM_getResourceText // @resource seedtag http://pastebin.com/raw.php?i=s7wFWwDS // ==/UserScript== var badgeGames = JSON.parse(GM_getValue("badgeGames", "[]")) var installedGames = JSON.parse(GM_getValue("installedGames", "[]")) var profilesData = JSON.parse(GM_getValue("profilesData", "{}")) var giftInventoryData = JSON.parse(GM_getValue("giftInventoryData", "{}")) var giftPrey = JSON.parse(GM_getValue("giftPrey", '[]')) var preyImage = '<div class="preyDiv"></div>' var badgesInstalled var uninstalledAreVisible = true var unresolvedConnections = 0 var MAX_CONNECTIONS = 4 var pendingAjaxCalls = [] var ajaxIntervalID = null var AJAX_DELAY = 2000 var GRAB_INVENTORY_DELAY = 1500 var MAX_INVENTORY_TRIES = 10 var untaggedGameIDs = null var gameTags = JSON.parse(GM_getValue("gameTags", "{}")) var gamesToNotTag = JSON.parse(GM_getValue("gamesToNotTag", "[]")) var gameRedirectedFrom = JSON.parse(GM_getValue("gameRedirectedFrom", "{}")) var gamesLackingTagsOnPage = JSON.parse(GM_getValue("gamesLackingTagsOnPage", "[]")) var filterGameTags = null //Only games found with MMO (but not Massively Multiplayer) are 42890, 107900, 212180, 239220, 31740. None of them are MMOs. //13630 is amazing. DLC with a messed up breadcrumb and no tags/genre. //This array is sorted for binary search var TAGS_TO_IGNORE = ["Early Access", "Includes Source SDK", "MMO", "Mods (require HL2)", "New releases"] var TAGS_TO_TRASH_GAME = ["Downloadable Content", "Game demo"] var LOAD_SEED_TAGS_THRESHOLD = 56 //If both 237621(batman DLC) and the puzzling 33340 (Dawn of Discover: Venice addon) both require the base game we can trash anything with the Downloadable Content tag //214490 is Alien: Isolation, which right now has no genre or tags. Awkward. Array.prototype.binarySearch = function(target) { var low = 0, high = this.length - 1, i, comparison while (low <= high) { i = Math.floor((low + high) / 2); if (this[i] < target) { low = i + 1; continue; } if (this[i] > target) { high = i - 1; continue; } return i } return -1 } Array.prototype.numericSort = function(){ this.sort(function(a,b){return a-b}) } function getTextForResourceFile(){ var result = "gameTags=" + JSON.stringify(gameTags) result += "\n\rgamesToNotTag="+JSON.stringify(gamesToNotTag) result += "\n\rgameRedirectedFrom="+JSON.stringify(gameRedirectedFrom) return result } //console.info(getTextForResourceFile()) function main(){ loadSeedTagging() var lastPath = getLastPath(window.location.pathname) var appID = getAppIDFromURL(window.location.pathname) if (appID) { if (window.location.pathname.indexOf("friendsthatplay") != -1) { var owners = getFriendOwners(appID) addFriendsWhoOwnUI(appID) addFriendsWhoLackUI(appID) return } else if (window.location.pathname.indexOf("app") != -1) { window.addEventListener("load", function(){ handleAppPage(appID) }, false) addScanForTagsButton() return } } if (isMyAccount()) { if (lastPath == "badges") { scanForRemaining() addTrimmerButton() } else if (lastPath == "friends") { handleFriendsListPage() } else if (lastPath == "games") { installMutationObserver( handlePersonalGames ) } else if (lastPath == "inventory") { window.addEventListener("load", window.setTimeout(function() { scanGiftInventoryData(1) }, 0), false) } else if (window.location.hash == "#members") { //addScanMembersButton() } } else { if (lastPath == "games") { installMutationObserver(function(){ scanProfileGamesOwned(); addTagFilter();}) addTagFilter() //For loading smaller lists on Chrome } else if (lastPath == "wishlist") { //console.info("On a wishlist") scanProfileGamesWishlist() showGiftCountsForWishlist() //installMutationObserver( function(){ scanProfileGamesWishlist(); showGiftCountsForWishlist()} ) // showGiftCountsForWishlist() //installMutationObserver( showGiftCountsForWishlist ) } } } function loadSeedTagging(){ if (Object.keys(gameRedirectedFrom).length < LOAD_SEED_TAGS_THRESHOLD){ var seedTagsSource = GM_getResourceText( "seedtag" ) baseUntaggable = loadVariableFromSource('gamesToNotTag', seedTagsSource) baseTags = loadVariableFromSource('gameTags', seedTagsSource) baseRedirects = loadVariableFromSource('gameRedirectedFrom', seedTagsSource) if (baseUntaggable) { gamesToNotTag = baseUntaggable GM_setValue("gamesToNotTag", JSON.stringify(gamesToNotTag)) } if (baseTags) { gameTags = baseTags GM_setValue("gameTags", JSON.stringify(gameTags)) } if (baseRedirects) { gameRedirectedFrom = baseRedirects GM_setValue("gameRedirectedFrom", JSON.stringify(gameRedirectedFrom)) } } } function loadVariableFromSource(varName, source){ try { var loaded = source.match(RegExp("^"+varName+"=(.*)$", "m")) return JSON.parse(loaded[1]) } catch (err){ return null } } function getLastPath(fullPath){ return fullPath.match(".+\/([^\/]+)")[1] } function isMyAccount(){ var userid = $("a.username").attr("href").match("\/(id|profiles)\/(.+)\/(.*)\/$") if (userid == null) { return false } var viewingid = $("span.profile_small_header_name > a.whiteLink").attr("href").match("\/(id|profiles)\/(.+)$") return (viewingid && userid[2] == viewingid[2]) } function getSteamIDFromPage(root){ var result = $(root).find("span.profile_small_header_name a.whiteLink") if (result.length > 0) { result = result.attr("href").match("\/profiles\/([0-9]+)$") } if (result) { return result[1] } result = getSteamIDByName(getProfileNameFromPage(root)) if (result) { return result } result = $('body script[type="text/javascript"]:eq(2)') if (result.length > 0) { result = result.html().match(/g_steamID = "(\d*)";$/m) if (result) { return result[1] } } return null } function getProfileNameFromPage(root){ return $(root).find("span.profile_small_header_name").text() } function getSteamIDByName(name){ for (var steamID in profilesData){ if (name == profilesData[steamID].name) { return steamID } } return null } function scanForRemaining(){ var gameRows = $("div.badge_title_row") badgeGames = new Array() var appName var appID = 0 gameRows.each( function(){ if ($(this).find("div.badge_title_stats:contains('remaining'):not(:contains('No'))").length == 1) { appID = $(this).find('div.badge_title_stats div.badge_title_playgame a').attr("href").match(/[0-9]*$/)[0] appName = $(this).find("div.badge_title").contents().filter(function() { return this.nodeType === 3; //Node.TEXT_NODE }).text().trim() badgeGames.push([appID, appName]) } }) GM_setValue("badgeGames", JSON.stringify(badgeGames)) badgesInstalled = getBadgeGamesInstalled() } function installMutationObserver(func){ var observer = new MutationObserver(checkInsertForInstallResults) observer.eventualCallback = func observer.observe( document.getElementById("games_list_row_container"), { childList: true, subtree: true }) } function checkInsertForInstallResults(mutations, observer){ for (var i =0; i< mutations.length;i++){ var mutant = mutations[i] //added wishlist_items part //console.info(mutant.target.className) if (mutant.addedNodes && mutant.addedNodes[0] && (mutant.target.id=="games_list_rows") ){ window.setTimeout(observer.eventualCallback, 50) break } } } function handlePersonalGames(){ scanForInstalledAndUpdateOwned() addTagFilter() } function scanForInstalledAndUpdateOwned(){ if (!isListingAllGames($("body"))){ return } installedGames = new Array() var gameRows = $("div.gameListRow") var appID = 0 var steamID = getSteamIDFromPage($("body")) var allGames = [] gameRows.each( function (){ appID = Number($(this).find('a').attr("href").match("[0-9]*$")[0]) allGames.push(appID) if ( $(this).find('div.clientConnItemTextBlock:contains("Ready to play")').length > 0) { installedGames.push(appID) } }) if (steamID && (allGames.length > 0)){ allGames.sort( function(a,b){return a-b}) var storeUpdate = {} storeUpdate.owned = allGames storeUpdate.name = getProfileNameFromPage($("body")) updateOneProfile(steamID, storeUpdate) } GM_setValue("installedGames", JSON.stringify(installedGames)) } function isListingAllGames(root){ try { var matched = $(root).find("div.info.scroll_info").html().match(/1 - (\d+) of (\d+) items/) if (matched[1] == matched[2]){ return true} } catch(err){ } return false } function addTagFilter(){ filterGameTags = rebuildFilterGameTags() $("select#tagFilter").remove() $("span#tagFilterLabel").remove() var options = $.map(Object.keys(filterGameTags).sort(), function(tag){ return ('<option value="'+tag+'"'+((filterGameTags[tag].length == 0) ? ' disabled="disabled"':'a="b"') +'>'+tag+' ('+filterGameTags[tag].length+')</option>') }) $("div#gameslist_sort_options").append('<span id="tagFilterLabel">Require tag:</span><select multiple size="6" id="tagFilter">' +options.join('')+'</select>') $("select#tagFilter").change(filterByTags) $('body').prepend('<style> div.gameListRow.hidden { display:none}</style>') } function getHeuristicText(steamID){ //We assume the basic gameTags represent steam as a whole in terms of tag distribution (a lie) //Then for each SingleTag, we do: (SteamSingleTag*x/SteamAllTags) = (UserSingleTag/UserAllTags) // x = (UserSingleTag/UserAllTags)*(SteamAllTags/SteamSingleTag) = (UserSingleTag*SteamAllTags)/(UserAllTags*SteamSingleTag) //Also do some screening for really low-incidence tags. Regardless of how many VR-enabled games you have it means nothing. var tagMultipliers = {} var userTotal = 0 var steamTotal = 0 var profile = profilesData[steamID] var tag var MINIMUM_GAME_CUTOFF = 20 var LIKE_THRESHOLD = 1.5 var DISLIKE_THRESHOLD = 0.5 for (tag in gameTags){ if (gameTags[tag].length < MINIMUM_GAME_CUTOFF) { continue; } steamTotal += gameTags[tag].length tagMultipliers[tag] = $.grep(gameTags[tag], function(taggedGame){ return (profile.owned.binarySearch(taggedGame) != -1) }).length userTotal += tagMultipliers[tag] } for (tag in tagMultipliers){ tagMultipliers[tag] = ((tagMultipliers[tag] *steamTotal)/(userTotal*gameTags[tag].length)) } var sortedTags = Object.keys(tagMultipliers).sort(function(a,b){return tagMultipliers[b] - tagMultipliers[a]}) var output = '' for (var i=0; i < sortedTags.length;i++){ tag = sortedTags[i] if ((tagMultipliers[tag] > LIKE_THRESHOLD) || (tagMultipliers[tag] < DISLIKE_THRESHOLD)) output += (tag + ":" + tagMultipliers[tag]) + '\n' } return output } function rebuildFilterGameTags(){ filterGameTags = {} gamesOnPage = [] $("div#games_list_rows div.gameListRow div.gameLogo a").each(function(){ gamesOnPage.push(Number($(this).attr("href").match("[0-9]*$")[0])) }) gamesOnPage.sort( function(a,b){return a-b} ) for (var key in gameTags){ filterGameTags[key] = $.grep(gamesOnPage, function(appID,index){ return gameHasTag(appID, key)}) } return filterGameTags } function filterByTags(e){ var tagArray = [] $(e.target).find("option:selected").each(function(){ tagArray.push($(this).attr('value')) }) $("div#games_list_rows div.gameListRow").each(function(){ appID = $(this).find('a').attr("href").match("[0-9]*$")[0] if (gameHasAllFilterTags(appID, tagArray)){ $(this).removeClass("hidden") } else { $(this).addClass("hidden") } }) } function gameHasAllFilterTags(appID, tagArray){ for (var i=0; i < tagArray.length; i++){ if ((gameTags[tagArray[i]].binarySearch(appID) == -1) ) { return false } } return true } function addScanForTagsButton(){ var extraButton = '<a class="btn_blue_white_innerfade btn_medium ajaxscanbtn" id="scanfortagsbutton"><span>Tag untagged games (' + (getUntaggedGameIDs().length - gamesLackingTagsOnPage.length) +')</span></a>' $("div.apphub_OtherSiteInfo").children().last().after(extraButton) $("#scanfortagsbutton").click(scanGamesForTags) } function scanGamesForTags(){ if (pendingAjaxCalls.length > 0) { return } var uniqueAppIDs = getUntaggedGameIDs() console.info(uniqueAppIDs) pendingAjaxCalls = [] for (var i = (uniqueAppIDs.length-1); i>= 0; i--) { pendingAjaxCalls.push(getAjaxForLoadAppPage(uniqueAppIDs[i])) } $("a.ajaxscanbtn span").animate({color: "#FF9966"}, 1000) ajaxIntervalID = setInterval(spinOffAjaxRequest, AJAX_DELAY) } function getUntaggedGameIDs(){ if (untaggedGameIDs != null) { return untaggedGameIDs } var untaggedSet = {} var i, profile, appID for (var steamID in profilesData) { profile = profilesData[steamID] if (profile.owned){ for (i = 0; i < profile.owned.length; i++){ appID = profile.owned[i] if (!(untaggedSet.hasOwnProperty(appID)) && !(gameHasAnyTag(appID))) { untaggedSet[appID] = true } } } if (profile.wishlist) { for (i = 0; i < profile.wishlist.length; i++){ appID = profile.wishlist[i] if (!(untaggedSet.hasOwnProperty(appID)) && !(gameHasAnyTag(appID))) { untaggedSet[appID] = true } } } } for (i = 0; i < gamesToNotTag.length; i++){ delete untaggedSet[gamesToNotTag[i]] } untaggedGameIDs = Object.keys(untaggedSet).map(Number) untaggedGameIDs.sort() var cleanedLackingTags = [] for (var i = 0; i < gamesLackingTagsOnPage.length ;i++){ appID = gamesLackingTagsOnPage[i] if ($.inArray(appID, untaggedGameIDs) != -1){ cleanedLackingTags.push(appID) } } gamesLackingTagsOnPage = cleanedLackingTags GM_setValue("gamesLackingTagsOnPage", JSON.stringify(gamesLackingTagsOnPage)) return untaggedGameIDs } function gameHasAnyTag(appID){ for (var tag in gameTags){ if (gameHasTag(appID, tag)) { return true } } return false } function getAjaxForLoadAppPage(appID) { var link = "http://store.steampowered.com/app/"+appID+"/" return { url: link, crossDomain: true, xhrFields: { withCredentials: true }, error: function(){ //console.info("Tagging: Error loading page for " + appID) }, success: function(data, textStatus, jqXHR) { handleAppPageLoad(data, textStatus, jqXHR, appID) }, complete: function(jqXHR, textStatus, errorThrown){ completeAsyncAjax() } } } function handleAppPageLoad(data, textStatus, jqXHR, appID) { scanGameTags($(data),appID) spotDifferingAppID($(data), appID) } function spotDifferingAppID(data, referringAppID){ var redirectedAppID = getAppIDFromLinkOnPage(data) if (redirectedAppID && (redirectedAppID != referringAppID)){ gameRedirectedFrom[redirectedAppID] = referringAppID GM_setValue("gameRedirectedFrom", JSON.stringify(gameRedirectedFrom)) } } function getAppIDFromLinkOnPage(data){ var link = $(data).filter('link[rel="canonical"]').attr('href') if (link) { return (getAppIDFromURL($(data).filter('link[rel="canonical"]').attr('href'))) } return null } function addTrimmerButton(){ var extraButton = '<label id="trimmerbutton" class="badge_sort_option whiteLink es_badges"><span>Easy cards only ('+ badgesInstalled.length+") "+ "("+installedGames.length+" games installed"+')</span></label>' $("div.profile_badges_sortoptions").children().last().after(extraButton) $("#trimmerbutton").click(toggleUninstalledVisibility) } function toggleUninstalledVisibility(){ uninstalledAreVisible = !uninstalledAreVisible if (uninstalledAreVisible) { $("div.badge_row").css("display", "") $("#trimmerbutton").text("Easy cards (" +badgesInstalled.length+") "+ " ("+installedGames.length+" games installed)") } else { hideUninstalled() } } function hideUninstalled(){ var gameRows = $("div.badge_row") var appID = 0 gameRows.each( function(){ if ($(this).find("div.badge_title_stats:contains('remaining'):not(:contains('No'))").length == 1) { appID = $(this).find('div.badge_title_stats div.badge_title_playgame a').attr("href").match(/[0-9]*$/)[0] if (badgesInstalled.indexOf(appID) == -1) { $(this).css("display","none") } } else { $(this).css("display","none") } }) $("#trimmerbutton").text("Show uninstalled") } function getBadgeGamesInstalled(){ var result = new Array() for (var i = 0; i < badgeGames.length; i++) { if (installedGames.indexOf(parseInt(badgeGames[i][0]) ) != -1) { result.push(badgeGames[i][0]) } } return result } function scanGiftInventoryData(tries){ if (tries > MAX_INVENTORY_TRIES) { return } var steamInventory = grabRgInventory() if (null == steamInventory ){ window.setTimeout(function() { scanGiftInventoryData(tries+1) }, GRAB_INVENTORY_DELAY) return } var attemptedNewGiftData = getGiftAssociativeArray( getNumbersAndNamesFromRgInventory(steamInventory) ) if (null == attemptedNewGiftData) { window.setTimeout(function() { scanGiftInventoryData(tries+1) }, GRAB_INVENTORY_DELAY) return } giftInventoryData = getGiftAssociativeArray( getNumbersAndNamesFromRgInventory(steamInventory) ) GM_setValue("giftInventoryData", JSON.stringify(giftInventoryData)) $("div#context_selector").prepend('<div style="color:green">Gift inventory scanned</div>') addGiftRecipientSelector() } function getGiftAssociativeArray(giftTuples){ if (giftTuples.length == 0) { return null } var finalGifts = {} var lastAppID = -1 var tuple for (var i=0; i< giftTuples.length; i++){ tuple = giftTuples[i] if (tuple[0] == lastAppID) { finalGifts[lastAppID].count += 1 } else { lastAppID = tuple[0] finalGifts[lastAppID] = {"name":tuple[1], "count":1} } } return finalGifts } function addGiftRecipientSelector(){ var recipientSelect = $('<select id="recipient_selector" />') //console.info(recipientSelect) $("<option value='-1'>Choose your gift target</option>").appendTo(recipientSelect) var sortedArray = [] for (var entryID in profilesData){ sortedArray.push([profilesData[entryID]["name"], entryID]) } sortedArray.sort(function(a,b) {return a[0].localeCompare(b[0])}) for (var i=0; i < sortedArray.length; i++){ $('<option />', {value: sortedArray[i][1], text: sortedArray[i][0]}).appendTo(recipientSelect) } $('div#context_selector').append(recipientSelect) recipientSelect.change(recipientSelectorChange) addExtraFilteringToPageJS() } var filterFuncString = '<script type="text/javascript">\ function getLastPath(fullPath){\ return fullPath.match(".+\/([^\/]+)")[1]\ }\ \ function getGiftItemAppIDsAndNames(rgItem) {\ var hrefPattern = ' + /<a href="[^"]+\/app\/(\d+)\/?">([^<]+)</igm +';\ try {\ if (rgItem.actions && rgItem.actions[0].link.indexOf("/sub/") == -1) {\ return [ [getLastPath(rgItem.actions[0].link), rgItem.name] ];\ }\ else {\ var result = [];\ var alldescs = "";\ for (var i = 0; i < rgItem.descriptions.length; i++){\ alldescs = alldescs + rgItem.descriptions[i].value;\ }\ var oneHref;\ while ((oneHref = hrefPattern.exec(alldescs)) != null){\ result.push(new Array(oneHref[1], oneHref[2]));\ }\ return result;\ };\ } catch (err) {\ if (rgItem && rgItem.name){\ console.info(err);\ }\ return null;\ };\ };\ \ Filter.MatchItemNotOwned = function(elItem, ownedList){\ if (!elItem || !elItem.rgItem || !ownedList){\ return true;\ }\ var giftGames = getGiftItemAppIDsAndNames(elItem.rgItem);\ if (giftGames != null) {\ for (var i = 0; i < giftGames.length; i++){\ if (ownedList.indexOf(Number(giftGames[i][0])) == -1){\ return true;\ };\ };\ return false;\ };\ return true;\ };\ \ Filter._oldMatchItem = Filter.MatchItem;\ Filter.MatchItem = function( elItem, rgTerm, rgCategories ) {\ return (Filter._oldMatchItem(elItem, rgTerm, rgCategories) && (Filter.MatchItemNotOwned(elItem, window.alreadyOwnedGames)));\ };\ </script>' //Section 9 is coming back null for gifts //My jquery parses hres, but the $J version on the page doesn't. Okay, regex it is! // Regex needs to match <a href="SOMESTUFF" // <a href="[^"]"> function addExtraFilteringToPageJS(){ $("head").append(filterFuncString) } function recipientSelectorChange(){ var selected = $("#recipient_selector").val() filterGiftsForRecipient(selected) } function filterGiftsForRecipient(recipID){ var gift var ownedArray = [] if ((profilesData[recipID]) && (profilesData[recipID].owned)){ ownedArray = profilesData[recipID].owned } unsafeWindow.alreadyOwnedGames = cloneInto(ownedArray, unsafeWindow) var oldFilterText = $("input#filter_control").val() $("input#filter_control").val("|") $("input#filter_control").click() $("input#filter_control").val(oldFilterText) $("input#filter_control").click() } function personOwns(person, appID){ var refAppID = getReferringAppID(appID) return (("owned" in person) && ((person.owned.binarySearch(appID) != -1) || (person.owned.binarySearch(refAppID) != -1)) ) } function grabRgInventory() { if ((unsafeWindow.UserYou.rgAppInfo["753"].rgContexts["1"].inventory) && (unsafeWindow.UserYou.rgAppInfo["753"].rgContexts["1"].inventory.rgInventory) ) { return unsafeWindow.UserYou.rgAppInfo["753"].rgContexts["1"].inventory } return null } function getNumbersAndNamesFromRgInventory(steamInventory){ var tidyGifts = [] var gift for (var i=0; i < steamInventory.rgItemElements.length; i++){ gift = steamInventory.rgItemElements[i].rgItem var appIDsAndNames = getGiftItemAppIDsAndNames(gift) if (appIDsAndNames) { $.merge(tidyGifts, appIDsAndNames) } } tidyGifts.sort(function(a,b) { return a[0]-b[0]}) return tidyGifts } function getGiftItemAppIDsAndNames(rgItem) { var hrefPattern = /<a href="[^"]+\/app\/(\d+)\/?">([^<]+)</igm try { if (rgItem.actions && rgItem.actions[0].link.indexOf("/sub/") == -1 ) { return [ [getLastPath(rgItem.actions[0].link), rgItem.name] ] } else { var result = [] var alldescs = "" for (var i = 0; i < rgItem.descriptions.length; i++){ alldescs = alldescs + rgItem.descriptions[i].value } var oneHref while ((oneHref = hrefPattern.exec(alldescs)) != null){ result.push(new Array(oneHref[1], oneHref[2])) } return result } } catch (err) { console.info(err) return null } } function handleFriendsListPage(){ scanFriendsList() addScanEachFriendButton() addPreyImages() } function scanFriendsList(){ var friendBlocks = $("div.friendBlock.persona") var updatedFriends = {} friendBlocks.each( function () { var steamID = $(this).find("div.manage_friend_checkbox input.friendCheckbox").attr("data-steamid") var avatar = $(this).find("div.playerAvatar img").attr("src") var name = $(this).find("span.friendSmallText").parent("div").text().split("\n")[1].trim() updatedFriends[steamID.toString()] = {"avatar":avatar,"name":name, "isFriend":true} }) updateAllFriendsData(updatedFriends) } function addScanEachFriendButton(){ var extraButton = '<a class="btn_blue_white_innerfade btn_medium ajaxscanbtn" id="scaneachfriendbutton"><span>Scan everyone</span></a>' $("div.manage_friends_header").children().last().after(extraButton) $("#scaneachfriendbutton").click(scanEachFriend) } function addPreyImages(){ var friendBlocks = $("div.friendBlock.persona") friendBlocks.append(preyImage) $("div.preyDiv").click(togglePreyStatus) friendBlocks.each( function() { var steamID = ($(this).find("div.manage_friend_checkbox input.friendCheckbox").attr("data-steamid")) if (isPrey(steamID) ) { $(this).find('div.preyDiv').addClass("active") } }) $('body').prepend('<style> div.preyDiv { position:absolute; right:0; z-index:3; top:8px;}' + 'div.preyDiv {background-image:url("http://media.steampowered.com/steamcommunity/public/images/apps/200510/b9d2d27c2fc9d188f605b4300e475e8d510c41a4.jpg");'+ 'background-size:32px 32px; padding:32px 32px 0 0; z-index:10;}'+ 'div.preyDiv.active {background-image:url("http://media.steampowered.com/steamcommunity/public/images/apps/200510/c5deca36ee53aab6eebc4e5db9d76625d4c67914.jpg")}</style>') } function isPrey(steamID){ return (giftPrey.indexOf(steamID) != -1) } function togglePreyStatus(){ var steamID = $(this).parent().find("div.manage_friend_checkbox input.friendCheckbox").attr("data-steamid") var i = giftPrey.indexOf(steamID) if (i != -1){ giftPrey.splice(i, 1) $(this).removeClass("active") } else { giftPrey.push(steamID) $(this).addClass("active") } GM_setValue("giftPrey", JSON.stringify(giftPrey)) } function scanEachFriend(){ if (pendingAjaxCalls.length > 0) { return } pendingFriendsProfiles = getFilteredProfiles(function(entry) { var now = $.now() return (("isFriend" in entry) && (entry.isFriend) && ((entry.lastAttempted == undefined) || ((now - entry.lastAttempted) > 300000) ) ) } ) pendingAjaxCalls = [] for (var steamID in pendingFriendsProfiles) { pendingAjaxCalls.push(getAjaxForLoadOwnedGames(steamID)) pendingAjaxCalls.push(getAjaxForLoadWishlistGames(steamID)) } $("#scaneachfriendbutton span").animate({color: "#FF9966"}, 1000) ajaxIntervalID = setInterval(spinOffAjaxRequest, AJAX_DELAY) } function spinOffAjaxRequest(){ if ((0==unresolvedConnections) && (0 == pendingAjaxCalls.length)) { clearInterval(ajaxIntervalID) ajaxIntervalID = null updateConnectionDisplay() return } while ((unresolvedConnections < MAX_CONNECTIONS) && (pendingAjaxCalls.length != 0)) { var nextRequest = pendingAjaxCalls.pop() unresolvedConnections++ $.ajax(nextRequest) } updateConnectionDisplay() } function updateConnectionDisplay(){ if ((0 == unresolvedConnections) && (0 == pendingAjaxCalls.length)) { $("a.ajaxscanbtn span").text("Done scanning") $("a.ajaxscanbtn span").animate({color: "#99FF66"}, 1000) } else { $("a.ajaxscanbtn span").text(unresolvedConnections + " active requests, " + pendingAjaxCalls.length + " requests remaining") } } function getAjaxForLoadOwnedGames(steamID){ var link = "http://steamcommunity.com/profiles/"+steamID+"/games?tab=all#0|2000" return { url: link, type: "GET", ifModified: true, beforeSend: function(xhr) { xhr.setRequestHeader( 'X-Requested-With', {toString: function() { return ''; } }) stampConnectionAttempt(steamID) }, headers: { "Accept" : "text/plain; charset=utf-8", "Content-Type": "text/plain; charset=utf-8" }, dataType: "html", success: function (data, textStatus, jqXHR) { handleOwnedGamesLoad(data, textStatus, jqXHR, steamID) }, complete: completeAsyncAjax } } function getAjaxForLoadWishlistGames(steamID){ var link = "http://steamcommunity.com/profiles/"+steamID+"/wishlist" return { url: link, ifModified: true, success: handleWishlistGamesLoad, complete: completeAsyncAjax, beforeSend: function(xhr) { stampConnectionAttempt(steamID) } } } function completeAsyncAjax(jqXHR, textStatus){ unresolvedConnections-- updateConnectionDisplay() } function handleWishlistGamesLoad(data, textStatus, jqXHR) { if (textStatus == "success") { scanProfileGamesList($(data), "wishlist", "wishlistRow") } } function handleOwnedGamesLoad(data, textStatus, jqXHR, steamID) { if (textStatus == "success") { var gamesList = getAppIDsFromRgGames(getRgGamesFromHTML(data)) addGamesListToSteamID(steamID, gamesList, "owned") } } function getRgGamesFromHTML(data){ var re = /^<script language="javascript">([\n\s\S.]*)var rgGames =(.*);/gm var match = re.exec(data) return JSON.parse(match[2]) } function getAppIDsFromRgGames(rgGames){ var appIDs = [] for (var i = 0; i < rgGames.length; i++){ appIDs.push(rgGames[i]["appid"]) } return appIDs } function showGiftCountsForWishlist(){ var appIDs = getGamesList($("body"), "wishlistRow") for (var i = 0; i < appIDs.length; i++){ var appID = appIDs[i] var giftCount = getGiftInventoryCount(appID) if (giftCount > 0) { var textdiv = $("div#game_"+appID +" h4") textdiv.css("color", "red") var giftText = " (" +giftCount + " gift" +((giftCount> 1) ?"s" : "") +" in inventory)" textdiv.text(textdiv.text() + giftText) } } } function stampConnectionAttempt(steamID){ var storeUpdate = {} storeUpdate.lastAttempted = $.now() updateOneProfile(steamID, storeUpdate) } function updateAllFriendsData(updatedFriends){ var steamID for (steamID in profilesData) { if (!(steamID in updatedFriends)) { profilesData[steamID].isFriend = false } } for(steamID in updatedFriends) { updateOneProfile(steamID, updatedFriends[steamID]) } } function updateOneProfile(steamID, updated){ if (steamID in profilesData) { for (var property in updated) profilesData[steamID][property] = updated[property] } else { profilesData[steamID] = updated } GM_setValue("profilesData", JSON.stringify(profilesData)) } function scanProfileGamesOwned(){ var root = $("body") if (isListingAllGames(root)){ scanProfileGamesList(root, "owned", "gameListRow") } else{ console.info("Failed to list all owned games for: http://steamcommunity.com/profiles/"+steamID) } } function scanProfileGamesWishlist(){ scanProfileGamesList($("body"), "wishlist", "wishlistRow") } function scanProfileGamesList(root, storageKey, rowClass){ var steamID = getSteamIDFromPage(root) if (!(steamID in profilesData)) { return } addGamesListToSteamID(steamID, getGamesList(root, rowClass), storageKey) } function addGamesListToSteamID(steamID, gamesList, storageKey) { var storeUpdate = {} gamesList.sort(function(a,b){return a - b}) storeUpdate[storageKey] = gamesList updateOneProfile(steamID, storeUpdate) } function getGamesList(root, rowClass){ var listedGames = new Array() var appID $(root).find("div."+rowClass).each( function (){ appID = $(this).find('a').attr("href").match("[0-9]*$")[0] listedGames.push(Number(appID)) }) return listedGames } function getAppIDFromURL(url){ var appID = url.match("(app|friendsthatplay)\/([0-9]+)\/?$") if (appID != null) { return Number(appID[2]) } return null } function getFriendOwners(appID){ var refAppID = getReferringAppID(appID) return getFilteredProfiles(function(entry) {return (("owned" in entry) && ((entry.owned.binarySearch(appID) != -1) || (entry.owned.binarySearch(refAppID) != -1)))} ) } //TODO: This can be a misnomer right now since we don't filter for friends. function getFriendLackers(appID){ var refAppID = getReferringAppID(appID) return getFilteredProfiles(function(entry) { return (("owned" in entry) && (entry.owned.binarySearch(appID) == -1) && (entry.owned.binarySearch(refAppID) == -1)) } ) } function getPreyLackers(appID){ result = getFriendLackers(Number(appID)) for (steamID in result){ if (!(isPrey(steamID))) { delete(result[steamID]) } } return result } function getFilteredProfiles(filterFunc){ var filtered = {} for (var steamID in profilesData) { if (filterFunc(profilesData[steamID]) == true) { filtered[steamID] = profilesData[steamID] } } return filtered } function addFriendsWhoOwnUI(appID){ var owners = getFriendOwners(appID) addFriendsUI(appID, "Friends who just own ", owners) } function addFriendsWhoLackUI(appID){ var lackers = getFriendLackers(appID) addFriendsUI(appID, "Friends who lack ", lackers) } function addFriendsUI(appID, friendDesc, players){ var gameLink = '<a href="http://steamcommunity.com/app/' +appID + '">'+$("div.friendListSectionHeader a").html() +'</a><span class="underscoreColor">_</span>' var sectionHead = '<div class="mainSectionHeader friendListSectionHeader">' + friendDesc + gameLink +'</div>' var friendList = '<div class="profile_friends">' if (Object.keys(players).length > 0){ for (var steamID in players) { friendList += createFriendBlock(steamID) } friendList += '</div><div style="clear: left;"></div></div>' $("div#memberList").children().last().after(sectionHead) $("div#memberList").children().last().after(friendList) } } function createFriendBlock(steamID){ var result = '<div class="friendBlock persona"> <a class="friendBlockLinkOverlay" href="http://steamcommunity.com/profiles/'+steamID+ '"></a><div class="playerAvatar online"><img src="'+profilesData[steamID].avatar+'"></div>'+ '<div class="friendBlockContent">'+profilesData[steamID].name+'</div></div>' return result } function handleAppPage(appID){ showAppGiftingTargets(appID) scanGameTags($("body"), appID) } function scanGameTags(root,appID){ if (!onGamePage(root)){ if ($(root).find("div#supernav").length != 0){ addToGamesToNotTag(appID) } return } var potentialTags = [] $(root).find('div.game_meta_data div#category_block div.game_area_details_specs a.name, div.game_details a[href^="http://store.steampowered.com/genre/"]').each( function(){ potentialTags.push($(this).text()) }) if (potentialTags.length > 0) { removeFromGamesLackingTagsOnPage(appID) for (var i=0; i < TAGS_TO_TRASH_GAME.length; i++){ if (potentialTags.indexOf(TAGS_TO_TRASH_GAME[i]) != -1){ addToGamesToNotTag(appID) return } } for (var i=0; i < potentialTags.length; i++){ addAppIDToTag(appID, potentialTags[i]) } GM_setValue("gameTags", JSON.stringify(gameTags)) } if (!gameHasAnyTag(appID)){ addToGamesLackingTagsOnPage(appID) } } function addToGamesLackingTagsOnPage(appID){ if (gamesLackingTagsOnPage.indexOf(appID) == -1){ gamesLackingTagsOnPage.push(appID) GM_setValue("gamesLackingTagsOnPage", JSON.stringify(gamesLackingTagsOnPage)) } } function removeFromGamesLackingTagsOnPage(appID){ var removeIndex = gamesLackingTagsOnPage.indexOf(appID) if (removeIndex != -1){ gamesLackingTagsOnPage.splice(removeIndex,1) GM_setValue("gamesLackingTagsOnPage", JSON.stringify(gamesLackingTagsOnPage)) } } function addToGamesToNotTag(appID){ gamesToNotTag.push(appID) gamesToNotTag.sort() GM_setValue("gamesToNotTag", JSON.stringify(gamesToNotTag)) } function onGamePage(root){ if ($(root).find('div.breadcrumbs div.blockbg a[href="http://store.steampowered.com/search/?term=&snr=1_5_9__205"]').length != 1){ return false } if ($(root).find('div.breadcrumbs div.blockbg a[href^="http://store.steampowered.com/dlc/"]').length != 0){ return false } if ($(root).find('div.notice_box_content a[href^="http://www.steampowered.com/v/index.php?area=game&AppId="]').length != 0){ return false } return true } function addAppIDToTag(appID, tagText){ if (gameTags[tagText]){ if (gameHasTag(appID, tagText)){ return } gameTags[tagText].push(Number(appID)) gameTags[tagText].sort(function(a,b){return a-b}) } else{ if (!(TAGS_TO_IGNORE.binarySearch(tagText) != -1)){ gameTags[tagText] = [Number(appID)] } else { return false } } return true } function gameHasTag(appID, tagText){ return (gameTags[tagText].binarySearch(appID) != -1) } function showAppGiftingTargets(appID){ var giftCount = getGiftInventoryCount(appID) if (giftCount > 0) { var giftText = " (" +giftCount + " gift" +((giftCount> 1) ?"s" : "") +" in inventory)" $("div.apphub_AppName").first().append(giftText) } var prey = getPreyLackers(appID) if (Object.keys(prey).length > 0){ var preyDiv = '<div class="preyDiv">' $.each(prey, function(key, value) { preyDiv += ' <a href="http://steamcommunity.com/profiles/' + key + '"' + ((value["wishlist"] && (value.wishlist.binarySearch(appID) != -1)) ?" class='wished'" : "")+ '>' + value.name + '</a>' }) preyDiv += '</div>' $("div.apphub_AppName").append(preyDiv) $('body').prepend('<style> div.preyDiv {position: absolute; z-index:4;} ' + 'div.preyDiv a { font-size: 14px; color:#FFFFFF} '+ 'div.preyDiv a.wished{color:#32CD32;font-size:16px; animation: glow .75s infinite alternate;}'+ '@keyframes glow { to { text-shadow: 0 0 4px #00FF00; } }'+ 'div.preyDiv a[href$="/76561198024962141"]{color:#FFCCFF;}'+ 'div.preyDiv a[href$="/76561198024962141"].wished {color:#FF00FF;animation: pinkglow .75s infinite alternate;}'+ '@keyframes pinkglow { to { text-shadow: 0 0 4px #FFCCFF; } }' + '</style>') } } function getReferringAppID(appID){ if (appID in gameRedirectedFrom){ return gameRedirectedFrom[appID] } return appID } function getGiftInventoryCount(appID){ return ( (appID in giftInventoryData) ? giftInventoryData[appID].count : 0) } main()