// ==UserScript==
// @name Currency Converter
// @namespace http://tampermonkey.net/
// @version 2.1.0
// @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.origin.com/*
// @include http*://www.humblebundle.com/*
// @include http*://www.indiegala.com/*
// @include http*://www.bundlestars.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,
document, location, MutationObserver
*/
// inject 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 = () => {
const preferred = config.preferredCurrency;
if (has.call(config.exchangeRate.rates, preferred)) return preferred;
return '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) {
$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
$('.grid-old-price, .prtag > span, div > span > .bold.white, .price_price > span, li > .f_right:nth-child(2) > span').bindPriceData();
break;
case 'www.greenmangaming.com':
originalCurrency = $('.currency-code').eq(0).text();
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());
// home page
new MutationObserver(mutations => {
mutations.forEach(mutation => {
mutation.addedNodes.forEach(addedNode => {
if (addedNode.src && addedNode.src.includes('bam.nr-data.net')) {
const $prices = $('.prices > span');
const src = decodeURI(addedNode.src);
originalCurrency = src.split('currency_code":"').pop().slice(0, 3) || 'USD';
// offset decimal place in Europe (. ,)
if (['EUR', 'RUB'].includes(originalCurrency)) {
$prices.text((i, text) => text.replace(',', '.'));
}
$prices.bindPriceData();
updateCurrency();
}
});
});
}).observe(document.head, {
childList: true
});
// product page
$('price > span').bindPriceData();
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.slice(1).split('/').shift();
const currencyRegion = {
twn: 'NTD',
jpn: 'JPY',
rus: 'RUB',
usa: 'USD',
nzl: 'NZD',
irl: 'EUR',
};
originalCurrency = 'USD';
break;
}*/
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;
default:
}
// only update prices when appended currencies are selected
if (originalCurrency && 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();
});