// ==UserScript== // @author Revadike // @connect bartervg.com // @connect steam-tracker.com // @connect store.steampowered.com // @contributor Barter.vg // @contributor Black3ird // @contributor Lex // @contributor Luckz // @description Check every web page for game, dlc and package links to the steam store and mark using icons whether it's owned, unowned, wishlisted, ignored (not interested), DLC, removed/delisted (decommissioned), has low confidence metric, has cards, or is bundled. // @exclude /^https?\:\/\/(.+.steampowered|steamcommunity).com\/(?!groups\/groupbuys).*/ // @grant GM_addStyle // @grant GM_getValue // @grant GM_info // @grant GM_openInTab // @grant GM_registerMenuCommand // @grant GM_setValue // @grant GM_xmlhttpRequest // @grant unsafeWindow // @homepageURL https://www.steamgifts.com/discussion/y9vVm/ // @icon https://store.steampowered.com/favicon.ico // @include /^https?\:\/\/.+/ // @name Steam Web Integration // @namespace Royalgamer06 // @require https://cdn.jsdelivr.net/npm/jquery@3.3.1/dist/jquery.slim.min.js // @require https://cdn.jsdelivr.net/gh/kapetan/jquery-observe@2.0.3/jquery-observe.js // @run-at document-start // @supportURL https://github.com/Revadike/SteamWebIntegration/issues/ // @version 1.9.7 // @downloadURL https://update.greasyfork.org/scripts/26273/Steam%20Web%20Integration.user.js // @updateURL https://update.greasyfork.org/scripts/26273/Steam%20Web%20Integration.meta.js // ==/UserScript== // ==Code== // eslint-disable-next-line no-multi-assign this.$ = this.jQuery = jQuery.noConflict(true); let settings; let boxNode; function displaySettings() { const { name, version, author } = GM_info.script; $(`#title`).text(`${name} (${version}) by ${author}`); $(`#settings`).show(); $(`#notinstalled`).hide(); Object.keys(settings).forEach((setting) => { const value = settings[setting]; if (typeof value === `boolean`) { $(`#${setting}`).prop(`checked`, value); } else { $(`#${setting}`).val(value); } }); } function showToast() { $(`#snackbar`).addClass(`show`); setTimeout(() => $(`#snackbar`).removeClass(`show`), 3000); } function onChange(elem) { const name = $(elem).attr(`name`); const val = $(elem).val(); if (elem.type === `checkbox`) { settings[name] = $(elem).prop(`checked`); } else { settings[elem.name] = Number.isFinite(val) ? parseInt(val, 10) : val; } GM_setValue(`swi_settings`, JSON.stringify(settings)); showToast(); } function createBoxNode() { return $(`
`, { "class": `swi-block${settings.boxed ? ` boxed` : ``}` }); } function getIconHTML(color, str, lcs, icon, link) { const { name, version, author } = GM_info.script; const titlePlus = `\nLast updated at ${lcs}\n${name} (${version}) by ${author}`; if (link) { return `${icon}`; } return `${icon}`; } function getBoxNode(html, appID, subID) { const node = boxNode.clone(false, false); if (subID) { node.attr(`data-subid`, subID); } else { node.attr(`data-appid`, appID); } node.html(html); return node; } function refreshDecommissioned(callback) { if (!settings.wantDecommissioned) { callback(); return; } const cachedDecommissioned = JSON.parse(GM_getValue(`swi_decommissioned`, null)); const lastCachedDecommissioned = GM_getValue(`swi_decommissioned_last`, 0); if (cachedDecommissioned && Date.now() - lastCachedDecommissioned < settings.decommissionedRefreshInterval * 60000) { callback(cachedDecommissioned); return; } GM_xmlhttpRequest({ "method": `GET`, "url": `https://steam-tracker.com/api?action=GetAppListV3`, "timeout": 30000, "onload": (response) => { let json = null; try { json = JSON.parse(response.responseText); if (json.success) { GM_setValue(`swi_decommissioned`, JSON.stringify(json.removed_apps)); GM_setValue(`swi_decommissioned_last`, Date.now()); } callback(json.removed_apps); return; } catch (error) { console.log(`[Steam Web Integration] Unable to parse removed steam games data. Using cached data...`, error); } callback(cachedDecommissioned); }, "onerror": () => { console.log(`[Steam Web Integration] An error occurred while refreshing removed steam games data. Using cached data...`); callback(cachedDecommissioned); }, "ontimeout": () => { console.log(`[Steam Web Integration] It took too long to refresh removed steam games data. Using cached data...`); callback(cachedDecommissioned); } }); } function refreshDLC(callback) { if (!settings.wantDLC) { callback(); return; } const cachedDLC = JSON.parse(GM_getValue(`swi_dlc`, null)); const lastCachedDLC = parseInt(GM_getValue(`swi_dlc_last`, 0), 10) || 1; if (cachedDLC && Object.keys(cachedDLC).length > 7000 && Date.now() - lastCachedDLC < settings.dlcRefreshInterval * 60000) { callback(cachedDLC); return; } GM_xmlhttpRequest({ "method": `GET`, "url": `https://bartervg.com/browse/dlc/json/`, "timeout": 30000, "onload": (response) => { let json = null; try { json = JSON.parse(response.responseText); if (Object.keys(json).length > 7000) { // sanity check GM_setValue(`swi_dlc`, JSON.stringify(json)); GM_setValue(`swi_dlc_last`, Date.now()); } callback(json); return; } catch (error) { console.log(`[Steam Web Integration] Unable to parse Barter.vg downloadable content data. Using cached data...`, error); } callback(cachedDLC); }, "onerror": (response) => { console.log(`[Steam Web Integration] An error occurred while refreshing Barter.vg downloadable content data. Using cached data...`, response); callback(cachedDLC); }, "ontimeout": () => { console.log(`[Steam Web Integration] It took too long to refresh Barter.vg downloadable content data. Using cached data...`); callback(cachedDLC); } }); } function refreshLimited(callback) { if (!settings.wantLimited) { callback(); return; } const cachedLimited = JSON.parse(GM_getValue(`swi_limited`, null)); const lastCachedLimited = parseInt(GM_getValue(`swi_limited_last`, 0), 10) || 1; if (cachedLimited && Object.keys(cachedLimited).length > 7000 && Date.now() - lastCachedLimited < settings.limitedRefreshInterval * 60000) { callback(cachedLimited); return; } GM_xmlhttpRequest({ "method": `GET`, "url": `https://bartervg.com/browse/tag/481/json/`, "timeout": 30000, "onload": (response) => { let json = null; try { json = JSON.parse(response.responseText); if (Object.keys(json).length > 7000) { // sanity check GM_setValue(`swi_limited`, JSON.stringify(json)); GM_setValue(`swi_limited_last`, Date.now()); } callback(json); return; } catch (error) { console.log(`[Steam Web Integration] Unable to parse Barter.vg low confidence metric data. Using cached data...`, error); } callback(cachedLimited); }, "onerror": (response) => { console.log(`[Steam Web Integration] An error occurred while refreshing Barter.vg low confidence metric data. Using cached data...`, response); callback(cachedLimited); }, "ontimeout": () => { console.log(`[Steam Web Integration] It took too long to refresh Barter.vg low confidence metric data. Using cached data...`); callback(cachedLimited); } }); } function refreshCards(callback) { if (!settings.wantCards) { callback(); return; } const cachedCards = JSON.parse(GM_getValue(`swi_tradingcards`, null)); const lastCachedCards = parseInt(GM_getValue(`swi_tradingcards_last`, 0), 10) || 1; if (cachedCards && Object.keys(cachedCards).length > 7000 && Object.values(cachedCards)[0].marketable && Date.now() - lastCachedCards < settings.cardsRefreshInterval * 60000) { callback(cachedCards); return; } GM_xmlhttpRequest({ "method": `GET`, "url": `https://bartervg.com/browse/cards/json/`, "timeout": 30000, "onload": (response) => { let json = null; try { json = JSON.parse(response.responseText); if (Object.keys(json).length > 7000) { // sanity check GM_setValue(`swi_tradingcards`, JSON.stringify(json)); GM_setValue(`swi_tradingcards_last`, Date.now()); } callback(json); return; } catch (error) { console.log(`[Steam Web Integration] Unable to parse Barter.vg trading cards data. Using cached data...`, error); } callback(cachedCards); }, "onerror": (response) => { console.log(`[Steam Web Integration] An error occurred while refreshing Barter.vg trading cards data. Using cached data...`, response); callback(cachedCards); }, "ontimeout": () => { console.log(`[Steam Web Integration] It took too long to refresh Barter.vg trading cards data. Using cached data...`); callback(cachedCards); } }); } function refreshBundles(callback) { if (!settings.wantBundles) { callback(); return; } const cachedBundles = JSON.parse(GM_getValue(`swi_bundles`, null)); const lastCachedBundles = parseInt(GM_getValue(`swi_bundles_last`, 0), 10) || 1; if (cachedBundles && Object.keys(cachedBundles).length > 7000 && Object.values(cachedBundles)[0].bundles && Date.now() - lastCachedBundles < settings.bundlesRefreshInterval * 60000) { callback(cachedBundles); return; } GM_xmlhttpRequest({ "method": `GET`, "url": `https://bartervg.com/browse/bundles/json/`, "timeout": 30000, "onload": (response) => { let json = null; try { json = JSON.parse(response.responseText); if (Object.keys(json).length > 7000) { // sanity check GM_setValue(`swi_bundles`, JSON.stringify(json)); GM_setValue(`swi_bundles_last`, Date.now()); } callback(json); return; } catch (error) { console.log(`[Steam Web Integration] Unable to parse Barter.vg bundles data. Using cached data...`, error); } callback(cachedBundles); }, "onerror": (response) => { console.log(`[Steam Web Integration] An error occurred while refreshing Barter.vg bundles data. Using cached data...`, response); callback(cachedBundles); }, "ontimeout": () => { console.log(`[Steam Web Integration] It took too long to refresh Barter.vg bundles data. Using cached data...`); callback(cachedBundles); } }); } function doApp(elem, wishlist, ownedApps, ignoredApps, decommissioned, limited, cards, bundles, dlc, lcs, dlcs, dlclcs, llcs, clcs, blcs) { $(elem).addClass(`swi`); const attr = settings.attributes.find((a) => /apps?\//g.test($(elem).attr(a))); const attrVal = $(elem).attr(attr); const appID = parseInt(attrVal.match(/apps?\/[0-9]+/g)[0].split(/apps?\//)[1], 10); if (Number.isNaN(appID)) { return; } setTimeout(() => { let html; let subject; if (dlc && dlc[appID]) { subject = `DLC`; } else if (!dlc) { subject = `Game or DLC`; } else { subject = `Game`; } if (ownedApps && ownedApps.includes(appID)) { // if owned html = getIconHTML(settings.ownedColor, `${subject} (${appID}) owned`, lcs, settings.ownedIcon); // ✔ } else if (wishlist.includes(appID)) { // if not owned and wishlisted html = getIconHTML(settings.wishlistColor, `${subject} (${appID}) wishlisted`, lcs, settings.wishlistIcon); // ❤ } else { // else not owned and not wishlisted html = getIconHTML(settings.unownedColor, `${subject} (${appID}) not owned`, lcs, settings.unownedIcon); // ✘ } if (settings.wantIgnores && ignoredApps && ignoredApps.includes(appID)) { // if ignored and enabled html += getIconHTML(settings.ignoredColor, `${subject} (${appID}) ignored`, llcs, settings.ignoredIcon); // 🛇 } if (settings.wantDLC && dlc && dlc[appID]) { // if DLC and enabled const base = dlc[appID].base_appID; const ownsBase = ownedApps.includes(base); html += getIconHTML(settings.dlcColor, `${subject} (${appID}) is downloadable content for an ${ownsBase ? `` : `un`}owned base game (${base})`, dlclcs, settings.dlcIcon); // ⇩ } if (settings.wantDecommissioned && decommissioned) { // if decommissioned and have cache or new data const app = decommissioned.find((obj) => obj.appid === appID.toString()); if (app) { // if decommissioned? html += getIconHTML(settings.decommissionedColor, `The ${app.type} '${app.name.replace(/"|'/g, ``)}' (${appID}) is ${app.category.toLowerCase()} and has only ${app.count} confirmed owner${app.count === 1 ? `` : `s`} on Steam`, dlcs, settings.decommissionedIcon, `https://steam-tracker.com/app/${appID}/`); // 🗑 } } if (settings.wantLimited && limited && limited[appID]) { // if is limited html += getIconHTML(settings.limitedColor, `Game (${appID}) has profile features limited`, llcs, settings.limitedIcon); // ⚙ } if (settings.wantCards && cards && cards[appID] && cards[appID].cards && cards[appID].cards > 0) { // if has cards and enabled html += getIconHTML(settings.cardColor, `Game (${appID}) has ${cards[appID].cards} ${cards[appID].marketable ? `` : `un`}marketable card${cards[appID].cards === 1 ? `` : `s`}`, clcs, settings.cardIcon, `https://www.steamcardexchange.net/index.php?gamepage-appid-${appID}`); } if (settings.wantBundles && bundles && bundles[appID] && bundles[appID].bundles && bundles[appID].bundles > 0) { // if is bundled and enabled html += getIconHTML(settings.bundleColor, `Game (${appID}) has been in ${bundles[appID].bundles} bundle${bundles[appID].bundles === 1 ? `` : `s`}`, blcs, settings.bundleIcon, `https://barter.vg/steam/app/${appID}/#bundles`); } if (settings.prefix) { $(elem).before(getBoxNode(html, appID)); } else { $(elem).after(getBoxNode(html, appID)); } $(elem).parent().css(`overflow`, `visible`); }, 0); } function doSub(elem, ownedPackages, lcs) { $(elem).addClass(`swi`); const subID = parseInt(elem.href.match(/sub\/[0-9]+/g)[0].split(`sub/`)[1], 10); if (Number.isNaN(subID)) { return; } setTimeout(() => { let html; if (ownedPackages.includes(subID)) { // if owned html = getIconHTML(settings.ownedColor, `Package (${subID}) owned`, lcs, settings.ownedIcon); // ✔ } else { // else not owned html = getIconHTML(settings.unownedColor, `Package (${subID}) not owned`, lcs, settings.unownedIcon); // ✖ } if (settings.prefix) { $(elem).before(getBoxNode(html, undefined, subID)); } else { $(elem).after(getBoxNode(html, undefined, subID)); } $(elem).parent().css(`overflow`, `visible`); }, 0); } function integrate(userdata, decommissioned, cards, bundles, limited, dlc, lastCached) { const ignoredApps = Object.keys(userdata.rgIgnoredApps).map((a) => parseInt(a, 10)); const ownedApps = userdata.rgOwnedApps; const ownedPackages = userdata.rgOwnedPackages; const wishlist = userdata.rgWishlist; const lcs = settings.dateOverride ? new Date(lastCached).toLocaleString(`sv-SE`) : new Date(lastCached).toLocaleString(); const dlcs = new Date(GM_getValue(`swi_decommissioned_last`, 0)).toLocaleString(settings.dateOverride ? `sv-SE` : undefined); const dlclcs = new Date(GM_getValue(`swi_dlc_last`, 0)).toLocaleString(settings.dateOverride ? `sv-SE` : undefined); const llcs = new Date(GM_getValue(`swi_limited_last`, 0)).toLocaleString(settings.dateOverride ? `sv-SE` : undefined); const clcs = new Date(GM_getValue(`swi_tradingcards_last`, 0)).toLocaleString(settings.dateOverride ? `sv-SE` : undefined); const blcs = new Date(GM_getValue(`swi_bundles_last`, 0)).toLocaleString(settings.dateOverride ? `sv-SE` : undefined); const appSelector = [ `[href*="steamcommunity.com/app/"]`, `[href*="steamdb.info/app/"]`, `[href*="store.steampowered.com/agecheck/app/"]`, `[href*="store.steampowered.com/app/"]`, `[style*="cdn.akamai.steamstatic.com/steam/apps/"]`, `[style*="cdn.edgecast.steamstatic.com/steam/apps/"]`, `[style*="steamcdn-a.akamaihd.net/steam/apps/"]`, `[style*="steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/"]`, `[style*="steamcdn-a.opskins.media/steam/apps/"]`, `[style*="steamcdn-a.opskins.media/steamcommunity/public/images/apps/"]`, `[style*="steamdb.info/static/camo/apps/"]`, `img[src*="cdn.akamai.steamstatic.com/steam/apps/"]`, `img[src*="cdn.edgecast.steamstatic.com/steam/apps/"]`, `img[src*="steamcdn-a.akamaihd.net/steam/apps/"]`, `img[src*="steamcdn-a.akamaihd.net/steamcommunity/public/images/apps/"]`, `img[src*="steamcdn-a.opskins.media/steam/apps/"]`, `img[src*="steamcdn-a.opskins.media/steamcommunity/public/images/apps/"]`, `img[src*="steamdb.info/static/camo/apps/"]` ].filter((s) => settings.attributes.find((a) => s.includes(`[${a}`))).map((s) => `${s}:not(.swi)`).join(`, `); const subSelector = [ `[href*="steamdb.info/sub/"]`, `[href*="store.steampowered.com/sub/"]` ].map((s) => `${s}:not(.swi)`).join(`, `); let delaySWI; const doSWI = (delay = 750) => { if (delaySWI) { clearTimeout(delaySWI); } delaySWI = setTimeout(() => { if (settings.dynamicContent !== `ping`) { console.log(`[Steam Web Integration] Executing`); } clearTimeout(delaySWI); $(appSelector, document.body).get().forEach((elem) => doApp(elem, wishlist, ownedApps, ignoredApps, decommissioned, limited, cards, bundles, dlc, lcs, dlcs, dlclcs, llcs, clcs, blcs)); $(subSelector, document.body).get().forEach((elem) => doSub(elem, ownedPackages, lcs), 0); }, delay); }; const clearSWI = () => { console.log(`[Steam Web Integration] Clearing`); $(`.swi-block`).remove(); $(`.swi`).removeClass(`swi`); }; const reloadSWI = () => { clearSWI(); doSWI(0); }; $(document).ready(() => { doSWI(0); GM_registerMenuCommand(`Run again`, () => doSWI(0)); GM_registerMenuCommand(`Clear all`, clearSWI); GM_registerMenuCommand(`Clear and run (reload)`, reloadSWI); unsafeWindow.doSWI = doSWI; unsafeWindow.clearSWI = clearSWI; unsafeWindow.reloadSWI = reloadSWI; if (settings.dynamicContent === `disabled`) { return; } if (settings.dynamicContent === `observe`) { $(`body`).observe({ "added": true, "attributes": true, "attributeFilter": settings.attributes }, appSelector, () => doSWI()); $(`body`).observe({ "added": true, "attributes": true, "attributeFilter": [`href`] }, subSelector, () => doSWI()); } else if (settings.dynamicContent === `ping`) { setInterval(doSWI, 1500); } }); } function refresh() { const cachedJson = GM_getValue(`swi_data`, null); let lastCached = GM_getValue(`swi_last`, 0); if (cachedJson && Date.now() - lastCached < settings.userRefreshInterval * 60000) { const userdata = JSON.parse(cachedJson); refreshDecommissioned((decommissioned) => refreshDLC((dlc) => refreshLimited((limited) => refreshCards((cards) => refreshBundles((bundles) => integrate(userdata, decommissioned, cards, bundles, limited, dlc, lastCached)))))); return; } GM_xmlhttpRequest({ "method": `GET`, "url": `https://store.steampowered.com/dynamicstore/userdata/?t=${Date.now()}`, "onload": (response) => { let userdata = JSON.parse(response.responseText); if (userdata.rgOwnedApps.length === 0 && userdata.rgOwnedPackages.length === 0 && userdata.rgIgnoredApps.length === 0 && userdata.rgWishlist.length === 0) { // not logged in if (!cachedJson) { console.log(`[Steam Web Integration] No cached information available. Please login to Steam to fix this.`); return; } userdata = JSON.parse(cachedJson); } else { lastCached = Date.now(); GM_setValue(`swi_last`, lastCached); GM_setValue(`swi_data`, JSON.stringify(userdata)); } refreshDecommissioned((decommissioned) => refreshDLC((dlc) => refreshLimited((limited) => refreshCards((cards) => refreshBundles((bundles) => integrate(userdata, decommissioned, cards, bundles, limited, dlc, lastCached)))))); } }); } function init() { const settingsuri = `https://revadike.com/swi/settings`; const defaults = { "attributes": [`href`, `src`, `style`], "blackList": ``, "boxed": true, "bundleColor": `#ffff00`, "bundleIcon": `🎁︎`, "bundlesRefreshInterval": 2880, "cardColor": `#0000ff`, "cardIcon": `🂡`, "cardsRefreshInterval": 2880, "dateOverride": false, "decommissionedColor": `#ffffff`, "decommissionedIcon": `☠`, "decommissionedRefreshInterval": 1440, "dlcColor": `#a655b2`, "dlcIcon": `⇩`, "dlcRefreshInterval": 1440, "dynamicContent": `observe`, "ignoredColor": `#808080`, "ignoredIcon": `🚫︎`, "limitedColor": `#00ffff`, "limitedIcon": `⚙`, "limitedRefreshInterval": 2880, "ownedColor": `#008000`, "ownedIcon": `✔`, "prefix": false, "unownedColor": `#ff0000`, "unownedIcon": `✘`, "userRefreshInterval": 1, "wantBundles": true, "wantCards": true, "wantDecommissioned": true, "wantDLC": true, "wantIgnores": true, "wantLimited": true, "wishlistColor": `#ff69b4`, "wishlistIcon": `❤` }; const stylesheet = ` .swi-block { display: inline-block; line-height: initial; } .swi-block.boxed { background: rgba(0, 0, 0, 0.7); border-radius: 5px; margin: auto 4px auto 4px; padding: 2px 4px 2px 4px; position: relative; } .swi-block span { cursor: help; margin: 2px; } .swi-block a { text-decoration: none; } `; settings = JSON.parse(GM_getValue(`swi_settings`, JSON.stringify(defaults))); Object.keys(defaults).forEach((setting) => { if (settings[setting] !== undefined) { return; } settings[setting] = defaults[setting]; }); if (unsafeWindow.location.href.startsWith(settingsuri)) { unsafeWindow.onChange = onChange; unsafeWindow.scriptInfo = GM_info.script; unsafeWindow.settings = settings; $(document).ready(displaySettings); } else if (!settings.blackList.split(`\n`).find((url) => unsafeWindow.location.href.includes(url.trim()))) { boxNode = createBoxNode(); GM_addStyle(stylesheet); GM_registerMenuCommand(`Change settings`, () => unsafeWindow.open(settingsuri, `_blank`)); refresh(); } } init(); // ==/Code==