Steam valute to RUB conversion

Steam tourists, rejoice!

// ==UserScript==
// @name            Steam valute to RUB conversion
// @name:ru         Перевод валюты в рубли в Steam
// @version         1.1.2
// @namespace       https://store.steampowered.com/
// @description     Steam tourists, rejoice!
// @description:ru  Путешественники в Steam, объединяйтесь!
// @author          CJMAXiK
// @license         MIT
// @homepage        https://cjmaxik.com
// @match           https://store.steampowered.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
// ==/UserScript==

const signToValute = {
  "₸": "KZT",
  TL: "TRY",
  "€": "EUR",
  "£": "GBP",
  ARS$: "ARS",
  "₴": "UAH",
  $: "USD",
};

let valute = null;
let valuteSign = 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;
  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 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.replace(/[^0-9.,-]+/g, "");
    if (valute !== "USD") {
      text = text.replace(".", "").replace(",", ".");
    }
    price = Number(text);
  }

  if (!price) return;

  let convertedPrice = Math.ceil(price / rate);
  convertedPrice = GStoreItemData.fnFormatCurrency(convertedPrice * 100, true)
    .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 ||
        "";
    }
  }

  element.innerHTML = `
    <span style="font-size: 11px;">
      ${element.innerText
        .replace(" ", "&nbsp;")
        .replace("ARS$ ", "$")
        .replace("Credit", "")
        .trim()}
    </span>
    <span style="padding-left: 5px;">
      ≈${convertedPrice}&nbsp;₽
    </span>${suffix}
  `;
  element.classList.add("done");
};

const grabCurrentCurrency = () => {
  if (valute) return;

  let currency = document.querySelector('meta[itemprop="priceCurrency"]');
  console.log(currency);
  if (currency && currency.content) {
    valute = currency.content;
    valuteSign = Object.keys(signToValute).find(
      (key) => signToValute[key] === valute
    );
    return;
  }

  try {
    // eslint-disable-next-line no-undef
    const valuteFromPrice = GStoreItemData.fnFormatCurrency(12345)
      .replace("123,45", "")
      .replace("123.45", "")
      .trim();
    valute = signToValute[valuteFromPrice];
    valuteSign = valuteFromPrice;
  } catch (e) {
    console.warn(e);
    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",
  // 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; }";
  css +=
    ".home_marketing_message.small .discount_block { height: auto !important; } .discount_block_inline { white-space: nowrap !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) {
          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();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址