// ==UserScript== // @name humble-bundle-extra // @namespace https://humblebundle.com // @version 1.1.0 // @description User script for humble bundle. Adds steam store links to all games and marks already owned games // @match *://www.humblebundle.com/* // @author MrMarble // @grant GM_xmlhttpRequest // @connect api.steampowered.com // @connect store.steampowered.com // @icon https://humblebundle-a.akamaihd.net/static/hashed/47e474eed38083df699b7dfd8d29d575e3398f1e.ico // @source https://github.com/MrMarble/humble-bundle-extra // @downloadURL none // ==/UserScript== (function () { 'use strict'; const xtmlHttp = (options) => { return new Promise((resolve, reject) => { GM_xmlhttpRequest({ ...options, onload: resolve, onabort: reject, }); }) }; const decodeEntities = (() => { const element = document.createElement("div"); function decodeHTMLEntities(str) { if (str && typeof str === "string") { str = str.replace(/]*>([\S\s]*?)<\/script>/gim, ""); str = str.replace(/<\/?\w(?:[^"'>]|"[^"]*"|'[^']*')*>/gim, ""); element.innerHTML = str; str = element.textContent; element.textContent = ""; } return str } return decodeHTMLEntities })(); const sanitize = (str) => { return decodeEntities(str) .replace(/[\u{2122}\u{00AE}]/gu, "") .toLowerCase() }; const htmlToElement = (html) => { var template = document.createElement("template"); html = html.trim(); template.innerHTML = html; return template.content.firstChild }; const isBundlePage = () => { return !!document.querySelector( "div.inner-main-wrapper div.bundle-info-container" ) }; const createModal = (icon, title, text) => htmlToElement(`

${title}

${text}
`); const CACHE_STEAM_APPS_KEY = "&&hh_extras&&"; const CACHE_OWNED_APPS_KEY = "&&hh_extras_owned&&"; const fetchSteamApps = async () => { const r = await xtmlHttp({ url: "https://api.steampowered.com/ISteamApps/GetAppList/v0002/?format=json", method: "GET", }); const { applist } = JSON.parse(r.responseText); const apps = {}; applist?.apps?.forEach(({ name, appid }) => { apps[sanitize(name)] = appid; }); return apps }; const cacheSteamApps = async (force) => { let data = {}; if (force) { data = await fetchSteamApps(); localStorage.setItem(CACHE_STEAM_APPS_KEY, JSON.stringify(data)); } else { if ((data = localStorage.getItem(CACHE_STEAM_APPS_KEY))) { data = JSON.parse(data); } else { data = await fetchSteamApps(); localStorage.setItem(CACHE_STEAM_APPS_KEY, JSON.stringify(data)); } } return data }; const fetchOwnedApps = async () => { const r = await xtmlHttp({ url: "https://store.steampowered.com/dynamicstore/userdata/", method: "GET", }); const { rgOwnedApps } = JSON.parse(r.responseText); return rgOwnedApps }; const cacheOwnedApps = async (force) => { let data = []; if (force) { data = await fetchOwnedApps(); localStorage.setItem(CACHE_OWNED_APPS_KEY, JSON.stringify(data)); } else { if ((data = localStorage.getItem(CACHE_OWNED_APPS_KEY))) { data = JSON.parse(data); } else { data = await fetchOwnedApps(); localStorage.setItem(CACHE_OWNED_APPS_KEY, JSON.stringify(data)); } } return data }; function showModal() { const modal = createModal( "hb-exclamation-circle", "You are not logged in to the steam store", `

Information about games already in your library will not be available.

You can login using this link. Reload the page after login to load the games in your library.

` ); document.querySelector("#site-modal").appendChild(modal); } async function main() { const apps = await cacheSteamApps(); const owned = await cacheOwnedApps(); const loggedIn = owned.length != 0; if (!loggedIn) { showModal(); } document.querySelectorAll(".front-page-art-image-text").forEach((el) => { let appid; if ((appid = apps[sanitize(el.textContent)])) { el.innerHTML = `${el.textContent}`; if (loggedIn && owned.includes(appid)) { el.parentElement.parentElement .querySelector(".dd-caption-lock") .remove(); el.firstChild.style.color = "#7f9a2f"; } } }); } if (isBundlePage()) { main(); } }());