// ==UserScript==
// @name Currency Converter
// @namespace http://tampermonkey.net/
// @version 2.2.3
// @description try to take over the world!
// @icon http://store.steampowered.com/favicon.ico
// @author Bisumaruko
// @include http*://yuplay.ru/*
// @include http*://*.gamersgate.com/*
// @include http*://www.greenmangaming.com/*
// @include http*://gama-gama.ru/*
// @include http*://*.gamesplanet.com/*
// @include http*://www.cdkeys.com/*
// @include http*://directg.net/*
// @include http*://www.humblebundle.com/*
// @include http*://www.indiegala.com/*
// @include http*://www.bundlestars.com/*
// @include http*://www.opiumpulses.com/*
// @require https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.slim.min.js
// @require https://cdnjs.cloudflare.com/ajax/libs/limonte-sweetalert2/6.6.6/sweetalert2.min.js
// @resource SweetAlert2CSS https://cdnjs.cloudflare.com/ajax/libs/limonte-sweetalert2/6.6.6/sweetalert2.min.css
// @grant GM_xmlhttpRequest
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_addStyle
// @grant GM_getResourceText
// @run-at document-start
// @connect api.fixer.io
// ==/UserScript==
/*
global GM_xmlhttpRequest, GM_setValue, GM_getValue, GM_addStyle, GM_getResourceText,
$, swal, games,
document, location, MutationObserver
*/
// inject swal css
GM_addStyle(GM_getResourceText('SweetAlert2CSS'));
// setup swal
swal.setDefaults({
timer: 3000,
useRejections: false
});
// load config
const config = JSON.parse(GM_getValue('Bisko_CC', '{}'));
const interval = 3 * 60 * 60 * 1000; // update exchange rate every 3 hours
const currencies = {
ORI: {
nameEN: '',
nameZH: '恢復',
symbol: ''
},
AUD: {
nameEN: 'Australian Dollar',
nameZH: '澳幣',
symbol: 'AU$'
},
CAD: {
nameEN: 'Canadian Dollar',
nameZH: '加幣',
symbol: 'CA$'
},
CNY: {
nameEN: 'Chinese Yuan',
nameZH: '人民幣',
symbol: 'CN¥'
},
EUR: {
nameEN: 'Euro',
nameZH: '歐元',
symbol: '€'
},
GBP: {
nameEN: 'Great Britain Pound',
nameZH: '英鎊',
symbol: '£'
},
HKD: {
nameEN: 'Hong Kong Dollar',
nameZH: '港幣',
symbol: 'HK$'
},
JPY: {
nameEN: 'Japanese Yen',
nameZH: '日圓',
symbol: 'JP¥'
},
KRW: {
nameEN: 'South Korean Won',
nameZH: '韓圓',
symbol: '₩'
},
MYR: {
nameEN: 'Malaysian Ringgit',
nameZH: '令吉',
symbol: 'RM'
},
NTD: {
nameEN: 'New Taiwan Dollar',
nameZH: '台幣',
symbol: 'NT$'
},
NZD: {
nameEN: 'New Zealand Dollar',
nameZH: '紐幣',
symbol: 'NZ$'
},
RUB: {
nameEN: 'Russian Ruble',
nameZH: '盧布',
symbol: 'руб'
},
USD: {
nameEN: 'United States Dollar',
nameZH: '美元',
symbol: 'US$'
}
};
let originalCurrency = 'USD';
// jQuery extension
$.fn.price = function price() {
return this.eq(0).text().replace(/[^.0-9]/g, '');
};
$.fn.bindPriceData = function bindPriceData() {
return this.each((index, element) => {
const $ele = $(element);
$ele.addClass('CCPrice').attr('data-CCPrice', JSON.stringify({
currency: originalCurrency,
price: $ele.text().replace(/[^.0-9]/g, '')
}));
});
};
// constructing functions
const has = Object.prototype.hasOwnProperty;
const preferredCurrency = () => has.call(config.exchangeRate.rates, config.preferredCurrency) ? config.preferredCurrency : 'CNY'; // default to CNY
const updateCurrency = (currency = null) => {
let targetCurrency = currency || preferredCurrency();
$('.CCPrice').each((index, element) => {
const $ele = $(element);
const data = JSON.parse($ele.attr('data-CCPrice'));
let convertedPrice = 0;
if (targetCurrency === 'ORI') {
targetCurrency = data.currency;
convertedPrice = data.price;
} else {
const rates = config.exchangeRate.rates;
convertedPrice = data.price * (rates[targetCurrency] / rates[data.currency]);
}
$ele.text(convertedPrice.toLocaleString('en', {
style: 'currency',
currency: targetCurrency,
maximumFractionDigits: 2
}).replace('MYR', currencies.MYR.symbol).replace('NTD', currencies.NTD.symbol));
});
};
const constructMenu = () => {
const $li = $('<li class="Bisko_CC_Menu"><a>Currencies</a></li>');
const $ul = $('<ul></ul>').appendTo($li);
const preferred = preferredCurrency();
GM_addStyle(`
.Bisko_CC_Menu ul {
display: none;
position: absolute;
padding: 0;
background-color: #272727;
z-index: 9999;
}
.Bisko_CC_Menu:hover ul { display: block; }
.Bisko_CC_Menu li { padding: 2px 10px; list-style-type: none; cursor: pointer; }
.Bisko_CC_Menu li:hover, .preferred { background-color: SandyBrown; }
`);
Object.keys(currencies).forEach(currency => {
const itemName = `${currency} ${currencies[currency].nameZH}`;
$ul.append($(`<li class="${currency}">${itemName}</li>`).addClass(() => preferred === currency ? 'preferred' : '').click(() => {
config.preferredCurrency = currency;
GM_setValue('Bisko_CC', JSON.stringify(config));
updateCurrency(currency);
$('.Bisko_CC_Menu .preferred').removeClass('preferred');
$(`.Bisko_CC_Menu .${currency}`).addClass('preferred');
}));
});
return $li;
};
const handler = () => {
switch (location.host) {
case 'yuplay.ru':
originalCurrency = 'RUB';
GM_addStyle(`
.games-pricedown span.CCPrice { font-size: 18px; }
.good-title span.CCPrice { margin-right: 3px; font-size: 22px; }
`);
$('.header-right').append(constructMenu());
// homepage games-pricedown
$('.games-pricedown .sale > s, .games-pricedown .price').bindPriceData();
// homepage game box & product page
$('.games-box .price, .good-title .price').contents().each((index, node) => {
const $node = $(node);
const text = $node.text().trim();
if (node.tagName === 'S') $node.bindPriceData(); // retail price
if (node.tagName === 'SPAN') $node.remove(); // currency
else if (node.nodeType === 3 && text.length > 0) {
// sales price
$node.replaceWith($(`<span>${text}<span>`).bindPriceData());
}
});
break;
case 'gama-gama.ru':
originalCurrency = 'RUB';
GM_addStyle(`
.Bisko_CC_Menu > a, .Bisko_CC_Menu li { color: white; }
.Bisko_CC_Menu ul { margin: 0; }
`);
$('#top_back').append(constructMenu().wrapInner('<div class="Bisko_CC_Menu top_menu"></div>').children().unwrap());
// homepage
$('.price_1, .old_price, .promo_price').bindPriceData();
// product page
$('.card-info-oldprice > span, .card-info-price > span').bindPriceData();
$('.card-info-oldprice, .card-info-price').contents().filter((i, node) => node.nodeType !== 1).remove();
break;
// case 'www.gamersgate.com':
case 'ru.gamersgate.com':
case 'cn.gamersgate.com':
if (location.host.startsWith('ru')) originalCurrency = 'RUB';
if (location.host.startsWith('cn')) originalCurrency = 'CNY';
GM_addStyle(`
.Bisko_CC_Menu { width: 49px; text-align: center; }
.Bisko_CC_Menu svg {
width: 36px;
height: 36px;
background-color: #093745;
border: 1px solid #2c7c92;
}
.Bisko_CC_Menu, .Bisko_CC_Menu li { background-image: none !important; color: white; }
.Bisko_CC_Menu li {
height: initial !important;
float: none !important;
padding: 5px 10px !important;
}
.Bisko_CC_Menu li:hover, .preferred { background-color: SandyBrown !important; }
`);
$('.btn_menuseparator').replaceWith(constructMenu());
$('.Bisko_CC_Menu a').replaceWith(`
<svg viewBox="0 0 24 24">
<path fill="#a9ebea" d="M11.8,10.9C9.53,10.31 8.8,9.7 8.8,8.75C8.8,7.66 9.81,6.9 11.5,6.9C13.28,6.9 13.94,7.75 14,9H16.21C16.14,7.28 15.09,5.7 13,5.19V3H10V5.16C8.06,5.58 6.5,6.84 6.5,8.77C6.5,11.08 8.41,12.23 11.2,12.9C13.7,13.5 14.2,14.38 14.2,15.31C14.2,16 13.71,17.1 11.5,17.1C9.44,17.1 8.63,16.18 8.5,15H6.32C6.44,17.19 8.08,18.42 10,18.83V21H13V18.85C14.95,18.5 16.5,17.35 16.5,15.3C16.5,12.46 14.07,11.5 11.8,10.9Z" />
</svg>
`);
// homepage & product page
$(`
.prtag > span,
.grid-old-price,
.price_price > span,
div > span > .bold.white,
li > .f_right:nth-child(2) > span
`).bindPriceData();
break;
case 'www.greenmangaming.com':
GM_addStyle(`
.Bisko_CC_Menu > a {
margin: 0 !important;
padding: 6px 15px !important;
font-size: 14px;
}
.Bisko_CC_Menu > a:hover, .Bisko_CC_Menu li:hover, .preferred { background-color: #494a4f !important; }
`);
$('.megamenu').append(constructMenu());
try {
// product page
originalCurrency = games.Currency;
$('.prices-info > p').bindPriceData();
} catch (e) {
// home page & search page
const currencyObserver = new MutationObserver(mutations => {
mutations.forEach(mutation => {
mutation.addedNodes.forEach(addedNode => {
if (addedNode.tagName === 'SCRIPT' && addedNode.src.includes('bam.nr-data.net')) {
const currency = decodeURI(addedNode.src).split('currency_code":"').pop().slice(0, 3);
if (currency.length > 0) {
originalCurrency = currency;
const $prices = $('.prices > span, .prices > p, .listing-price > span');
// offset decimal place in Europe (. ,)
if (['EUR', 'RUB'].includes(currency)) $prices.text((i, text) => text.replace(',', '.'));
$prices.bindPriceData();
updateCurrency();
}
currencyObserver.disconnect();
}
});
});
});
currencyObserver.observe(document.head, { childList: true });
// search listing
const $listing = $('.table-search-listings');
if ($listing.length > 0) {
new MutationObserver(mutations => {
mutations.forEach(mutation => {
mutation.addedNodes.forEach(addedNode => {
$(addedNode).find('.listing-price > span').bindPriceData();
updateCurrency();
});
});
}).observe($listing[0], { childList: true });
}
}
break;
case 'uk.gamesplanet.com':
case 'de.gamesplanet.com':
case 'fr.gamesplanet.com':
{
originalCurrency = !location.host.startsWith('uk') ? 'EUR' : 'GBP';
const GPHandler = () => {
const $prices = $('.price_base > strike, .price_current');
$('.container > ul').append(constructMenu());
// offset decimal place in Europe (. ,)
if (!location.host.startsWith('uk')) {
$prices.text((i, text) => text.replace(',', '.'));
}
$prices.bindPriceData();
updateCurrency();
};
GPHandler(true);
new MutationObserver(mutations => {
mutations.forEach(mutation => {
mutation.removedNodes.forEach(removedNode => {
if (removedNode.id === 'nprogress') GPHandler();
});
});
}).observe(document, {
childList: true,
subtree: true
});
break;
}
case 'www.cdkeys.com':
{
originalCurrency = $('.currency-switcher:first-child .value').text();
const currenciesDefault = ['AUD', 'CAD', 'EUR', 'GBP', 'JPY', 'NZD', 'USD'];
const currenciesAppend = ['CNY', 'HKD', 'KRW', 'MYR', 'NTD', 'RUB'];
// bind event to default currency switcher to save preferrred currency
$('.currency-switcher:first-child ul span').each((index, element) => {
const $ele = $(element);
const currency = $ele.text().slice(0, 3);
if (currenciesDefault.includes(currency)) {
$ele.parent().click(() => {
config.preferredCurrency = currency;
GM_setValue('Bisko_CC', JSON.stringify(config));
});
}
});
// append currencies not in the default currency switcher
currenciesAppend.forEach(currency => {
$('.currency-switcher:first-child ul').append($(`<li><a>${currency} - ${currencies[currency].nameEN}</a></li>`).click(() => {
config.preferredCurrency = currency;
GM_setValue('Bisko_CC', JSON.stringify(config));
updateCurrency(currency);
}));
});
$('.price').bindPriceData();
break;
}
case 'directg.net':
originalCurrency = 'KRW';
GM_addStyle(`
.Bisko_CC_Menu ul {
width: 180px;
border-top: 3px solid #67c1f5;
background-color: #1b2838 !important;
opacity: 0.95;
color: rgba(255,255,255,0.5) !important;
}
.Bisko_CC_Menu ul li { padding: 10px 30px; font-weight: bold; }
.Bisko_CC_Menu ul li:hover, .preferred { background-color: initial !important; color: white; }
`);
$('.nav').append(constructMenu());
$('span.PricebasePrice, span.PricesalesPrice').bindPriceData().next('span[itemprop="priceCurrency"]').remove();
break; /*
case 'www.origin.com': {
const region = location.pathname.split('/')[1];
const currencyRegion = {
twn: 'NTD',
jpn: 'JPY',
rus: 'RUB',
usa: 'USD',
nzl: 'NZD',
irl: 'EUR',
kor: 'KRW',
};
}*/
case 'www.humblebundle.com':
originalCurrency = 'USD';
GM_addStyle(`
.Bisko_CC_Menu { float: left; }
.Bisko_CC_Menu > a { display: block; padding: 15px 20px; }
.Bisko_CC_Menu ul { background-color: #494f5c !important; color: rgba(255, 255, 255, 0.6); }
.Bisko_CC_Menu ul li { padding: 5px 10px; }
.Bisko_CC_Menu ul li:hover, .preferred { background-color: initial !important; color: white; }
`);
$('.nav:first-child').append(constructMenu());
new MutationObserver(mutations => {
mutations.forEach(mutation => {
mutation.removedNodes.forEach(removedNode => {
if (removedNode.data === 'The Humble Store: Loading') {
$('.price, .store-price, .full-price, .current-price').bindPriceData();
updateCurrency();
}
});
});
}).observe(document.head, {
childList: true,
subtree: true
});
break;
case 'www.indiegala.com':
originalCurrency = 'USD';
GM_addStyle(`
.Bisko_CC_Menu ul {
transform: translateY(-100%);
background-color: #474747 !important;
border: 1px solid #272727;
}
.Bisko_CC_Menu ul li { padding: 8px 15px; color: #dad6ca; }
.Bisko_CC_Menu ul li:hover, .preferred { background-color: #2E2E2E !important; color: white; }
`);
$('#libdContainer > ul:not(:first-child)').append(constructMenu());
$('.Bisko_CC_Menu > a').addClass('libd-group-item libd-bounce');
// homepage & product page
$('.inner-info, .inner, .price-container').contents().each((index, node) => {
const $node = $(node);
const text = $node.text().trim();
$node.parent().parent().css('overflow', 'hidden');
if (node.nodeType === 1 && node.tagName !== 'BR') $node.bindPriceData();else if (node.nodeType === 3 && text.length) {
$node.replaceWith($(`<a>${text}</a>`).bindPriceData());
}
});
// search result
$('.price-cont').bindPriceData();
break;
case 'www.bundlestars.com':
originalCurrency = 'USD';
GM_addStyle(`
.Bisko_CC_Menu ul { background-color: #212121 !important; }
.Bisko_CC_Menu ul li { padding: 8px 15px; }
.Bisko_CC_Menu ul li:hover, .preferred { background-color: #1A1A1A !important; }
`);
$('.nav').eq(0).append(constructMenu());
new MutationObserver(mutations => {
mutations.forEach(mutation => {
mutation.removedNodes.forEach(removedNode => {
if (removedNode.id === 'loading-bar-spinner') {
$('.bs-price, .bs-currency-discount > span, .bs-currency-price').bindPriceData();
$('.bs-card-meta > .bs-pricing, .bs-card-meta > .bs-currency-discount').css({
position: 'absolute',
right: '19px'
});
updateCurrency();
}
});
});
}).observe(document.body, { childList: true });
break;
case 'www.opiumpulses.com':
originalCurrency = 'USD';
GM_addStyle(`
.Bisko_CC_Menu {
float: right;
margin-top: 5px;
padding: 1px 5px;
vertical-align: middle;
background-image: linear-gradient(#4E4E4E, #101112 40%, #191b1d);
font-size: 12px;
line-height: 1.5;
border-radius: 3px;
border-bottom-right-radius: 0;
border-top-right-radius: 0;
}
.Bisko_CC_Menu > a { color: #f89406; }
`);
$('.top-bar > .container > form').after(constructMenu().wrapInner('<div class="Bisko_CC_Menu top_menu"></div>').children().unwrap());
// homepage
$('.album__container p > span:first-of-type').bindPriceData();
// store page
$('.product-box s, .product-box .text-danger').bindPriceData();
new MutationObserver(mutations => {
mutations.forEach(mutation => {
mutation.addedNodes.forEach(addedNode => {
if (addedNode.id === 'productlistview') {
$(addedNode).find('.product-box s, .product-box .text-danger').bindPriceData();
updateCurrency();
}
});
});
}).observe(document.body, {
childList: true,
subtree: true
});
break;
default:
}
// only update prices when appended currencies are selected
if (originalCurrency !== preferredCurrency()) updateCurrency();
};
const getExchangeRate = () => {
GM_xmlhttpRequest({
method: 'GET',
url: 'http://api.fixer.io/latest',
onload: res => {
if (res.status === 200) {
try {
const exchangeRate = JSON.parse(res.response);
config.exchangeRate = exchangeRate;
config.lastUpdate = Date.now();
// add EUR, NTD
config.exchangeRate.rates.EUR = 1;
config.exchangeRate.rates.NTD = config.exchangeRate.rates.HKD * 3.9;
GM_setValue('Bisko_CC', JSON.stringify(config));
handler();
} catch (e) {
swal('Parsing Failed', 'An error occured when parsing exchange rate data, please reload to try again', 'error');
}
} else {
swal('Loading Failed', 'Unable to fetch exchange rate data, please reload to try again', 'error');
}
}
});
};
$(() => {
if (Object.keys(config).length === 0) getExchangeRate(); // first installed
else if (Date.now() - interval > config.lastUpdate) getExchangeRate(); // update exchange rate
else handler();
});