|
// ==UserScript== |
|
// @name Steam valute to RUB convertation |
|
// @namespace https://store.steampowered.com/ |
|
// @version 0.9 |
|
// @description for StopGame boosty chat |
|
// @author CJMAXiK |
|
// @homepage https://dtf.ru/u/3920-cjmaxik/2582934-perevod-valyuty-steam-dlya-puteshestvennikov |
|
// @downloadURL https://gist.github.com/cjmaxik/7ce493d08958eecd56a78c01482e49fa/raw/script.user.js |
|
// @updateURL https://gist.github.com/cjmaxik/7ce493d08958eecd56a78c01482e49fa/raw/script.user.js |
|
// @match https://store.steampowered.com/* |
|
// @icon https://www.google.com/s2/favicons?sz=64&domain=steampowered.com |
|
// @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', |
|
} |
|
|
|
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) |
|
|
|
// 12-hour timeout for the value |
|
GM_setValue('timeout_v2', Date.now() + 12 * 3600 * 1000) |
|
GM_setValue('currency_v2', value) |
|
|
|
// Clear v0.1 - v0.3 cache |
|
GM_deleteValue('timeout', null) |
|
GM_deleteValue('currency', null) |
|
|
|
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 2 days from now (failsafe) |
|
if ( |
|
!cache || |
|
!timeout || |
|
timeout <= Date.now() || |
|
rateDate + 48 * 3600 * 1000 <= Date.now() |
|
) { |
|
currency = await updateRates() |
|
console.log('getCurrency NEW', currency) |
|
return |
|
} |
|
|
|
currency = cache |
|
console.log('getCurrency CACHED', currency) |
|
} |
|
|
|
const injectPrice = async (element) => { |
|
if (element?.id === 'header_wallet_balance') return |
|
|
|
const classList = element.classList.value |
|
|
|
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' |
|
} |
|
|
|
price = Number( |
|
element.innerText |
|
.replace(/[^0-9.,-]+/g, '') |
|
.replace('.', '') |
|
.replace(',', '.') |
|
) |
|
} |
|
|
|
if (!price) return |
|
|
|
let convertedPrice = Math.ceil(price / rate) |
|
convertedPrice = GStoreItemData.fnFormatCurrency(convertedPrice * 100, true) |
|
.replace(valuteSign, '') |
|
.replace(' ', '') |
|
.trim() |
|
|
|
element.innerHTML = element.innerText.replace('ARS$ ', '$') |
|
element.innerHTML = |
|
`<span style="font-size: 11px;">` + |
|
element.innerText.replace(' ', ' ').trim() + |
|
`</span><span style="padding-left: 5px;">≈${convertedPrice} ₽</span>` |
|
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', '') |
|
.trim() |
|
valute = signToValute[valuteFromPrice] |
|
valuteSign = valuteFromPrice |
|
} catch (e) { |
|
console.warn(e) |
|
return |
|
} |
|
} |
|
|
|
const globalClasses = [ |
|
'.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)', |
|
'#header_wallet_balance', |
|
'div[class*=StoreSalePriceBox]', |
|
'.match_subtitle', |
|
'.game_area_dlc_price:not(:has(> *))', |
|
'.savings.bundle_savings', |
|
// 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() |