// This code is licensed under GNU GPLv3. const BASE_URL = "/api/appinfo"; var TS4_APP_ID = 1222670; const APPID_REGEX = /#((http(s)?:\/\/store\.steampowered\.com\/app\/)?(?\d{4,10})(.*))/ if(APPID_REGEX.test(window.location.hash)===true) { let matches = APPID_REGEX.exec(window.location.hash); TS4_APP_ID = parseInt(matches.groups.appID); console.debug("Set app ID to " + TS4_APP_ID); } else { console.debug("No custom ID set or does not match regex."); } const PRICE_ELEMENT = document.getElementById("price"); const NAME_ELEMENT = document.getElementById("name"); /* * These prices are gathered from a range of sources, such as Tesco, Amazon, and CeX. * Some prices may no longer be correct. They do not include discounts. * Prices are often not updated after they're added. Prices are in GBP. * * Last updated: 2024-09-02 */ const PRICES = { "Freddo (18g)": 0.25, "Skittles (136G)": 1.25, "Cadbury Fingers (114G)": 1.70, "howmuchdoesthesims4cost.lol": 1.78, "Pepsi Max (2L)": 2.00, "Pringles (Salt & Vinegar, 185g)": 2.25, "BLÅHAJ Soft toy, shark (40 cm)": 22.00, "Dell Optiplex 7040 USFF (6th generation Intel i5)": 25.00, "Tenda AC10 V3.0 AC1200 Dual Band Gigabit Wireless Cable Router": 27.99, "Seagate SkyHawk 4TB 3.5\" 7200RPM HDD": 30.00, "Dell Optiplex 7040 SFF (6th generation Intel i5)": 36.00, "Dell Optiplex 3020 USFF (4th generation Intel i5)": 39.99, "TP-Link AX1800 WiFi 6 Router": 39.99, "KIOXIA EXERIA NVMe M.2 SSD (1TB)": 43.20, // I *wish* they still cost this much - ~nex 2024-09-02 "Seagate BarraCuda,Internal Hard Drive 2.5 Inch (1TB)": 49.99, "Kingston NV2 NVMe PCIe 4.0 Internal SSD (1TB)": 59.99, "NVIDIA GeForce GTX 1650": 117.96, "AMD Radeon RX 5700 8GB (Second hand from CeX)": 150.00, "TP-Link AX5400 WiFi 6 Router": 159.99, "Intel Core i5-13400F": 191.99, "Apple Watch SE (1st generation)": 219.00, "Windows 11 Pro license": 219.99, // https://www.microsoft.com/en-gb/d/windows-11-pro/dg7gmgf0d8h4/000P "HP ProBook 455 G8": 289.79, // second hand from eBay, bought 2024-07-06 "iPhone SE (3rd generation/5G/2022)": 499.00, "My custom PC (https://uk.pcpartpicker.com/user/nexy7574/saved/#view=cKYV4D)": 800.00, "NVIDIA GeForce RTX 4090": 1519.00, } function compare(price, item) { let n = Math.floor((price / PRICES[item]) * 100) / 100; if(n>=1) { n = Math.floor(n); }; return n } function update(n, unit="$") { PRICE_ELEMENT.textContent = unit + n; console.debug("Updated price to: " + n); } function ids_to_body(ids) { return JSON.stringify({"app_ids": ids}) } function ratelimited(response) { let retry_after = response.headers.get("retry-after"); console.warn("Rate-limited for " + retry_after + " seconds."); setTimeout(() => {window.location.reload()}, retry_after * 1000); setInterval( () => { let element = document.getElementById("retry-after"); let value = parseFloat(element.textContent); value -= 0.1; element.textContent = value.toFixed(1); }, 100 ) let element = document.getElementById("retrying"); element.hidden = false; document.getElementById("retry-after").textContent = (retry_after * 1).toFixed(1); document.querySelector("main").hidden = true; document.querySelector("main").style.display = "none"; } async function get_app_info(id) { const response = await fetch(BASE_URL, {method: "POST", body: ids_to_body([id]), headers: {"Content-Type": "application/json"}}); if(response.status === 429) { ratelimited(response); return {id: {"name": ""}} } const data = await response.json(); console.debug(data); return data; } async function calculate_price(ids) { console.debug("Calculating price for: " + ids); const response = await fetch(BASE_URL, {method: "POST", body: ids_to_body(ids), headers: {"Content-Type": "application/json"}}); if(response.status === 429) { ratelimited(response); let o = {} for (let id of ids) { o[id] = {"price_overview": {"final": 0.0}} } return o } const data = await response.json(); console.debug("mass price data:", data); let _price = 0.0; let unit = "£"; for (let value of Object.values(data)) { console.debug("Value:", value) if (value.price_overview == null) { continue; } let new_unit = value.price_overview.final_formatted.slice(0, 1); if((/\d/).test(new_unit)) { //new_unit = value.price_overview.final_formatted.slice(-2, -1); new_unit = value.price_overview.currency || '£' } unit = new_unit _price += parseFloat(value.price_overview.final / 100); } return [_price, unit]; } function find_unit(formatted) { let unit = "£"; if((/\d/).test(formatted.slice(0, 1))) { unit = formatted.slice(-2, -1); } return unit; } async function main() { console.debug("Fetching app info for base game"); let root; try { root = await get_app_info(TS4_APP_ID); } catch (e) { alert(`Error fetching information from Steam: ${e}`); window.location.reload(); }; console.debug("Fetching app info for DLCs"); let key = Object.keys(root)[0]; NAME_ELEMENT.textContent = root[key].name; console.debug("Checking key: " + key); let dlc_price = (root[key].price_overview || {final: 0.0}).final / 100; let unit = find_unit((root[key].price_overview || {final_formatted: "£0.00"}).final_formatted); if(root[key].dlc && root[key].dlc.length >= 0) { document.getElementById("dlcs").hidden = false; document.getElementById("dlc-count").textContent = root[key].dlc.length + " "; let _result= await calculate_price(root[key].dlc) dlc_price += _result[0]; } if(root[key].header_image && root[key].capsule_image) { let element2 = document.createElement("img"); element2.src = root[key].header_image; element2.alt = root[key].name; element2.style.height = "2em" NAME_ELEMENT.textContent = null; NAME_ELEMENT.appendChild(element2); } update(dlc_price.toFixed(2), unit); let element = document.getElementById("comparison"); element.hidden = false; for(let item of Object.keys(PRICES)) { let price = compare(dlc_price, item); if(price >= 0.95) { let li = document.createElement("li"); li.textContent = `${price}x ${item} (${unit}${PRICES[item]}/pc)`; element.children[1].appendChild(li); } }; } main();