// ==UserScript==
// @name Steam Wholesale Sites Extension
// @namespace http://tampermonkey.net/
// @version 1.0.1
// @description try to take over the world!
// @icon http://store.steampowered.com/favicon.ico
// @author Bisumaruko
// @include https://mini.wmtransfer.com/*
// @include http://steamkeys.ovh/*
// @include http://steamfarmkey.ru/*
// @include http://steam1.lequeshop.ru/*
// @include http://steam1.ru/*
// @include http://lastkey.ru/*
// @include http://steamkeyswhosales.com/*
// @include http://alfakeys.ru/*
// @include http://cheap-steam-games.ru/*
// @include http://dmshop.lequeshop.ru/*
// @include http://kartonanet.lequeshop.ru/*
// @include http://keyssell.ru/*
// @include http://keys.farm/*
// @include http://rig4all.lequeshop.ru/*
// @include http://steam-tab.ru/*
// @include http://steamd.lequeshop.ru/*
// @include http://steamkeys-shop.ru/*
// @include http://steamkey.lequeshop.ru/*
// @include http://steamkeystore.ru/*
// @include http://farmacc.ru/*
// @include http://steamrandomkeys.ru/*
// @include http://animekeys.ru/*
// @include http://drunkpatrick.store/*
// @include http://steamfarm.lequestore.ru/*
// @include http://maxfarmshop.ru/*
// @include http://bestkeystore.ru/*
// @include http://bestfarmkey.lequestore.ru/*
// @include http://tkfg.ru/*
// @include http://indiegamekeys.lequestore.ru/*
// @include http://m-b-shop.leque.shop/*
// @include http://200plus.lequeshop.ru/*
// @include http://randomkey.ru/*
// @require https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js
// @require https://cdnjs.cloudflare.com/ajax/libs/limonte-sweetalert2/7.0.6/sweetalert2.min.js
// @resource SweetAlert2CSS https://cdnjs.cloudflare.com/ajax/libs/limonte-sweetalert2/7.0.6/sweetalert2.min.css
// @connect store.steampowered.com
// @grant GM_xmlhttpRequest
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_addStyle
// @grant GM_getResourceText
// @run-at document-start
// ==/UserScript==
/* global GM_xmlhttpRequest, GM_setValue, GM_getValue, GM_addStyle, GM_getResourceText,
window, document, location, fetch, localStorage, Option, sessionStorage,
swal */
// inject swal css
GM_addStyle(GM_getResourceText('SweetAlert2CSS'));
// inject styles
GM_addStyle(`
.SWSE_hide { display: none; }
.SWSE_settings, .SWSE_settings input { width: -webkit-fill-available; width: -moz-available; }
.SWSE_settings .name { text-align: right; vertical-align: top; }
.SWSE_settings .value { text-align: left; }
.SWSE_settings .value > * { height: 30px; margin: 0 20px 10px; }
.SWSE_settings .switch { position: relative; display: inline-block; width: 60px; }
.SWSE_settings .switch input { display: none; }
.SWSE_settings .slider {
position: absolute;
cursor: pointer;
top: 0; left: 0; right: 0; bottom: 0;
background-color: #ccc;
transition: 0.4s;
}
.SWSE_settings .slider:before {
position: absolute;
content: "";
height: 26px; width: 26px;
left: 2px; bottom: 2px;
background-color: white;
transition: 0.4s;
}
.SWSE_settings input:checked + .slider { background-color: #2196F3; }
.SWSE_settings input:focus + .slider { box-shadow: 0 0 1px #2196F3; }
.SWSE_settings input:checked + .slider:before { transform: translateX(30px); }
.SWSE_settings > span { display: inline-block; cursor: pointer; color: white; }
.SWSE_owned {
background-image: linear-gradient(135deg, rgba(0, 0, 0, 0.3) 60%, rgba(0, 0, 0, 0.1) 90%) !important;
background-color: #9ccc65 !important;
transition: background 500ms ease 0s;
color: white !important;
}
.SWSE_owned a { color: white !important; }
`);
// load up
const regURL = /(https?:\/\/)?([.\w]*steam[-.\w]*){1}\/.*?(apps?|subs?){1}\/(\d+){1}(\/.*\/?)?/m;
const regKey = /((?:([A-Z0-9])(?!\2{4})){5}-){2,5}[A-Z0-9]{5}/g;
const eol = "\r\n";
const has = Object.prototype.hasOwnProperty;
const owned = JSON.parse(localStorage.getItem('SWSE_owned') || '{}');
const config = {
data: JSON.parse(GM_getValue('SWSE_config') || '{}'),
set(key, value, callback = null) {
this.data[key] = value;
GM_setValue('SWSE_config', JSON.stringify(this.data));
if (typeof callback === 'function') callback();
},
get(key) {
return has.call(this.data, key) ? this.data[key] : null;
},
init() {
if (!has.call(this.data, 'language')) this.data.language = 'english';
if (!has.call(this.data, 'count')) this.data.count = 1;
if (!has.call(this.data, 'email')) this.data.email = '';
}
};
// text
const i18n = {
tchinese: {
name: '繁體中文',
settingsTitle: '設定',
settingsCount: '購買數量',
settingsEmail: '購買信箱',
settingsLanguage: '語言',
menuHideOwned: '隱藏已擁有',
menuSyncLibrary: '同步遊戲庫',
keyField: 'Steam 序號',
payButtonText: '付款',
syncSuccessTitle: '同步成功',
syncSuccess: '成功同步Steam 遊戲庫資料'
},
schinese: {
name: '简体中文',
settingsTitle: '设置',
settingsCount: '购买数量',
settingsEmail: '购买邮箱',
settingsLanguage: '语言',
menuHideOwned: '隐藏已拥有',
menuSyncLibrary: '同步游戏库',
keyField: 'Steam 激活码',
payButtonText: '付款',
syncSuccessTitle: '同步成功',
syncSuccess: '成功同步Steam 游戏库资料'
},
english: {
name: 'English',
settingsTitle: 'Settings',
settingsCount: 'Purchase Quantity',
settingsEmail: 'Purchase Email',
settingsLanguage: 'Language',
menuHideOwned: 'Hide Owned',
menuSyncLibrary: 'Sync Library',
keyField: 'Steam Keys',
payButtonText: 'Pay',
syncSuccessTitle: 'Sync Successful',
syncSuccess: 'Successfully sync Steam library data'
}
};
let text = has.call(i18n, config.get('language')) ? i18n[config.get('language')] : i18n.english;
const settingsHandler = arg => {
swal.showLoading();
arg();
setTimeout(swal.hideLoading, 500);
};
const settings = () => {
const panelHTML = `
<table class="SWSE_settings">
<tr>
<td class="name">${text.settingsLanguage}</td>
<td class="value"><select class="language"></select></td>
</tr>
<tr>
<td class="name">${text.settingsCount}</td>
<td class="value"><input type="number" class="count" value="1" min="1"></select></td>
</tr>
<tr>
<td class="name">${text.settingsEmail}</td>
<td class="value"><input type="text" class="email"></td>
</tr>
</table>
`;
const panelHandler = panel => {
// apply settings
const $panel = $(panel).find('.SWSE_settings');
// language
const $language = $panel.find('.language');
Object.keys(i18n).forEach(language => {
$language.append(new Option(i18n[language].name, language));
});
$panel.find(`option[value=${config.get('language')}]`).prop('selected', true);
$language.change(settingsHandler.bind(null, () => {
const newLanguage = $language.val();
config.set('language', newLanguage);
text = has.call(i18n, newLanguage) ? i18n[newLanguage] : i18n.english;
}));
// count, email
['count', 'email'].forEach(field => {
const $field = $panel.find(`.${field}`);
const val = config.get(field);
if (val) $field.val(val);
$field.change(settingsHandler.bind(null, () => {
config.set(field, $field.val().trim());
}));
});
};
swal({
title: text.settingsTitle,
html: panelHTML,
onBeforeOpen: panelHandler
});
};
const matchSteamUrl = (str = '') => {
const input = str.trim();
let output = null;
if (input.length > 0) {
const found = input.match(regURL);
if (found) {
output = {
type: found[3].slice(0, 3),
id: parseInt(found[4], 10),
index: found.index,
matched: found[0]
};
}
}
return output;
};
const check = (d, s, c) => {
let source = d;
let selector = s;
let callback = c;
if (typeof d === 'string') {
// dom source omitted
source = document;
selector = d;
callback = s;
}
$(source).find(selector.split(',').map(x => `${x}:not(.SWSE_checked)`).join()).each((index, element) => {
const $ele = $(element);
let attr = null;
for (let i = 0; i < element.attributes.length; i += 1) {
if (!attr) attr = matchSteamUrl(element.attributes[i].value);
}
if (attr && owned[attr.type].includes(attr.id)) callback($ele, 'SWSE_owned');
$ele.addClass('SWSE_checked');
});
};
const syncLibrary = (notify = false) => {
GM_xmlhttpRequest({
method: 'GET',
url: `http://store.steampowered.com/dynamicstore/userdata/t=${Math.random()}`,
onload: res => {
if (res.status === 200) {
const data = JSON.parse(res.response);
if (data.rgOwnedApps.length > 0) owned.app = data.rgOwnedApps;
if (data.rgOwnedPackages.length > 0) owned.sub = data.rgOwnedPackages;
owned.lastSync = Date.now();
localStorage.setItem('SWSE_owned', JSON.stringify(owned));
if (notify) {
swal({
title: text.syncSuccessTitle,
text: text.syncSuccess,
type: 'success',
timer: 3000
});
}
}
}
});
};
const headerMenu = () => {
// insert header menu
GM_addStyle(`
header.SWSE_header {
display: flex !important;
position: sticky;
top: 0;
padding: 0 7%;
background: #2d3f51;
box-shadow: 0 2px 5px rgba(68, 68, 68, 0.3);
transition: all 0.2s ease;
z-index: 9999;
}
.SWSE_nav ul { margin: 0; padding: 0; list-style: none; }
.SWSE_nav li { float: left; }
.SWSE_nav a {
color: #f5f5f5;
text-decoration: none;
display: block;
padding: 1.5em;
font-size: initial;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 1px;
transition: all 0.2s ease;
}
.SWSE_nav a:hover { color: #16A085; }
`);
const $nav = $('<nav class="SWSE_nav"><ul></ul></nav>');
$nav.prependTo('body').wrap('<header class="SWSE_header"></header>');
$nav.find('ul').append(
// hide owned button
$(`<li><a>${text.menuHideOwned}</a></li>`).click(() => {
$('.SWSE_owned').toggleClass('SWSE_hide');
}),
// sync library button
$(`<li><a>${text.menuSyncLibrary}</a></li>`).click(syncLibrary.bind(null, true)),
// settings button
$(`<li><a>${text.settingsTitle}</a></li>`).click(settings));
};
const handler = () => {
// order page, auto download keys
if (location.pathname.startsWith('/order/get/')) {
const url = $('table a').attr('href');
if (url.length > 0) {
fetch(url, {
method: 'GET',
credentials: 'include'
}).then(res => res.text()).then(t => {
const keys = t.match(regKey);
$('table tr:last-child').before(`<tr><td>${text.keyField}</td><td>${keys.join(eol)}</td></tr>`);
});
}
// product page
} else {
// pre-fill inputs
$('input[name=count]').val(config.get('count'));
$('input[name=email]').val(config.get('email'));
// insert pay button
const $payButton = $(`<span class="SWSE_payButton">${text.payButtonText}</span>`).click(() => {
const data = {
wm_wmk: true,
purse: $('#purse > span, #copyfund > b').text(),
amount: parseFloat($('#price > span, .payprice').text()),
desc: $('#message > span, #copybill > b').text()
};
window.open(`https://mini.wmtransfer.com/SendWebMoney.aspx?${JSON.stringify(data)}`, '', 'height=800,width=1000');
});
switch (location.hostname) {
case 'steamkeyswhosales.com':
case 'alfakeys.ru':
$payButton.addClass('btn').css({
'margin-right': '10px',
cursor: 'pointer',
color: '#FFF',
'background-color': '#337ab7',
'border-color': '#2e6da4'
}).insertBefore('#check_pay');
break;
case 'cheap-steam-games.ru':
case 'lastkey.ru':
case 'keys.farm':
case 'steamkeys-shop.ru':
case 'maxfarmshop.ru':
case 'bestkeystore.ru':
case 'bestfarmkey.lequestore.ru':
case 'steamfarmkey.ru':
case 'indiegamekeys.lequestore.ru':
case 'randomkey.ru':
$payButton.addClass('btn-leque btn-leque-primary btn-leque-xs').css({
float: 'right',
'margin-top': '5px'
}).insertAfter('.btn-leque-xs');
break;
case 'steam1.lequeshop.ru':
case 'steam1.ru':
case 'steam-tab.ru':
case 'steamd.lequeshop.ru':
case 'steamkeystore.ru':
case 'steamrandomkeys.ru':
case 'keyssell.ru':
$payButton.addClass('btn btn-primary').css('margin-top', '10px').insertBefore('.checkpayButton');
break;
case 'dmshop.lequeshop.ru':
case 'kartonanet.lequeshop.ru':
case 'rig4all.lequeshop.ru':
case 'steamkey.lequeshop.ru':
case 'farmacc.ru':
case 'drunkpatrick.store':
case 'steamfarm.lequestore.ru':
case 'animekeys.ru':
case 'tkfg.ru':
case '200plus.lequeshop.ru':
$payButton.addClass('btn btn-primary').insertBefore('.checkpayButton, .checkpaybtn');
break;
default:
}
// check owned
switch (location.hostname) {
case 'steamkeys.ovh':
check('a[href*="steampowered"]', ($ele, classes) => {
$ele.closest('tr').addClass(classes);
});
break;
case 'steamkeyswhosales.com':
case 'alfakeys.ru':
case 'ign.akens.ru':
case 'bestkey.akens.ru':
case 'goldkeys.akens.ru':
case 'domenkeys.akens.ru':
case 'cada.akens.ru':
check('div[title*="steam"]', ($ele, classes) => {
$ele.closest('tr').addClass(classes);
});
break;
case 'keymarket.pw':
check('div[style*="steam/apps/"]', ($ele, classes) => {
$ele.nextAll().addClass(classes);
$ele.parent().parent().addClass(classes);
});
break;
case 'steamkey.lequeshop.ru':
check('img[src*="steam/apps/"]', ($ele, classes) => {
$ele.next().addClass(classes);
$ele.parent().addClass(classes);
});
break;
case 'cheap-steam-games.ru':
check('img[src*="steam/apps/"]', ($ele, classes) => {
$ele.closest('.hero-feature').addClass(classes);
$ele.parent().next().addClass(classes);
});
break;
case 'cheapkey.lequeshop.ru':
check('div[style*="steam/apps/"]', ($ele, classes) => {
$ele.parent().addClass(classes);
});
break;
case 'steamfarmkey.ru':
case 'kartonanet.lequeshop.ru':
case 'lastkey.ru':
case 'rig4all.lequeshop.ru':
case 'steamkeys-shop.ru':
case 'farmacc.ru':
case 'keys.lequestore.ru':
case 'steamfarm.lequestore.ru':
case 'proxzy.lequestore.ru':
case 'maxfarmshop.ru':
case 'bestkeystore.ru':
case 'keys.farm':
case 'alonekey.net':
case 'bestfarmkey.lequestore.ru':
case 'm-b-shop.leque.shop':
case 'indiegamekeys.lequestore.ru':
case '200plus.lequeshop.ru':
check('img[src*="steam"]', ($ele, classes) => {
$ele.closest('tr').addClass(classes);
});
break;
case 'keyssell.ru':
case 'steam-tab.ru':
case 'steamd.lequeshop.ru':
case 'steam1.ru':
case 'steamkeystore.ru':
case 'steam1.lequeshop.ru':
case 'steamrandomkeys.ru':
check('a > img[src*="steam/apps/"]', ($ele, classes) => {
$ele.closest('.item-loop').addClass(classes);
$ele.closest('div').prev().addClass(classes);
});
check('.item-poster > img[src*="steam/apps/"]', ($ele, classes) => {
$ele.parent().addClass(classes);
$ele.prev().prev().addClass(classes);
});
break;
case 'reronage.akens.ru':
check('.good-title > div', ($ele, classes) => {
$ele.closest('tr').addClass(classes);
});
break;
case 'animekeys.ru':
check('img[src*="steam"]', ($ele, classes) => {
$ele.closest('div.list-item').addClass(classes);
});
break;
case 'tkfg.ru':
case 'randomkey.ru':
$('#header').css('position', 'initial');
check('a > img[src*="steam"]', ($ele, classes) => {
$ele.parent().prev('.title').find('p').addClass(classes);
$ele.closest('.b-poster').addClass(classes);
});
break;
case 'steamground.com':
GM_xmlhttpRequest({
method: 'GET',
url: 'https://www.steamgifts.com/discussion/iy081/steamground-wholesale-build-a-bundle',
onload: res => {
if (res.status !== 200) return;
const games = $('<div/>', {
html: res.response
}).find('.comment__description:first table a[href*="steampowered"]');
const titles = $('.wholesale-card_title');
const hrefs = {};
const process = t => {
let tx = t.trim();
if (tx === 'Ball of Light (Journey)') tx = 'Ball of Light';
if (tx === 'Shake Your Money Simulator') tx = 'Shake Your Money Simulator 2016';
return tx.toLowerCase().replace(/[\W]/g, '');
};
games.each((index, element) => {
hrefs[process(element.textContent)] = element.href;
});
titles.each((index, element) => {
const $ele = $(element);
const href = hrefs[process($ele.text())] || '';
if (href.length > 0) $ele.parent().attr('href', href);
});
check('.wholesale-card > a[href*="steampowered"]', (element, classes) => {
$(element).parent().addClass(classes);
$(element).children().eq(1).css('color', 'black');
});
}
});
break;
default:
}
}
};
const init = () => {
// on WebMoney payment page
if (location.hostname === 'mini.wmtransfer.com') {
if (location.href.includes('purses-view-history') && sessionStorage.getItem('wm_wmk')) window.close();
if (location.pathnaame === '/SendWebMoney.aspx' && location.search.length > 0) {
try {
const search = location.href.split('?').pop();
const data = JSON.parse(decodeURIComponent(search));
if (data.wm_wmk) {
$('#ctl00_cph_tbEmailOrPurseNumber').val(data.purse);
$('#ctl00_cph_tbAmount').val(data.amount);
$('#ctl00_cph_tbDesc').val(data.desc);
sessionStorage.setItem('wm_wmk', 1);
}
} catch (err) {
throw err;
}
}
// wholesale sites
} else {
config.init();
headerMenu();
$(window).on('load', handler);
// sync owned every 10 min
const syncTimer = 10 * 60 * 1000;
if (!owned.lastSync || owned.lastSync < Date.now() - syncTimer) syncLibrary();
}
};
$(init);