// ==UserScript==
// @name Steam valute to RUB convertation
// @name:ru Перевод валюты в рубли в Steam
// @version 1.2.0
// @namespace https://store.steampowered.com/
// @description Steam tourists, rejoice!
// @description:ru Путешественники в Steam, объединяйтесь!
// @author CJMAXiK
// @license MIT
// @homepage https://cjmaxik.com/steam-valute
// @match https://store.steampowered.com/*
// @match https://steamcommunity.com/*
// @icon https://icons.duckduckgo.com/ip3/steampowered.com.ico
// @grant GM_xmlhttpRequest
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_deleteValue
// @grant GM_addStyle
// @connect cdn.jsdelivr.net
// @tag productivity
// @downloadURL https://update.greasyfork.icu/scripts/521550/%D0%9F%D0%B5%D1%80%D0%B5%D0%B2%D0%BE%D0%B4%20%D0%B2%D0%B0%D0%BB%D1%8E%D1%82%D1%8B%20%D0%B2%20%D1%80%D1%83%D0%B1%D0%BB%D0%B8%20%D0%B2%20Steam.user.js
// @updateURL https://update.greasyfork.icu/scripts/521550/%D0%9F%D0%B5%D1%80%D0%B5%D0%B2%D0%BE%D0%B4%20%D0%B2%D0%B0%D0%BB%D1%8E%D1%82%D1%8B%20%D0%B2%20%D1%80%D1%83%D0%B1%D0%BB%D0%B8%20%D0%B2%20Steam.meta.js
// ==/UserScript==
const signToValute = {
"₸": "KZT",
TL: "TRY",
"€": "EUR",
"£": "GBP",
ARS$: "ARS",
"₴": "UAH",
$: "USD",
};
// https://github.com/IsThereAnyDeal/AugmentedSteam/blob/develop/src/js/Core/Currencies.ts
const steamCurrencies = [
{ id: 1, abbr: "USD", symbol: "$", hint: "United States Dollars" },
{ id: 2, abbr: "GBP", symbol: "£", hint: "British Pound" },
{ id: 3, abbr: "EUR", symbol: "€", hint: "European Union Euro" },
{ id: 4, abbr: "CHF", symbol: "CHF", hint: "Swiss Francs" },
{ id: 5, abbr: "RUB", symbol: "pуб", hint: "Russian Rouble" },
{ id: 6, abbr: "PLN", symbol: "zł", hint: "Polish Złoty" },
{ id: 7, abbr: "BRL", symbol: "R$", hint: "Brazilian Reals" },
{ id: 8, abbr: "JPY", symbol: "¥", hint: "Japanese Yen" },
{ id: 9, abbr: "NOK", symbol: "kr", hint: "Norwegian Krone" },
{ id: 10, abbr: "IDR", symbol: "Rp", hint: "Indonesian Rupiah" },
{ id: 11, abbr: "MYR", symbol: "RM", hint: "Malaysian Ringgit" },
{ id: 12, abbr: "PHP", symbol: "P", hint: "Philippine Pes" },
{ id: 13, abbr: "SGD", symbol: "S$", hint: "Singapore Dollar" },
{ id: 14, abbr: "THB", symbol: "฿", hint: "Thai Baht" },
{ id: 15, abbr: "VND", symbol: "₫", hint: "Vietnamese Dong" },
{ id: 16, abbr: "KRW", symbol: "₩", hint: "South Korean Won" },
{ id: 17, abbr: "TRY", symbol: "TL", hint: "Turkish Lira" },
{ id: 18, abbr: "UAH", symbol: "₴", hint: "Ukrainian Hryvnia" },
{ id: 19, abbr: "MXN", symbol: "Mex$", hint: "Mexican Peso" },
{ id: 20, abbr: "CAD", symbol: "CDN$", hint: "Canadian Dollars" },
{ id: 21, abbr: "AUD", symbol: "A$", hint: "Australian Dollars" },
{ id: 22, abbr: "NZD", symbol: "NZ$", hint: "New Zealand Dollar" },
{ id: 23, abbr: "CNY", symbol: null, hint: "Chinese Renminbi (yuan)" },
{ id: 24, abbr: "INR", symbol: "₹", hint: "Indian Rupee" },
{ id: 25, abbr: "CLP", symbol: "CLP$", hint: "Chilean Peso" },
{ id: 26, abbr: "PEN", symbol: "S/.", hint: "Peruvian Nuevo Sol" },
{ id: 27, abbr: "COP", symbol: "COL$", hint: "Colombian Peso" },
{ id: 28, abbr: "ZAR", symbol: "R ", hint: "South African Rand" },
{ id: 29, abbr: "HKD", symbol: "HK$", hint: "Hong Kong Dollar" },
{ id: 30, abbr: "TWD", symbol: "NT$", hint: "New Taiwan Dollar" },
{ id: 31, abbr: "SAR", symbol: "SR", hint: "Saudi Riyal" },
{ id: 32, abbr: "AED", symbol: "DH", hint: "United Arab Emirates Dirham" },
{ id: 34, abbr: "ARS", symbol: "ARS$", hint: "Argentine Peso" },
{ id: 35, abbr: "ILS", symbol: "₪", hint: "Israeli New Shekel" },
{ id: 37, abbr: "KZT", symbol: "₸", hint: "Kazakhstani Tenge" },
{ id: 38, abbr: "KWD", symbol: "KD", hint: "Kuwaiti Dinar" },
{ id: 39, abbr: "QAR", symbol: "QR", hint: "Qatari Riyal" },
{ id: 40, abbr: "CRC", symbol: "₡", hint: "Costa Rican Colón" },
{ id: 41, abbr: "UYU", symbol: "$U", hint: "Uruguayan Peso" },
];
const findCurrencyById = (id) =>
steamCurrencies.find((currency) => currency.id === id);
const findCurrencyByAbbr = (abbr) =>
steamCurrencies.find((currency) => currency.abbr === abbr);
let valute = null;
let valuteSign = null;
let valuteID = null;
let currency = null;
let rate = null;
const makeRequest = (url) => {
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: "GET",
url,
headers: {
"Content-Type": "application/json",
},
onload: function (response) {
resolve(response.responseText);
},
onerror: function (error) {
reject(error);
},
});
});
};
const updateRates = async () => {
const randomNumber = Math.random();
const url = `https://cdn.jsdelivr.net/npm/@fawazahmed0/currency-api@latest/v1/currencies/rub.json?${randomNumber}`;
const data = await makeRequest(url);
const value = JSON.parse(data);
// 6-hour timeout for the value
GM_setValue("timeout_v2", Date.now() + 6 * 60 * 60 * 1000);
GM_setValue("currency_v2", value);
console.log("updateCurrency", value);
return value;
};
const getRates = async () => {
if (currency) return;
const timeout = GM_getValue("timeout_v2", null);
const cache = GM_getValue("currency_v2", null);
const rateDate = cache ? Date.parse(cache.date) : Date.now();
console.log("getCurrency CACHE", timeout, cache);
// No cache OR no timeout OR timeout is after the current date OR rate internal date is after 1 day from now (failsafe)
if (
!cache ||
!timeout ||
timeout <= Date.now() ||
rateDate + 24 * 60 * 60 * 1000 <= Date.now()
) {
currency = await updateRates();
console.log("getCurrency NEW", currency);
return;
}
currency = cache;
console.log("getCurrency CACHED", currency);
};
const injectPrice = async (element) => {
const classList = element.classList.value;
element.classList.add("done");
if (classList.includes("es-regprice") || classList.includes("es-converted"))
return; // Augmented Steam fix
if (!element.parentElement.innerText.includes(valuteSign)) return;
if (classList.includes("discount_original_price")) return;
if (classList.includes("done") && element.innerText.includes("₽")) return;
let inline = false;
if (
element.parentElement?.parentElement?.classList.value.includes(
"item_market_actions"
)
)
inline = true;
let price;
if (element.dataset && element.dataset.priceFinal) {
price = element.dataset.priceFinal / 100;
} else {
const removeDiscountedPrice = element.querySelector("span > strike");
if (removeDiscountedPrice) {
removeDiscountedPrice.remove();
element.classList.remove("discounted");
element.style.color = "#BEEE11";
}
let text = element.innerText
.trim()
.split(/\r?\n|\r|\n/g)[0]
.replace(/[^0-9.,-]+/g, "");
if (valute !== "USD") {
text = text.replace(".", "").replace(",", ".");
}
price = Number(text);
}
if (!price) return;
let convertedPrice = Math.ceil(price / rate) * 100;
let formattedPrice = null;
try {
formattedPrice = GStoreItemData.fnFormatCurrency(convertedPrice, true);
} catch (e) {
// Skip
}
if (!formattedPrice) {
try {
formattedPrice = v_currencyformat(convertedPrice, valute);
} catch (e) {
// Skip
}
}
formattedPrice = formattedPrice
.replace(valuteSign, "")
.replace(" ", "")
.replace("USD", "")
.trim();
suffix = "";
if (window.location.href.includes("account/history")) {
if (element.innerText.includes("Credit")) {
suffix =
element.querySelector("div.wth_payment").cloneNode(true).outerHTML ||
"";
}
}
if (inline) {
element.innerHTML = `
${element.innerHTML}(≈${formattedPrice} ₽)${suffix}
`;
} else {
element.innerHTML = `
${element.innerText
.replace(" ", " ")
.replace("ARS$ ", "$")
.replace("Credit", "")
.trim()}
≈${formattedPrice} ₽
${suffix}
`;
}
};
const grabCurrentCurrency = () => {
if (valute) return;
let currency = document.querySelector('meta[itemprop="priceCurrency"]');
if (currency && currency.content) {
valute = currency.content;
valuteSign = Object.keys(signToValute).find(
(key) => signToValute[key] === valute
);
return;
}
currency = null;
try {
// eslint-disable-next-line no-undef
currency = GStoreItemData.fnFormatCurrency(12345)
.replace("123,45", "")
.replace("123.45", "")
.trim();
valute = signToValute[currency];
valuteSign = currency;
return;
} catch (e) {
console.warn(e);
}
currency = findCurrencyById(g_rgWalletInfo.wallet_currency);
if (currency) {
valute = currency.abbr;
valuteSign = currency.symbol;
}
return;
};
const globalClasses = [
"#header_wallet_balance",
"div[class*=StoreSalePriceBox]",
".game_purchase_price",
".discount_final_price:not(:has(> .your_price_label))",
".discount_final_price > div:not([class])",
".search_price",
".price:not(.spotlight_body):not(.similar_grid_price)",
".match_subtitle",
".game_area_dlc_price:not(:has(> *))",
".savings.bundle_savings",
".wallet_column",
".wht_total",
".normal_price:not(.market_table_value)",
".sale_price",
".StoreSalePriceWidgetContainer:not(.Discounted) div",
".StoreSalePriceWidgetContainer.Discounted div:nth-child(2) > div:nth-child(2)",
// Market
"#marketWalletBalanceAmount",
".market_commodity_order_summary > span:nth-child(2)",
".market_commodity_orders_table tr > td:first-child",
".market_listing_price_with_fee",
".market_activity_price",
// Inventory
".item_market_actions > div > div:nth-child(2)",
// eslint-disable-next-line no-return-assign
]
.map((x) => (x += ":not(.done)"))
.join(", ");
const observerInject = async () => {
const prices = document.querySelectorAll(globalClasses);
if (!prices) return;
for (const price of prices) await injectPrice(price);
};
const main = async () => {
"use strict";
// Updating currency
await getRates();
console.log("Current rates", currency);
// Grabbing
await grabCurrentCurrency();
console.log("Current valute", valute);
console.log("Current valute sign", valuteSign);
if (valute === "RUB") {
console.log("Привет, земляк ;)");
return;
}
if (!valute) throw new Error("No valute detected!");
// Grabbing the rate
rate = Math.round(currency.rub[valute.toLowerCase()] * 100) / 100;
console.log("Effective rate", rate);
if (!currency || !valute || !rate) return;
// Add styles
let css = `
.tab_item_discount { width: 160px !important; } .tab_item_discount .discount_prices { width: 100% !important; } .tab_item_discount .discount_final_price { padding: 0 !important; }
.home_marketing_message.small .discount_block { height: auto !important; } .discount_block_inline { white-space: nowrap !important; }
.curator #RecommendationsRows .store_capsule.price_inline .discount_block { min-width: 200px !important }
.market_listing_their_price { min-width: 130px !important; }
`;
GM_addStyle(css);
// Injecting prices for the first time
await observerInject();
// Dynamically inject prices
const observer = new MutationObserver(async (mutations, _observer) => {
try {
await observerInject();
for (const mutation of mutations) {
// Checking added elements
for (const node of mutation.addedNodes) {
observerInject();
// if (node.nodeType === Node.TEXT_NODE) {
// if (!node.parentElement?.innerText.includes(valuteSign)) continue;
// await injectPrice(node.parentElement);
// }
}
}
} catch (error) {
console.log(error);
}
});
observer.observe(document.body, {
childList: true,
subtree: true,
attributes: true,
});
};
window.onload = main();