Steam Wholesale Sites Extension

try to take over the world!

目前為 2017-12-02 提交的版本,檢視 最新版本

// ==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);

QingJ © 2025

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