Enhanced Barter

This userscript aims to enhance your experience at barter.vg

当前为 2019-06-22 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Enhanced Barter
// @icon         https://bartervg.com/imgs/ico/barter/favicon-32x32.png
// @namespace    Revadike
// @author       Revadike
// @version      1.2.1
// @description  This userscript aims to enhance your experience at barter.vg
// @match        https://barter.vg/*
// @match        https://wwww.barter.vg/*
// @connect      revadike.ga
// @connect      steamcommunity.com
// @connect      steamtrades.com
// @connect      store.steampowered.com
// @grant        GM_addStyle
// @grant        GM_getResourceText
// @grant        GM_info
// @grant        GM_setClipboard
// @grant        GM_xmlhttpRequest
// @grant        unsafeWindow
// @run-at       document-start
// @require      https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js
// @require      https://cdnjs.cloudflare.com/ajax/libs/fuse.js/3.4.4/fuse.min.js
// @require      https://cdnjs.cloudflare.com/ajax/libs/history.js/1.8/bundled-uncompressed/html4+html5/jquery.history.js
// @require      https://cdnjs.cloudflare.com/ajax/libs/noUiSlider/13.1.5/nouislider.min.js
// @resource     noUiSliderCss https://cdnjs.cloudflare.com/ajax/libs/noUiSlider/13.1.5/nouislider.min.css
// @homepageURL  https://github.com/Revadike/EnhancedBarter/
// @supportURL   https://github.com/Revadike/EnhancedBarter/issues
// ==/UserScript==

// ==Code==
"use strict";
const requests = [];
const requestRateLimit = 200;
const localStorage = unsafeWindow.localStorage;
let myuid, mysid, streps, usergroups, itemnames;

function init() {
    setTimeout(initBarter, 0);
}

function initBarter() {
    setInterval(handleRequests, requestRateLimit);

    $.fn.serializeObject = serializeObject;
    // $.ajaxSetup({
    //     "beforeSend": (xhr, options) => {
    //         showSpinner(options.url);

    //         xhr.url = options.url;
    //         const request = () => {
    //             options.beforeSend = (x, o) => x.url = o.url;
    //             $.ajax(options);
    //         };

    //         requests.push(request);
    //     },
    //     "complete": (xhr) => hideSpinner(xhr.url)
    // });
    $.post = jqueryPost;

    unsafeWindow.$ = $;
    unsafeWindow.syncLibrary = syncLibrary;
    $(document).ready(barterReady);
}

function barterReady() {
    console.log(`[Enhanced Barter] Enhancing`);

    // Every barter page
    setTimeout(ajaxify, 0);
    GM_addStyle(GM_getResourceText(`noUiSliderCss`));
    GM_addStyle(stylesheet);

    $(`li.bottomline`).after(`<li class="bottomline" title="${GM_info.script.name} (${GM_info.script.version}) by ${GM_info.script.author}">
        <a target="_blank" href="https://github.com/Revadike/EnhancedBarter/">
            <span>&#129302;&#xFE0E;</span>${GM_info.script.name}
        </a>
    </li>`);

    $(`.sortBy`).after(`<div id="filtercontainer">
        <span class="activityIcon normal gray">¶</span> Filter by 
        <input id="filterBy" type="text" placeholder="Search in displayed rows..." style="width: 530px;">
    </div>`);
    $(`#filtercontainer`).attr(`class`, $(`.sortBy`).attr(`class`));
    $(`#filterBy`).on(`change keyup paste`, filterRows);

    streps = JSON.parse(localStorage.stReps || `{}`);
    $(`a:has(>[alt="Steam Trades icon"])`).get().forEach(addSteamTradesRep);

    if ($(`#q`).length > 0) {
        itemnames = JSON.parse(localStorage.itemnames || `{}`);
        addAutoComplete();
    }

    usergroups = JSON.parse(localStorage.usergroups || `{}`);

    // The match page and user profile page
    $(`#tradeUser [label=Groups] option, [name=group] option`).get().forEach((elem) => {
        const id = Math.abs(elem.value);
        const offset = 64; // Char code offset
        const name = usergroups[id >= offset ? id - offset : id];
        if (!name) {
            return;
        }

        elem.innerText = `${elem.innerText} : ${name}`;
    });

    // The match page
    const cats = [`trade`, `wish`];
    const sortings = {};
    cats.forEach((cat) => {
        sortings[cat] = {};
        sortings[cat][`az`] = $(`select#${cat}Game option`).get();

        const initial = sortings[cat].az.shift();
        sortings[cat][`90`] = [...sortings[cat].az].sort((a, b) => parseInt(b.innerText.split(` (`).pop().replace(`)`, ``)) - parseInt(a.innerText.split(` (`).pop().replace(`)`, ``)));

        $(`select#${cat}Game`).after(`
            <div id="${cat}Sort" style="display: inline;">
                <label> Sorted by </label>
                <a data-sort="az" data-selected="1" style="text-decoration: none; font-weight: bold; color: inherit;">A-Z</a>
                <label>/</label>
                <a data-sort="90" data-selected="0" style="cursor: pointer;">9-0</a>
            </div>
        `);

        $(`#${cat}Sort [data-sort]`).click((event) => {
            $(`#${cat}Sort [data-selected="1"]`).attr(`data-selected`, 0).attr(`style`, `cursor: pointer;`);
            $(event.target).attr(`data-selected`, 1).attr(`style`, `text-decoration: none; font-weight: bold; color: inherit;`);

            const sortby = $(event.target).data(`sort`);
            console.log(sortings[cat][sortby][0]);
            $(`select#${cat}Game`).html([initial, ...sortings[cat][sortby]]).val(0);
        });
    });

    // Any bundle page
    if ($(`[accesskey="b"]`).parent().hasClass(`nav-sel`)) {
        $(`.warnings`).append(`<li style="float: right">
            <input id="togglebtn" onsubmit="void(0)" value="Hide/Show" type="button" class="addTo offer bold pborder">
        </li>
        <li style="float: right">
            <select id="toggleselect" title="Select which items you want to hide or show" class="pborderHover">
                <option value="[type=checkbox]:checked">Selected</option>
                <option value=".blist">Blacklist</option>
                <option value=".trad">Tradables</option>
                <option value=".wish">Wishlist</option>
                <option value=".libr">Library</option>
            </select>
        </li>`);

        $(`#togglebtn`).click(toggleBundleItems);
    }

    // The creating offer page
    $(`[name=remove_offer_items]`).val(`- Remove Selected`).css(`width`, `23.8%`).css(`margin-left`, `-4px`).removeClass(`extraLeft`).after(`<input type="button" value="◧ Invert Selection" style="width: 23.8%; margin-left: 0.5%" class="checkall addTo bborder" onsubmit="void(0)"><input type="button" value="&#128275;&#xFE0E; Enable Locked" style="width: 23.8%; margin-left: 0.5%" class="enableall addTo oborder" onsubmit="void(0)">`);
    $(`[name=add_to_offer]`).val(`+ Add Selected`).css(`width`, `23.8%`).css(`margin-right`, `0.5%`);

    $(`.checkall`).click(checkAllTradables);
    $(`.enableall`).click(enableAllTradables);

    // Every next barter page will have the sign out link
    if ($(`#signin`).length === 0 || $(`abbr+ a:has(.icon)`).length === 0) {
        return;
    }

    myuid = $(`#signin`).attr(`href`).split(`/`)[4];
    mysid = $(`abbr+ a:has(.icon)`).attr(`href`).split(`/`)[4];

    // Every next barter page will be on /u/myuid/
    if (!$(`h1 > a`).is(`[href*="/u/${myuid}"]`)) {
        return;
    }

    // The library page
    if ($(`[accesskey="l"]`).parent().hasClass(`nav-sel`) ) {
        $(`[name="sync_list"]`).after(`<input type="button" title="Sync ALL owned games and DLC" value="↻ Comprehensive Sync with Steam" style="margin-left: 0.5%" id="libsync" class="addTo gborder" onsubmit="void(0)">`);
        $(`#libsync`).click(clickLibSyncBtn);
    }

    // The settings page
    if ($(`.preferences`).length > 0) {
        $(`#offer`).before(`<div id="groups">
            <h3 class="listName sborder">&#x1F46A;&#xFE0E; User Group Names</h3>
            <p>
                <select name="groupid">
                    <option value="1">A</option>
                    <option value="2">B</option>
                    <option value="3">C</option>
                    <option value="4">D</option>
                    <option value="5">E</option>
                    <option value="6">F</option>
                    <option value="7">G</option>
                    <option value="8">H</option>
                    <option value="9">I</option>
                    <option value="10">J</option>
                    <option value="11">K</option>
                    <option value="12">L</option>
                    <option value="13">M</option>
                    <option value="14">N</option>
                    <option value="15">O</option>
                    <option value="16">P</option>
                    <option value="17">Q</option>
                    <option value="18">R</option>
                    <option value="19">S</option>
                    <option value="20">T</option>
                    <option value="21">U</option>
                    <option value="22">V</option>
                    <option value="23">W</option>
                    <option value="24">X</option>
                    <option value="25">Y</option>
                    <option value="26">Z</option>
                </select>
                <input type="text" name="groupname" placeholder="E.g. Unowned collectors">
                <input type="button" value="Save" id="savegroup" class="addTo dborder">
            </p>
        </div>`);

        $(`#savegroup`).click((event) => {
            event.preventDefault();
            const id = $(`[name=groupid]`).val();
            const name = $(`[name=groupname]`).val();
            usergroups[id] = name;
            localStorage.usergroups = JSON.stringify(usergroups);
        });

        $(`[name=groupid]`).change((event) => {
            const id = event.target.value;
            $(`[name=groupname]`).val(usergroups[id]);
        });

        $(`[name=groupid]`).trigger(`change`);
    }

    // The offer overview page
    if ($(`li:has([accesskey="o"])`).is(`.nav-sel`) && $(`.activity`).length > 0) {
        $(`[name="offer_setup"]+`).after(`<input id="automatedoffer" onsubmit="void(0)" value="Begin Automated Offer" class="addTo offer bold pborder" type="button" style="float: right;">`);
        $(`#automatedoffer`).parents(`form`).css(`width`, `100%`);
        $(`#automatedoffer`).click(setupAutomatedOffer);
        $(`.showMoreArea`).last().after(`<p>
            <label for="offersearch">
                <strong>Filter: </strong>
            </label>
            <input class="addTo pborder" id="offersearch" type="text" placeholder="Search in displayed offers..." style="width: 210px;">
            <input class="addTo pborder" style="float: right; margin-left: 4px;" id="canceloffers" type="button" onsubmit="void(0)" value="Cancel Offers">
            <input class="addTo pborder" style="float: right; margin-left: 4px;" id="messageoffers" type="button" onsubmit="void(0)" value="Message Offers">
            <input class="addTo pborder" style="float: right; margin-left: 4px;" id="extendoffers" type="button" onsubmit="void(0)" value="Change Expiration">
        </p>`);

        $(`#canceloffers`).click(cancelOffers);
        $(`#messageoffers`).click(messageOffers);
        $(`#extendoffers`).click(extendExpiry);
        $(`#offersearch`).on(`change keyup paste`, searchOffers);
    }

    // The accepted offer page
    $(`input[value='Completed Offer']`).click(finalizeOffer);
}

function clickLibSyncBtn(event) {
    event.preventDefault();
    showSpinner(`libsync`);
    $(event.target).prop(`disabled`, true).val(`↻ Syncing...`);
    syncLibrary(() => {
        hideSpinner(`libsync`);
        $(event.target).prop(`disabled`, false).val(`✔ Done`);
    });
}

function showSpinner(instanceid) {
    if ($(`.spinner`).length === 0) { // to avoid multiple spinners
        $(`body`).prepend(`<div class="spinner"></div>`);
    }

    $(`.spinner`).attr(`data-instanceid`, instanceid);
}

function hideSpinner(instanceid) {
    $(`.spinner[data-instanceid='${instanceid}']`).remove();
}

function enableAllTradables(event) {
    event.preventDefault();

    if (!confirm(`Are you sure you want to enable disabled tradables? Make sure the other party is okay with this!`)) {
        return;
    }

    $(event.target).parents(`.tradables`).find(`.collectionSelect input[disabled]`).get().forEach((elem) => {
        elem.removeAttribute(`disabled`);
        elem.removeAttribute(`title`);
        elem.name = `add_to_offer_${$(`.enableall:first`).is(event.target) ? `1` : `2`}[]`;
        elem.id = $(elem).find(`+`).attr(`for`);
        elem.value = `${$(elem).parents(`tr`).data(`item-id`)},${$(elem).next().attr(`for`).replace(`edit`, ``)}`;
    });

    $(event.target).remove();
}

function checkAllTradables(event) {
    event.preventDefault();
    $(event.target).parents(`.tradables`).find(`.collectionSelect input[type=checkbox]`).get().forEach((elem) => elem.checked = !elem.checked);
}

function toggleBundleItems(event) {
    event.preventDefault();
    $(`.collection tbody > tr:has(${$(`#toggleselect`).val()}):not(:has(.bargraphs))`).toggle();
    $(`.collection tbody > tr:visible [type=checkbox]`).prop(`disabled`, false);
    $(`.collection tbody > tr:hidden [type=checkbox]`).prop(`disabled`, true);
}

function createRep(rep) {
    if (rep && isFinite(rep.m) && isFinite(rep.p)) {
        return `<span class="strep" title="Steam Trades score">( <span class="plus">+${rep.p}</span>, <span class="minus${rep.m !== 0 ? ` hasMinus` : ``}">${rep.m}</span> )</span>`;
    }

    return $(`<span class="strep notfound">( none )</span>`);
}

function addSteamTradesRep(elem) {
    const steamid = elem.href.split(`/`)[4];

    if (streps[steamid] && streps[steamid].t && Date.now() - streps[steamid].t <= 48 * 60 * 60 * 1000) {
        $(elem).after(createRep(streps[steamid]));
        return;
    }

    request({
        "method": `GET`,
        "url": elem.href,
        "onload": (response) => {
            streps[steamid] = {};
            const plus = parseInt($(`.increment_positive_review_count`, response.responseText).first().text().replace(`,`, ``));
            const minus = parseInt($(`.increment_negative_review_count`, response.responseText).first().text().replace(`,`, ``));

            if (!isNaN(plus) && !isNaN(minus)) {
                streps[steamid].p = plus;
                streps[steamid].m = minus;
            }

            streps[steamid].t = Date.now();
            localStorage.stReps = JSON.stringify(streps);
            $(elem).after(createRep(streps[steamid]));
        }
    });
}

function addAutoComplete() {
    if (Date.now() - itemnames.lastupdated <= 24 * 60 * 60 * 1000) {
        const data = Object.entries(itemnames.data).map((e) => ({ "id": e[0], "title": e[1] }));
        // eslint-disable-next-line no-undef
        const fuse = new Fuse(data, {
            "shouldSort": true,
            "threshold": 0.3,
            "maxPatternLength": 32,
            "minMatchCharLength": 2,
            "keys": [`id`, `title`]
        });

        let delay;
        $(`#q`).parent().append(`<div id="acbox" class="autocomplete"></div>`);
        $(`#q`).on(`change keyup paste`, (event) => {
            clearTimeout(delay);
            showSpinner(`autocomplete`);
            delay = setTimeout(() => {
                const val = event.target.value.trim();
                $(`#acbox`).html(``);
                hideSpinner(`autocomplete`);

                if (val.length <= 2) {
                    return;
                }

                const results = fuse.search(val);
                for (let i = 0; i < 10; i++) {
                    const item = results.shift();
                    if (item) {
                        $(`#acbox`).append(`<p style="margin: 0.5em;"><a href="/i/${item.id}/">${item.title}</a></p>`);
                    }
                }

                if (results.length === 0) {
                    return;
                }

                $(`#acbox`).append(`<strong style="margin: 0.5em;"><a href="/search?q=${encodeURIComponent(val)}">And ${results.length} more...</a></strong>`);
            }, 500);
        }).blur(() => setTimeout(() => $(`#acbox`).hide(), 200)).focus(() => $(`#acbox`).show());
        return;
    }

    request({
        "method": `GET`,
        "url": `https://barter.vg/browse/json/`,
        "onload": (response) => {
            let json;
            try {
                json = JSON.parse(response.responseText);
            } catch (e) {
                return;
            }

            itemnames.data = {};
            for (const id in json) {
                itemnames.data[id] = json[id].title.trim();
            }

            itemnames.lastupdated = Date.now();
            localStorage.itemnames = JSON.stringify(itemnames);
            addAutoComplete();
        }
    });
}

function handleRequests() {
    if (requests.length === 0) {
        return;
    }

    if (requests.length === 1) {
        setTimeout(() => console.log(`All ajax requests are served!`), requestRateLimit);
    }

    console.log(`${requests.length} Ajax requests remaining...\nThis will roughly take ${requests.length * requestRateLimit / 1000} seconds to complete.`);
    const req = requests.shift();
    if (typeof request === `function`) {
        req();
    }
}

function finalizeOffer(event) {
    event.preventDefault();

    const main = $(event.target).parents(`#main`);
    const steamid = $(`#offerHeader [alt="Steam icon"]`, main).get().map((elem) => elem.title).find((id) => id !== mysid);
    const name = $(`#offerHeader tr:has([title="${steamid}"]) > td:nth-child(2) > strong > a`, main).text();
    const form = $(event.target).parents(`form`);
    const url = form.attr(`action`).split(`#`)[0];
    const msg = `+REP ${name} is an amazing trader, recommended! We successfully completed this trade: ${url}. Thanks a lot for the trade!`;

    $(event.target).val(`Completing trade, sending feedback and syncing library...`).prop(`disabled`, true);
    showSpinner(`feedback`);

    setPostTradeClipboard(url);
    $.post(url, form.serializeObject(), () => postSteamTradesComment(msg, steamid, () => syncLibrary(() => location.href = url)));
}

async function syncLibrary(callback) {
    if (callback) {
        await syncLibrary().catch(callback);
        callback();
        return;
    }

    return new Promise(async(res, rej) => {
        request({
            "url": `https://barter.vg/u/${myuid}/l/`,
            "method": `POST`,
            "headers": {
                "Content-Type": `application/x-www-form-urlencoded`
            },
            "data": $.param({
                "sync_list": `↻ Sync List`,
                "type": 1
            }),
            "onload": () => true
        });

        const response = await request({
            "method": `GET`,
            "url": `http://store.steampowered.com/dynamicstore/userdata/?t=${Date.now()}`
        }).catch(rej);

        const json = JSON.parse(response.responseText);
        const ownedApps = json.rgOwnedApps;
        const ownedPackages = json.rgOwnedPackages;
        await request({
            "url": `https://barter.vg/u/${myuid}/l/e/`,
            "method": `POST`,
            "headers": {
                "Content-Type": `application/x-www-form-urlencoded`
            },
            "data": $.param({
                "bulk_IDs": `app/${ownedApps.join(`,app/`)},sub/${ownedPackages.join(`,sub/`)}`,
                "add_IDs": `+ Add IDs`,
                "action": `Edit`,
                "change_attempted": 1,
                "add_from": `IDs`
            })
        }).catch(rej);

        res();
    });
}

async function postSteamTradesComment(msg, steamid, callback) {
    const empty = () => {};
    callback = callback || empty;

    const response = await request({
        "method": `GET`,
        "url": `https://www.steamtrades.com/user/${steamid}`,
        "headers": {
            "Accept": `text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3`,
            "Accept-Encoding": `gzip, deflate, br`,
            "Accept-Language": `en`
        }
    }).catch(callback);

    const profile_id = $(`[name=profile_id]`, response.responseText).val();
    const xsrf_token = $(`[name=xsrf_token]`, response.responseText).val();
    if (!profile_id || !xsrf_token) {
        callback();
        return;
    }

    const data = {
        "do": `review_insert`,
        "xsrf_token": xsrf_token,
        "profile_id": profile_id,
        "rating": 1,
        "description": msg
    };

    await request({
        "method": `POST`,
        "url": `https://www.steamtrades.com/ajax.php`,
        "data": $.param(data),
        "headers": {
            "Content-Type": `application/x-www-form-urlencoded; charset=UTF-8`,
            "Accept": `application/json, text/javascript, */*; q=0.01`,
            "Accept-Encoding": `gzip, deflate, br`,
            "Accept-Language": `en`
        }
    }).catch(callback);

    callback();
}

function setPostTradeClipboard(url) {
    const sgprofile = `https://www.steamtrades.com/user/${mysid}`;
    const msg = `Thanks for the trade!\nI completed the trade on barter.vg and left feedback on your steamtrades profile.\nIf you haven't done so already, could you please do the same?\n${url}\n${sgprofile}`;
    GM_setClipboard(msg);
}

function setupAutomatedOffer() {
    $.post(`/u/${myuid}/o/`, {
        "to_user_id": -2,
        "offer_setup": 1
    }, (data) => {
        $.post($(`[rel=canonical]`, `<div>${data}</div>`).attr(`href`), {
            "offer_setup": 3,
            "cancel_offer": `☒ Cancel Offer`
        });

        parseHtml(data);
        $(`#main h2`).html(`&#x1F916;&#xFE0E;✉ Automated Offers`);
        $(`#offerHeaderTo`).html(`To <strong>Qualified Users</strong> (select options below)`);
        $(`#offerHeaderTo`).parent().next().remove();
        $(`#offerHeaderTo`).parent().before(`<tr>
            <td class="gray">
                <span>Via</span>
            </td>
            <td colspan="6" class="gray">
                <a href="https://steamcommunity.com/groups/bartervg/discussions/6/1470841715930902039/">
                    <span title="version ${GM_info.script.version}">${GM_info.script.name}</span>
                </a>
                <a href="https://github.com/Royalgamer06/EnhancedBarter/">
                    <img src="https://bartervg.com/imgs/ico/github.png" alt="GitHub icon" title="GitHub" class="icon">
                </a>
                <span>(not associated with Barter.vg)</span>
            </td>
        </tr>`);
        $(`#offer`).replaceWith(`<form id=offer>${$(`#offer`).html()}</form>`);
        $(`[name=cancel_offer]`).replaceWith(`<input class="addTo failed" value="🗑 Cancel and Discard Offer" type="button" onclick="location.reload()">`);
        $(`.tradables:nth-child(3) legend`).html(`${$(`.tradables:nth-child(3) legend`).html().replace(`<strong class="midsized offer">1</strong> of `, `<input min="1" id="from_ratio" name="from_ratio" type="number" value="1" style="width: 40px;"> of `)}...`);

        $(`form[action*=comments]`).remove();
        $(`.noborder:nth-child(3)`).nextAll().remove();
        $(`.collectionSelect`).nextAll().remove();
        $(`#exchanges`).nextAll().remove();
        $(`#offer`).nextAll().remove();

        $(`.tradables`).after(`
        <ul>
        ...to users who...
            <li> <input type="checkbox" name="offering_to" id="towishlist" value="wishlist" checked="true"> <label for="towishlist">...have them in their wishlist...</label></li>
            <li> <input type="checkbox" name="offering_to" id="tounowned" value="unowned"> <label for="tounowned">...do <strong>not</strong> have them in their library...</label></li>
            <!-- <li> <input type="checkbox" name="offering_to" id="tolibrary" value="library"> <label for="tolibrary">...have them in their library...</label></li> -->
            <li> <input type="checkbox" name="offering_to" id="totradable" value="tradable"> <label for="totradable">...have them for trade...</label></li>
        </ul>
        <div style="margin: 2em 0; border-top: 1px solid rgb(153, 17, 187); border-bottom: 1px solid rgb(153, 17, 187);" class="offerDivide">
            <strong class="midsized">⇅</strong> ...in exchange for...
        </div>
        <p>
            ... <input min="1" id="to_ratio" name="to_ratio" type="number" value="1" style="width: 40px;"> of their tradables of type(s)...
        </p>
        <p>
            <input type="checkbox" name="type" id="game" value="game" checked="true"> <label for="game">Game</label>
            <input type="checkbox" name="type" id="dlc" value="dlc"> <label for="dlc">DLC</label>
            <input type="checkbox" name="type" id="application" value="application"> <label for="application">Application</label>
            <input type="checkbox" name="type" id="tool" value="tool"> <label for="tool">Tool</label>
            <input type="checkbox" name="type" id="demo" value="demo"> <label for="demo">Demo</label>
            <input type="checkbox" name="type" id="mod" value="mod"> <label for="mod">Mod</label>
            <input type="checkbox" name="type" id="media" value="media"> <label for="media">Media</label>
            <input type="checkbox" name="type" id="movie" value="movie"> <label for="movie">Movie</label>
            <input type="checkbox" name="type" id="video" value="video"> <label for="video">Video</label>
            <input type="checkbox" name="type" id="episode" value="episode"> <label for="episode">Episode</label>
            <input type="checkbox" name="type" id="series" value="series"> <label for="series">Series</label>
            <input type="checkbox" name="type" id="guide" value="guide"> <label for="guide">Guide</label>
            <input type="checkbox" name="type" id="config" value="config"> <label for="config">Config</label>
            <input type="checkbox" name="type" id="storeonly" value="storeonly"> <label for="storeonly">Storefront Only</label>
            <input type="checkbox" name="type" id="advertising" value="advertising"> <label for="advertising">Advertising</label>
            <input type="checkbox" name="type" id="hardware" value="hardware"> <label for="hardware">Hardware</label>
            <input type="checkbox" name="type" id="unknown" value="unknown"> <label for="unknown">Unknown</label>
            <input type="checkbox" name="type" id="unset" value="unset"> <label for="unset">Unset</label>
        </p>
        <p>
            ...meeting these requirements...
        </p>
        <fieldset>
            <small>TIP: Hover over the (?) with your mouse cursor for a better description and explanation of the requirement.</small>
        </fieldset>
        <fieldset style="border-top: 1px solid rgb(153, 17, 187);">
            <div id="limit" data-max="10000" class="offerSlider"></div>
            Offer count (<a style="cursor: help; text-decoration: none;" title="The number range of offers you want to send. \nIf you only have a limited stock of your selected tradable(s), I'd recommend limiting your offers to avoid getting more accepted offers than you can fulfill. \nNote that barter has a daily limit for offers. Please complain to barter.vg about this.">?</a>)
        </fieldset>
        <!-- <fieldset>
            <div id="DLC" data-max="1" data-suffix="%" class="offerSlider"></div>
            DLC (<a style="cursor: help; text-decoration: none;" title="Include DLC? \nThere are 3 options: 0%-0% means no DLC, 0%-100% means allow DLC (don't care) and 100%-100% means DLC only.">?</a>)
        </fieldset> -->
        <fieldset>
            <div id="limited" data-max="1" data-suffix="%" class="offerSlider"></div>
            Steam Limited (<a style="cursor: help; text-decoration: none;" title="Include Steam Limited? \nSteam Limited: no +1 for your library, no achievement showcase. \nThere are 3 options: 0%-0% means no Steam Limited, 0%-100% means allow Steam Limited (don't care) and 100%-100% means Steam Limited only.">?</a>)
        </fieldset>
        <fieldset>
            <div id="givenaway" data-max="1" data-suffix="%" class="offerSlider"></div>
            Given away (<a style="cursor: help; text-decoration: none;" title="Include games that are free or have been given away before?\nThere are 3 options: 0%-0% means no given away, 0%-100% means allow given away (don't care) and 100%-100% means given away only.">?</a>)
        </fieldset>
        <fieldset>
            <div id="bundles" data-max="100" class="offerSlider"></div>
            Bundles count (<a style="cursor: help; text-decoration: none;" title="The range of the number of times the game has been bundled. \nChoose range 0-0 to only want never bundled games. \nChoose range 1-max to avoid never-bundled games.">?</a>)
        </fieldset>
        <fieldset>
            <div id="cards" data-max="100" class="offerSlider"></div>
            Cards count (<a style="cursor: help; text-decoration: none;" title="The range of the how many Steam Trading Cards a game must have. \nChoose range 0-0 to only want games with no cards. \nChoose range 1-max to only want games with cards (any amount).">?</a>)
        </fieldset>
        <fieldset>
            <div id="achievements" data-max="1000000" class="offerSlider"></div>
            Achievements count (<a style="cursor: help; text-decoration: none;" title="The range of how many achievements a game must have. \nChoose range 0-0 to only want games with no achievements. \nChoose range 1-max if you only want games with achievements (any amount).">?</a>)
        </fieldset>
        <fieldset>
            <div id="reviews" data-max="1000000" class="offerSlider"></div>
            Review count (<a style="cursor: help; text-decoration: none;" title="I recommend leaving this one on default (0-max). \nThe range of how many reviews a game must have. \nChoose range 0-0 to only want games with no reviews. \nChoose range 1-max if you only want games with reviews (any amount).">?</a>)
        </fieldset>
        <fieldset>
            <div id="scores" data-max="100" data-suffix="%" class="offerSlider"></div>
            Review score (<a style="cursor: help; text-decoration: none;" title="The percentage range of the game review score. \nThe default range (0%-100%) allows games with any review score.">?</a>)
        </fieldset>
        <fieldset>
            <div id="price" data-max="10000" data-prefix="$" class="offerSlider"></div>
            Price (<a style="cursor: help; text-decoration: none;" title="The price range of the game. \nThe default range (0$-max$) allows games with any price amount. \nI'd recommend adjusting this range to roughly match your selected tradable(s).">?</a>)
        </fieldset>
        <fieldset>
            <div id="year" data-min="1950" data-max="2050" class="offerSlider"></div>
            Release year (<a style="cursor: help; text-decoration: none;" title="The release year range of the game.">?</a>)
        </fieldset>
        <fieldset>
            <div id="wishlist" data-max="10000" class="offerSlider"></div>
            Wishlist count (<a style="cursor: help; text-decoration: none;" title="The range of the wishlist count: the number of Barter.vg users that have the game in their wishlist. \nI'd recommend adjusting this range to roughly match your selected tradable(s).">?</a>)
        </fieldset>
        <fieldset>
            <div id="library" data-max="10000" class="offerSlider"></div>
            Library count (<a style="cursor: help; text-decoration: none;" title="The range of the library count: the number of Barter.vg users that have the game in their library. \nIf you want only rare games (games nobody or only a few have), you can lower the upper range bound.">?</a>)
        </fieldset>
        <fieldset style="border-bottom: 1px solid rgb(153, 17, 187);">
            <div id="tradables" data-max="10000" class="offerSlider"></div>
            Tradables count (<a style="cursor: help; text-decoration: none;" title="The range of the tradables count or availability: the number of Barter.vg users that have the game to trade.">?</a>)
        </fieldset>
        <p style="float: left;">...from these platforms...</p>
        <p style="margin-left: 50%;">...from users who...</p>
        <div style="width: 50%; float: right; height: 14em; overflow: auto; border-top: 1px solid rgb(153, 17, 187); border-bottom: 1px solid rgb(153, 17, 187);">
            <ul>
                <li> <input type="checkbox" name="want_from" id="wantwishlist" value="wishlist" checked="true"> <label for="wantwishlist">...have those tradables in your wishlist.</label></li>
                <li> <input type="checkbox" name="want_from" id="wantunowned" value="unowned"> <label for="wantunowned">...do <strong>not</strong> have those tradables in your library.</label></li>
                <li> <input type="checkbox" name="want_from" id="wantlibrary" value="library"> <label for="wantlibrary">...have those tradables in your library.</label></li>
                <li> <input type="checkbox" name="want_from" id="wanttradable" value="tradable"> <label for="wanttradable">...have those tradables in your tradables list.</label></li>
            </ul>
        </div>
        <div style="width: 50%; height: 14em; overflow: auto; border-top: 1px solid rgb(153, 17, 187); border-bottom: 1px solid rgb(153, 17, 187);">
            <ul>
                <li><input type="checkbox" name="platform" id="steam" value="1" checked="true"><label for="steam">Steam</label></li>
                <li><input type="checkbox" name="platform" id="steampkg" value="2"><label for="steampkg">Steam Package</label></li>
                <li><input type="checkbox" name="platform" id="humblebundle" value="3"><label for="humblebundle">Humble Bundle</label></li>
                <li><input type="checkbox" name="platform" id="gog" value="4"><label for="gog">GOG.com</label></li>
                <li><input type="checkbox" name="platform" id="origin" value="5"><label for="origin">Origin</label></li>
                <li><input type="checkbox" name="platform" id="desura" value="6"><label for="desura">Desura</label></li>
                <li><input type="checkbox" name="platform" id="indiegala" value="7"><label for="indiegala">Indiegala</label></li>
                <li><input type="checkbox" name="platform" id="fanatical" value="8"><label for="fanatical">Fanatical</label></li>
                <li><input type="checkbox" name="platform" id="groupees" value="9"><label for="groupees">Groupees</label></li>
                <li><input type="checkbox" name="platform" id="indieroyale" value="10"><label for="indieroyale">Indie Royale</label></li>
                <li><input type="checkbox" name="platform" id="gamersgate" value="11"><label for="gamersgate">GamersGate</label></li>
                <li><input type="checkbox" name="platform" id="amazon" value="12"><label for="amazon">Amazon</label></li>
                <li><input type="checkbox" name="platform" id="humblebundle" value="13"><label for="humblebundle">Humble Store</label></li>
                <li><input type="checkbox" name="platform" id="uplay" value="14"><label for="uplay">Uplay</label></li>
                <li><input type="checkbox" name="platform" id="lazyguys" value="15"><label for="lazyguys">Lazy Guys Bundle</label></li>
                <li><input type="checkbox" name="platform" id="greenlight" value="16"><label for="greenlight">Green Light Bundle</label></li>
                <li><input type="checkbox" name="platform" id="dailyindie" value="17"><label for="dailyindie">DailyIndieGame</label></li>
                <li><input type="checkbox" name="platform" id="flyingbundle" value="18"><label for="flyingbundle">Flying Bundle</label></li>
                <li><input type="checkbox" name="platform" id="steami" value="19"><label for="steami">Steam Item</label></li>
                <li><input type="checkbox" name="platform" id="gmg" value="20"><label for="gmg">Green Man Gaming</label></li>
                <li><input type="checkbox" name="platform" id="nuuvem" value="21"><label for="nuuvem">Nuuvem</label></li>
                <li><input type="checkbox" name="platform" id="macgamestore" value="22"><label for="macgamestore">MacGameStore</label></li>
                <li><input type="checkbox" name="platform" id="playinjector" value="23"><label for="playinjector">Playinjector</label></li>
                <li><input type="checkbox" name="platform" id="igamestand" value="24"><label for="igamestand">IndieGameStand</label></li>
                <li><input type="checkbox" name="platform" id="3ds" value="25"><label for="3ds">Nintendo 3DS</label></li>
                <li><input type="checkbox" name="platform" id="wiiu" value="26"><label for="wiiu">WiiU</label></li>
                <li><input type="checkbox" name="platform" id="coinplay" value="27"><label for="coinplay">Coinplay.io</label></li>
                <li><input type="checkbox" name="platform" id="itchio" value="28"><label for="itchio">itch.io</label></li>
                <li><input type="checkbox" name="platform" id="superduper" value="29"><label for="superduper">Super-Duper Bundle</label></li>
                <li><input type="checkbox" name="platform" id="onemore" value="30"><label for="onemore">one more bundle</label></li>
                <li><input type="checkbox" name="platform" id="cubicbundle" value="31"><label for="cubicbundle">Cubic Bundle</label></li>
                <li><input type="checkbox" name="platform" id="telltale" value="32"><label for="telltale">Telltale Games</label></li>
                <li><input type="checkbox" name="platform" id="hrk" value="33"><label for="hrk">HRK</label></li>
                <li><input type="checkbox" name="platform" id="wingamestore" value="34"><label for="wingamestore">WinGameStore</label></li>
                <li><input type="checkbox" name="platform" id="sgreenlight" value="35"><label for="sgreenlight">Steam Greenlight</label></li>
                <li><input type="checkbox" name="platform" id="orlygift" value="36"><label for="orlygift">Orlygift</label></li>
                <li><input type="checkbox" name="platform" id="squareenix" value="37"><label for="squareenix">Square Enix</label></li>
                <li><input type="checkbox" name="platform" id="otakumaker" value="38"><label for="otakumaker">OtakuMaker</label></li>
                <li><input type="checkbox" name="platform" id="bundlekings" value="39"><label for="bundlekings">Bundle Kings</label></li>
                <li><input type="checkbox" name="platform" id="gamebundle" value="40"><label for="gamebundle">GameBundle</label></li>
                <li><input type="checkbox" name="platform" id="chronogg" value="41"><label for="chronogg">Chrono.gg</label></li>
                <li><input type="checkbox" name="platform" id="dlgamer" value="42"><label for="dlgamer">DLGamer</label></li>
                <li><input type="checkbox" name="platform" id="gamesplanet" value="43"><label for="gamesplanet">Gamesplanet</label></li>
                <li><input type="checkbox" name="platform" id="silagames" value="44"><label for="silagames">Sila Games</label></li>
                <li><input type="checkbox" name="platform" id="bunchofkeys" value="45"><label for="bunchofkeys">Bunch of Keys</label></li>
                <li><input type="checkbox" name="platform" id="g2a" value="46"><label for="g2a">G2A</label></li>
                <li><input type="checkbox" name="platform" id="steamground" value="47"><label for="steamground">Steamground</label></li>
                <li><input type="checkbox" name="platform" id="99cent" value="48"><label for="99cent">99 Cent Bundle</label></li>
                <li><input type="checkbox" name="platform" id="redacted" value="49"><label for="redacted">Redacted Network</label></li>
                <li><input type="checkbox" name="platform" id="breadbox" value="50"><label for="breadbox">Bread Box Bundle</label></li>
                <li><input type="checkbox" name="platform" id="ps4" value="51"><label for="ps4">PlayStation 4</label></li>
                <li><input type="checkbox" name="platform" id="ps3" value="52"><label for="ps3">PlayStation 3</label></li>
                <li><input type="checkbox" name="platform" id="xb360" value="53"><label for="xb360">Xbox 360</label></li>
                <li><input type="checkbox" name="platform" id="xbone" value="54"><label for="xbone">Xbox One</label></li>
                <li><input type="checkbox" name="platform" id="steamgifts" value="55"><label for="steamgifts">Steamgifts.com</label></li>
                <li><input type="checkbox" name="platform" id="marvelous" value="56"><label for="marvelous">MarvelousCrate</label></li>
                <li><input type="checkbox" name="platform" id="barter" value="57"><label for="barter">Barter.vg</label></li>
                <li><input type="checkbox" name="platform" id="gogobundles" value="58"><label for="gogobundles">GoGoBundle</label></li>
                <li><input type="checkbox" name="platform" id="cdkeys" value="59"><label for="cdkeys">CDKeys.com</label></li>
                <li><input type="checkbox" name="platform" id="kinguin" value="60"><label for="kinguin">Kinguin</label></li>
                <li><input type="checkbox" name="platform" id="unspecified2" value="61"><label for="unspecified2">Unspecified Platform</label></li>
                <li><input type="checkbox" name="platform" id="otakubundle" value="62"><label for="otakubundle">Otaku Bundle</label></li>
                <li><input type="checkbox" name="platform" id="dogebundle" value="63"><label for="dogebundle">DogeBundle</label></li>
                <li><input type="checkbox" name="platform" id="plati" value="64"><label for="plati">Plati</label></li>
                <li><input type="checkbox" name="platform" id="streamtrades" value="65"><label for="streamtrades">Steamtrades.com</label></li>
                <li><input type="checkbox" name="platform" id="epicgamesstore" value="66"><label for="epicgamesstore">Epic Games Store</label></li>
                <li><input type="checkbox" name="platform" id="twitch" value="67"><label for="twitch">Twitch</label></li>
                <li><input type="checkbox" name="platform" id="indiedeals2" value="68"><label for="indiedeals2">IndieDeals.net</label></li>
                <li><input type="checkbox" name="platform" id="tigb" value="69"><label for="tigb">The Indie Games Bundle</label></li>
                <li><input type="checkbox" name="platform" id="tiltify" value="70"><label for="tiltify">Tiltify</label></li>
                <li><input type="checkbox" name="platform" id="tremorgames" value="71"><label for="tremorgames">Tremor Games</label></li>
                <li><input type="checkbox" name="platform" id="grepublic" value="72"><label for="grepublic">Games Republic</label></li>
                <li><input type="checkbox" name="platform" id="2game" value="73"><label for="2game">2game</label></li>
                <li><input type="checkbox" name="platform" id="d2d" value="74"><label for="d2d">Direct2Drive</label></li>
                <li><input type="checkbox" name="platform" id="dreamgate" value="75"><label for="dreamgate">Dreamgate</label></li>
                <li><input type="checkbox" name="platform" id="newegg" value="76"><label for="newegg">Newegg</label></li>
                <li><input type="checkbox" name="platform" id="ps3" value="77"><label for="ps3">PSVita</label></li>
                <li><input type="checkbox" name="platform" id="ps4" value="78"><label for="ps4">PSN</label></li>
                <li><input type="checkbox" name="platform" id="embloo" value="79"><label for="embloo">Embloo</label></li>
                <li><input type="checkbox" name="platform" id="battlenet" value="80"><label for="battlenet">battle.net</label></li>
                <li><input type="checkbox" name="platform" id="gamivo" value="81"><label for="gamivo">Gamivo</label></li>
                <li><input type="checkbox" name="platform" id="getgames" value="82"><label for="getgames">Get Games Go</label></li>
                <li><input type="checkbox" name="platform" id="g2play" value="83"><label for="g2play">G2play</label></li>
                <li><input type="checkbox" name="platform" id="fangamer" value="84"><label for="fangamer">Fangamer</label></li>
                <li><input type="checkbox" name="platform" id="trove" value="85"><label for="trove">Humble Trove</label></li>
                <li><input type="checkbox" name="platform" id="steam-tracker" value="86"><label for="steam-tracker">Steam-tracker</label></li>
                <li><input type="checkbox" name="platform" id="blinkbundle" value="87"><label for="blinkbundle">Blink Bundle</label></li>
                <li><input type="checkbox" name="platform" id="gamesrage" value="88"><label for="gamesrage">Games Rage</label></li>
                <li><input type="checkbox" name="platform" id="bbandits" value="89"><label for="bbandits">Bundle Bandits</label></li>
                <li><input type="checkbox" name="platform" id="bcentral" value="90"><label for="bcentral">Bundle Central</label></li>
                <li><input type="checkbox" name="platform" id="bdragon" value="91"><label for="bdragon">Bundle Dragon</label></li>
                <li><input type="checkbox" name="platform" id="biab" value="92"><label for="biab">Bundle In A Box</label></li>
                <li><input type="checkbox" name="platform" id="cultofmac" value="93"><label for="cultofmac">Cult of Mac</label></li>
                <li><input type="checkbox" name="platform" id="eurobundle" value="94"><label for="eurobundle">Eurobundle</label></li>
                <li><input type="checkbox" name="platform" id="gram" value="95"><label for="gram">gram.pl</label></li>
                <li><input type="checkbox" name="platform" id="indieammo" value="96"><label for="indieammo">Indie Ammo Box</label></li>
                <li><input type="checkbox" name="platform" id="iborg" value="97"><label for="iborg">IndieBundle</label></li>
                <li><input type="checkbox" name="platform" id="kissmb" value="98"><label for="kissmb">KissMyBundles</label></li>
                <li><input type="checkbox" name="platform" id="madorc" value="99"><label for="madorc">MadOrc</label></li>
                <li><input type="checkbox" name="platform" id="paddle" value="100"><label for="paddle">Paddle</label></li>
                <li><input type="checkbox" name="platform" id="paywuw" value="101"><label for="paywuw">PayWUW</label></li>
                <li><input type="checkbox" name="platform" id="peonb" value="102"><label for="peonb">Peon Bundle</label></li>
                <li><input type="checkbox" name="platform" id="gameolith" value="103"><label for="gameolith">Gameolith</label></li>
                <li><input type="checkbox" name="platform" id="selectnp" value="104"><label for="selectnp">Select n' Play</label></li>
                <li><input type="checkbox" name="platform" id="shinyloot" value="105"><label for="shinyloot">ShinyLoot</label></li>
                <li><input type="checkbox" name="platform" id="stacksocial" value="106"><label for="stacksocial">StackSocial</label></li>
                <li><input type="checkbox" name="platform" id="universala" value="107"><label for="universala">Universala</label></li>
                <li><input type="checkbox" name="platform" id="vodo" value="108"><label for="vodo">VODO</label></li>
                <li><input type="checkbox" name="platform" id="cybundle" value="109"><label for="cybundle">CY Bundle</label></li>
                <li><input type="checkbox" name="platform" id="discord" value="110"><label for="discord">Discord</label></li>
                <li><input type="checkbox" name="platform" id="lequestore" value="111"><label for="lequestore">lequestore</label></li>
                <li><input type="checkbox" name="platform" id="rockstar" value="112"><label for="rockstar">Rockstar Social Club</label></li>
                <li><input type="checkbox" name="platform" id="disc" value="113"><label for="disc">From boxed copy</label></li>
                <li><input type="checkbox" name="platform" id="puppygames" value="114"><label for="puppygames">PuppyGames</label></li>
                <li><input type="checkbox" name="platform" id="igpack" value="115"><label for="igpack">Indie-games pack</label></li>
                <li><input type="checkbox" name="platform" id="supershock" value="116"><label for="supershock">Super Shock Bundle</label></li>
                <li><input type="checkbox" name="platform" id="charlies" value="117"><label for="charlies">Charlie's Games</label></li>
                <li><input type="checkbox" name="platform" id="socks" value="118"><label for="socks">Buy Games Not Socks</label></li>
                <li><input type="checkbox" name="platform" id="subsoap" value="119"><label for="subsoap">Subsoap</label></li>
                <li><input type="checkbox" name="platform" id="bitcoin" value="120"><label for="bitcoin">Bitcoin Bundle</label></li>
                <li><input type="checkbox" name="platform" id="gamersgate" value="121"><label for="gamersgate">IndieFort</label></li>
                <li><input type="checkbox" name="platform" id="voidu" value="122"><label for="voidu">Voidu</label></li>
                <li><input type="checkbox" name="platform" id="gemly" value="123"><label for="gemly">gemly</label></li>
                <li><input type="checkbox" name="platform" id="akens" value="124"><label for="akens">akens.ru</label></li>
                <li><input type="checkbox" name="platform" id="farmkeys" value="125"><label for="farmkeys">FarmKEYS</label></li>
                <li><input type="checkbox" name="platform" id="tkfg" value="126"><label for="tkfg">TKFG</label></li>
                <li><input type="checkbox" name="platform" id="greenlighta" value="127"><label for="greenlighta">Greenlight Arcade</label></li>
                <li><input type="checkbox" name="platform" id="h2o" value="128"><label for="h2o">H2O Bundle</label></li>
                <li><input type="checkbox" name="platform" id="oculus" value="129"><label for="oculus">Oculus</label></li>
                <li><input type="checkbox" name="platform" id="razer" value="130"><label for="razer">Razer</label></li>
                <li><input type="checkbox" name="platform" id="alienware" value="131"><label for="alienware">Alienware</label></li>
                <li><input type="checkbox" name="platform" id="ign" value="132"><label for="ign">IGN</label></li>
                <li><input type="checkbox" name="platform" id="microsoft" value="133"><label for="microsoft">Microsoft Store</label></li>
                <li><input type="checkbox" name="platform" id="alphabundle" value="134"><label for="alphabundle">Alpha Bundle</label></li>
                <li><input type="checkbox" name="platform" id="opiumpulses" value="135"><label for="opiumpulses">Opium Pulses</label></li>
                <li><input type="checkbox" name="platform" id="brightlocker" value="137"><label for="brightlocker">BrightLocker</label></li>
                <li><input type="checkbox" name="platform" id="oyvey" value="138"><label for="oyvey">Oy Vey Keys</label></li>
                <li><input type="checkbox" name="platform" id="stardock" value="139"><label for="stardock">Stardock</label></li>
                <li><input type="checkbox" name="platform" id="lootcrate" value="140"><label for="lootcrate">Loot Crate</label></li>
                <li><input type="checkbox" name="platform" id="allyouplay" value="141"><label for="allyouplay">Allyouplay</label></li>
                <li><input type="checkbox" name="platform" id="bestbuy" value="142"><label for="bestbuy">Best Buy</label></li>
                <li><input type="checkbox" name="platform" id="gameuk" value="143"><label for="gameuk">GAME UK</label></li>
                <li><input type="checkbox" name="platform" id="gamebillet" value="144"><label for="gamebillet">Gamebillet</label></li>
                <li><input type="checkbox" name="platform" id="gamestop" value="145"><label for="gamestop">GameStop</label></li>
                <li><input type="checkbox" name="platform" id="xbone" value="146"><label for="xbone">Xbox Live</label></li>
                <li><input type="checkbox" name="platform" id="arenanet" value="147"><label for="arenanet">ArenaNet</label></li>
                <li><input type="checkbox" name="platform" id="bethesda" value="148"><label for="bethesda">Bethesda.net</label></li>
                <li><input type="checkbox" name="platform" id="gamehag" value="149"><label for="gamehag">Gamehag</label></li>
                <li><input type="checkbox" name="platform" id="kartridge" value="150"><label for="kartridge">Kartridge</label></li>
                <li><input type="checkbox" name="platform" id="lootboy" value="151"><label for="lootboy">LootBoy</label></li>
                <li><input type="checkbox" name="platform" id="dlhnet" value="152"><label for="dlhnet">Dlh.net</label></li>
                <li><input type="checkbox" name="platform" id="giveawaysu" value="153"><label for="giveawaysu">GiveAway.su</label></li>
                <li><input type="checkbox" name="platform" id="gleam" value="154"><label for="gleam">Gleam</label></li>
                <li><input type="checkbox" name="platform" id="gamehunt" value="155"><label for="gamehunt">GAMEHUNT</label></li>
                <li><input type="checkbox" name="platform" id="grabfreegame" value="156"><label for="grabfreegame">GrabFreeGame</label></li>
                <li><input type="checkbox" name="platform" id="wgn" value="157"><label for="wgn">Who's Gaming Now?!</label></li>
                <li><input type="checkbox" name="platform" id="keyjoker" value="158"><label for="keyjoker">KeyJoker</label></li>
                <li><input type="checkbox" name="platform" id="devsource" value="159"><label for="devsource">From Dev / Pub</label></li>
                <li><input type="checkbox" name="platform" id="blank" value="160"><label for="blank">WeGame X</label></li>
                <li><input type="checkbox" name="platform" id="blank" value="161"><label for="blank">Indie Face Kick</label></li>            
            </ul>
        </div>`);

        $(`#offerStatus`).html(`<div class="statusCurrent">Creating...</div><div class="">Preparing...</div><div class="">Sending offers...</div><div class="">Completed</div>`);
        $(`#offer`).prepend(`<div>
            <textarea class="offer_message" name="message" id="offerMessage" placeholder="Add a public comment to automated offers that is relevant and respectful" title="optional public comment up to 255 characters" maxlength="255" style="width: 100%;"></textarea>
        </div>`);

        $(`#from_ratio`).attr(`max`, $(`.tradables input`).length);
        $(`.tradables input[type=checkbox]`).click(() => {
            const n = $(`.tradables input:checked`).length;
            $(`#from_ratio`).attr(`max`, n || 1);
            $(`#from_ratio`).val(Math.min(n || 1, parseInt($(`#from_ratio`).val())));
        });
        $(`#from_ratio`).change(() => $(`#from_ratio`).val(Math.min(parseInt($(`#from_ratio`).attr(`max`)), parseInt($(`#from_ratio`).val()))));

        $(`#exchanges`).nextAll().remove();
        $(`[name=offer_setup]`).remove();
        $(`#exchanges`).after(`
        <p>
            <label for="expire_days">Offer expires in </label>
            <select name="expire_days" id="expire_days">
                <option value="1">1</option>
                <option value="2">2</option>
                <option value="3">3</option>
                <option value="4">4</option>
                <option value="5">5</option>
                <option value="6">6</option>
                <option value="7">7</option>
                <option value="8">8</option>
                <option value="9">9</option>
                <option value="10">10</option>
                <option value="11">11</option>
                <option value="12">12</option>
                <option value="13">13</option>
                <option value="14">14</option>
                <option selected="selected">15</option>
            </select>
            days.
        </p>
        <p>
            <label for="counter_preference">Counter offer is </label>
            <select name="counter_preference" id="counter_preference">
                <option value="-1">Discouraged, don't display counter</option>
                <option value="0" selected="selected">OK, display counter</option>
                <option value="1">Encouraged, bold counter</option>
            </select>
            .
        </p>
        <p>
            <input type="checkbox" name="synclib" id="synclib" value="true" checked><label for="synclib">Synchronize your <a target="_blank" href="/u/${myuid}/l/">barter library</a> with <a target="_blank" href="https://store.steampowered.com/dynamicstore/userdata/">steam store userdata</a> first (RECOMMENDED).</label>
        </p>
        <p><button id="massSendBtn" class="addTo gborder acceptOption" style="font-weight: bold; color: green; width: 100%; height: 2em; font-size: 1.2em; cursor: pointer;">Finish and Send Automated Offers</button>`);
        $(`#massSendBtn`).click((event) => {
            event.preventDefault();
            sendAutomatedOffers();
        });

        $(`[name='add_to_offer_1[]']`).attr(`name`, `offering`);
        $(`#offerStatus+ div`).remove();
        $(`#offerStatus`).attr(`style`, `border-top: 1px solid rgb(153, 17, 187); border-bottom: 1px solid rgb(153, 17, 187);`);
        $(`.offerSlider`).get().forEach(addSlider);
    });
}

function addSlider(slider) {
    const min = parseInt($(slider).data(`min`)) || 0;
    const max = parseInt($(slider).data(`max`)) || 1000;
    const digits = max.toString().split(``).length;
    const prefix = $(slider).data(`prefix`) || ``;
    const suffix = $(slider).data(`suffix`) || ``;
    const isToggle = max === 1;
    const isPercent = suffix === `%`;
    if (isToggle && !isPercent) {
        $(slider).after(`<input type="hidden" value="true" name="${slider.id}">`);
    } else {
        $(slider).after(`<input type="hidden" value="0" name="min${slider.id}">`);
        $(slider).after(`<input type="hidden" value="${max}" name="max${slider.id}">`);
    }

    const toggleTooltip = (val) => {
        if (max === 1) {
            if (isPercent) {
                return val ? `100%` : `0%`;
            } else {
                return val ? `Yes` : `No`;
            }
        }

        return `${prefix}${Number(val).toFixed(0)}${suffix}`;
    };

    const range = { min, max };
    const n = Math.log10(max);
    if (min === 0 && n - Math.floor(n) === 0) {
        let percentage = 0;
        for (let i = 1; i <= digits - 1; i++) {
            percentage += 100 / (digits - 1);
            range[`${percentage}%`] = Math.pow(10, i);
        }
    }

    // eslint-disable-next-line no-undef
    noUiSlider.create(slider, {
        "start": isToggle && !isPercent ? max : [0, max],
        "connect": !isToggle || isPercent,
        "behaviour": isToggle && !isPercent ? `none` : `tap`,
        "tooltips": [{ "to": toggleTooltip }].concat(isToggle && !isPercent ? [] : [{ "to": toggleTooltip }]),
        "step": 1,
        "range": range
    }).on(`update`, (values) => {
        if (isToggle && !isPercent) {
            const value = parseInt(values[0]);
            $(`[name="${slider.id}"]`).val(Boolean(value));

            if (value) {
                $(slider).addClass(`on`);
            } else {
                $(slider).removeClass(`on`);
            }
        } else {
            $(`[name="min${slider.id}"]`).val(Number(values[0]).toFixed(0));
            $(`[name="max${slider.id}"]`).val(Number(values[1]).toFixed(0));
        }
    });

    if (isToggle && !isPercent) {
        $(slider).find(`.noUi-connects`).click(() => slider.noUiSlider.set(parseInt(slider.noUiSlider.get()) ? 0 : 1));
    }
}

function checkSettings(settings) {
    if (!settings) {
        settings = $(`#offer`).serializeObject();
    }

    settings = fixObjectTypes(settings);

    if (!settings.offering) {
        alert(`Please select ${settings.from_ratio} or more of your tradable(s) that you want to offer.`);
        return;
    }

    if (!settings.offering_to) {
        alert(`Please select 1 or more trader groups you want to sent offers to.`);
        return;
    }

    if (!settings.want_from) {
        alert(`Please select 1 or more tradable groups you want to ask tradables from.`);
        return;
    }

    if (!settings.type) {
        alert(`Please select 1 or more tradable type(s) you want to ask tradables from.`);
        return;
    }

    if (!settings.platform) {
        alert(`Please select 1 or more platform(s) you want to ask tradables from.`);
        return;
    }

    if (settings.message && settings.message.length > 255) {
        alert(`Please limit your message to only 255 characters.`);
        return;
    }

    if (!confirm(`Are you sure you want to proceed? Beware that this may cause traders to add you to their ignore list!`)) {
        return;
    }

    settings.offering = Array.isArray(settings.offering) ? settings.offering : [settings.offering];
    settings.offering_to = Array.isArray(settings.offering_to) ? settings.offering_to.map((g) => g.toLowerCase()) : [settings.offering_to.toLowerCase()];
    settings.want_from = Array.isArray(settings.want_from) ? settings.want_from.map((g) => g.toLowerCase()) : [settings.want_from.toLowerCase()];
    settings.platform = Array.isArray(settings.platform) ? settings.platform : [settings.platform];
    settings.type = Array.isArray(settings.type) ? settings.type.map((g) => g.toLowerCase()) : [settings.type.toLowerCase()];

    if (settings.offering.length < settings.from_ratio) {
        alert(`Please select ${settings.from_ratio} or more of your tradable(s) that you want to offer.`);
        return;
    }

    if (settings.offering.length > 100) {
        alert(`Please select 100 or less of your tradable(s) that you want to offer.`);
        return;
    }

    return settings;
}

function logHTML(log) {
    console.log(log);
    $(`#log`).append(`<p>${log}</p>`);
    $(`#log`).get(0).scrollTop = $(`#log`).get(0).scrollHeight;
}

function changeAutomatedOfferStatus(i) {
    [1, 2, 3, 4].forEach((n) => $(`#offerStatus > div:nth-child(${n})`).removeClass(`statusCurrent`));
    $(`#offerStatus > div:nth-child(${i})`).addClass(`statusCurrent`);
}

function calculateStupidDailyLimit(offers) {
    const unique = [];
    for (const id in offers) {
        const offer = offers[id];
        if (offer.from_status !== `completed` || offer.to_status !== `completed`) {
            continue;
        }

        const otherparty = offer.to_user_id === myuid ? offer.from_user_id : offer.to_user_id;
        if (unique.includes(otherparty)) {
            continue;
        }

        unique.push(otherparty);
    }

    const daylength = 24 * 60 * 60 * 1000;
    const recent = [];
    for (const id in offers) {
        const offer = offers[id];
        if (offer.from_user_id === myuid && offer.created * 1000 > Date.now() - daylength && offer.to_status !== `completed` && offer.from_status !== `cancelled`) {
            recent.push(id);
        }
    }

    return (unique.length < 100 ? 200 : 400) - recent.length;
}

async function sendAutomatedOffers(options) {
    const settings = checkSettings(options);
    const alertError = (err) => {
        console.log(err);
        alert(err.message || err.name);
    };

    console.log({ settings });

    if (!settings) {
        return;
    }

    changeAutomatedOfferStatus(2);
    $(`#offer`).replaceWith(`<div style="height: 28em; overflow: auto; border-bottom: 1px solid rgb(153, 17, 187);" id="log"></div>`);
    showSpinner(`automatedoffers`);

    let offers = await getBarterOffers(parseInt(myuid, 16));
    let dailylimit = calculateStupidDailyLimit(offers); // barter's stupid daily offer limit

    if (dailylimit <= 0) {
        logHTML(`<strong>Cancelled, because you hit/exceeded <a target="_blank" href="/u/a0/">barter.vg</a>'s daily offer limit...<br>Help him convince to remove this limit by complaining to him!</strong>`);
        return;
    }

    if (settings.synclib) {
        logHTML(`Syncing your <a target="_blank" href="/u/${myuid}/l/">barter library</a> with your <a target="_blank" href="https://store.steampowered.com/dynamicstore/userdata/">steam user data</a>...`);
        await syncLibrary().catch(alertError);
    }

    logHTML(`Getting list of users that opted out for automated offers..`);
    const optins = await getBarterAppSettings(2);
    console.log(`optins`, optins);

    logHTML(`Getting info about the tradables you are offering...`);
    const allmatches = {};
    const mytradables = {};
    const { tags, "user": { region } } = await getBarterTradables(parseInt(myuid, 16));
    const tagregions = {
        // "26": region,
        // "27": `RU`,
        "28": 1,
        "29": 2,
        "30": 3,
        "31": 4,
        "34": 6,
        "35": 5
        // "346": region
        // "364": `PL`,
        // "376": `ROW`,
        // "387": `DE`,
        // "398": `IN`,
        // "479": `CN`
    };

    for (const i of settings.offering) {
        const key = i.split(`,`);
        mytradables[i] = await getBarterItemInfo(key[0]);
        mytradables[i].regions = Object.values(tags[key[1]] || {}).filter((tag) => Object.keys(tagregions).includes(tag.tag_id.toString())).map((tag) => tagregions[tag.tag_id]);
    }

    console.log(`mytradables`, mytradables);
    for (const key in mytradables) {
        mytradables[key].matches = new Set(); // the users that want this tradable
        const gameinfo = mytradables[key];

        logHTML(`Limiting traders that potentionally want <a target="_blank" href="/i/${gameinfo.id}/">this tradable</a>, according to their preferences...`);

        settings.offering_to.forEach((group) => {
            if (!gameinfo.users.hasOwnProperty(group)) {
                return;
            }

            for (const userid in gameinfo.users[group]) { // for every user in that group
                if (!gameinfo.users[group].hasOwnProperty(userid)) {
                    continue;
                }

                const uid = parseInt(userid);
                const user = gameinfo.users[group][userid];
                if (passesTheirPreferences(gameinfo, user, optins, group, settings.offering.length)) {
                    mytradables[key].matches.add(uid);
                    allmatches[uid] = user;
                    allmatches[uid].want = new Set();
                }
            }
        });
    }

    for (const userid in allmatches) {
        const uid = parseInt(userid);
        const offeringcount = Object.values(mytradables).filter((tradable) => tradable.matches.has(uid)).length; // the amount of tradables I can offer that user
        const pendingusers = Object.values(offers).filter((offer) => offer.from_user_id === myuid && offer.to_status === `pending`).map((offer) => parseInt(offer.to_user_id, 16));

        // if I can't offer enough (according to my options)
        // or if I exceed the user's max items per offer setting
        // or if I already have a pending offer sent to the user
        // then delete match
        if (offeringcount < settings.from_ratio || offeringcount > allmatches[uid].max_items_per_offer || pendingusers.includes(uid)) {
            delete allmatches[uid];
            for (const k in mytradables) {
                mytradables[k].matches.delete(uid);
            }
        }
    }

    delete allmatches[parseInt(myuid, 16)];
    for (const k in mytradables) {
        mytradables[k].matches.delete(parseInt(myuid, 16));
    }

    const matchedcount = Object.keys(allmatches).length;
    logHTML(`Found ${matchedcount} matching traders!`);
    console.log(`allmatches`, allmatches);
    if (matchedcount === 0) {
        logHTML(`Done!`);
        changeAutomatedOfferStatus(4);
        hideSpinner(`automatedoffers`);
        return;
    }

    logHTML(`Limiting traders that have tradables you want, according to your preferences...${matchedcount > 100 ? `<br><strong>This may take several minutes</strong>` : ``}`);
    for (const userid in allmatches) { // gonna filter out the matches that have no tradables that interest me
        const uid = parseInt(userid);

        const groups = await getFilteredTradables(uid).catch(alertError); // the user's tradables filtered according to my collections
        if (!groups) {
            delete allmatches[uid];
            for (const key in mytradables) {
                mytradables[key].matches.delete(uid);
            }
            continue;
        }

        const want_items = [].concat(...settings.want_from.map((group) => groups[group] || [])); // array of item id's of the group of tradables I (still) want from this user
        if (want_items.length === 0) { // if user has no tradables I'm interested in, delete match
            delete allmatches[uid];
            for (const key in mytradables) {
                mytradables[key].matches.delete(uid);
            }
            continue;
        }

        const theirtradables = await getBarterTradables(uid).catch(alertError);
        if (!theirtradables) {
            delete allmatches[uid];
            for (const key in mytradables) {
                mytradables[key].matches.delete(uid);
            }
            continue;
        }

        const no_offers_items = theirtradables.tags && Object.keys(theirtradables.tags).length > 0 ? Object.values(Object.assign(...Object.values(theirtradables.tags))).filter((tag) => tag.tag_id === 369).map((tag) => tag.line_id) : [];
        const limited_items = theirtradables.steam_limited;

        for (const platformid in theirtradables.by_platform) {
            const tradables = theirtradables.by_platform[platformid];
            for (const line_id in tradables) {
                const tradable = tradables[line_id];
                tradable.regions = Object.values(theirtradables.tags[line_id] || {}).filter((tag) => Object.keys(tagregions).includes(tag.tag_id.toString())).map((tag) => tagregions[tag.tag_id]);
                if (passesMyPreferences(tradable, settings, want_items, no_offers_items, limited_items, region)) {
                    allmatches[uid].want.add(`${tradable.item_id},${tradable.line_id}`);
                }
            }
        }

        if (allmatches[uid].want.size < parseInt(settings.to_ratio) || allmatches[uid].want.size === 0) {
            delete allmatches[uid];
            for (const key in mytradables) {
                mytradables[key].matches.delete(uid);
            }
            continue;
        }
    }

    const matchescount = Object.keys(allmatches).length;
    logHTML(`Found ${matchescount} matching traders!`);

    if (matchescount < settings.minlimit) {
        logHTML(`Not enough matches found. Done!`);
        changeAutomatedOfferStatus(4);
        hideSpinner(`automatedoffers`);
        return;
    }

    const max = Math.min(settings.maxlimit, matchescount);
    logHTML(`Sending ${max} automated offers...`);
    changeAutomatedOfferStatus(3);

    let sent = 0;
    let failed = 0;
    const matches = shuffle(Object.keys(allmatches)).slice(0, max); // the users to send the automated offers to
    for (const userid of matches) {
        if (sent === dailylimit) {
            const daylength = 24 * 60 * 60 * 1000 + 10000; // 24h + 10 seconds
            const endtime = Date.now() + daylength;
            const countdown = setInterval(() => {
                const distance = endtime - Date.now();

                if (distance < 0) {
                    clearInterval(countdown);
                    return;
                }

                const days = Math.floor(distance / (1000 * 60 * 60 * 24));
                const hours = Math.floor(distance % (1000 * 60 * 60 * 24) / (1000 * 60 * 60));
                const minutes = Math.floor(distance % (1000 * 60 * 60) / (1000 * 60));
                const seconds = Math.floor(distance % (1000 * 60) / 1000);

                $(`.countdown`).html(`${days}d ${hours}h ${minutes}m ${seconds}s`);
            }, 1000);

            logHTML(`<strong>Now we have to wait <i class="countdown"></i> before sending the remaining ${matches.length - failed - sent} offers, because <a target="_blank" href="/u/a0/">barter.vg</a> thinks this limit is neccesary...<br>Help him change his mind by complaining to him!</strong>`);

            await delay(daylength); // wait an entire day, because barter thinks it's neccesary

            offers = await getBarterOffers(parseInt(myuid, 16)).catch(alertError);
            dailylimit = sent + calculateStupidDailyLimit(offers); // barter's stupid daily offer limit
        }

        const uid = parseInt(userid);
        const ato1 = shuffle(Object.keys(mytradables).filter((key) => mytradables[key].matches.has(uid))); // only add my offered tradables that the user actually wants
        const ato2 = shuffle([...allmatches[uid].want]).splice(0, allmatches[uid].max_items_per_offer || 100);

        const offer = await sendBarterOffer({
            "to": uid,
            "from_and_or": settings.from_ratio === ato1.length ? 0 : settings.from_ratio,
            "to_and_or": settings.to_ratio === ato2.length ? 0 : settings.to_ratio,
            "expire": Math.max(allmatches[uid].expire_min_from || 1, settings.expire_days),
            "counter_preference": settings.counter_preference,
            "add_to_offer_from[]": ato1,
            "add_to_offer_to[]": ato2,
            "message": settings.message || ``
        }).catch((err) => {
            failed++;
            logHTML(`<strong>Failed to send automated offer to <a target="_blank" href="/u/${uid.toString(16)}/">${allmatches[uid].steam_persona}</a></strong> (${(sent)}/${max} sent${failed > 0 ? `, ${failed} failed` : ``})`);
            console.log(err);
        });

        if (offer) {
            sent++;
            logHTML(`Successfully sent automated <a target="_blank" href="/u/${myuid}/o/${offer.offer_id}/">offer</a> to <a target="_blank" href="/u/${uid.toString(16)}/">${allmatches[uid].steam_persona}</a> (${(sent)}/${max} sent${failed > 0 ? `, ${failed} failed` : ``})`);
        }
    }

    logHTML(`Done!`);
    changeAutomatedOfferStatus(4);
    hideSpinner(`automatedoffers`);
}

function getBarterOffers(uid) {
    return new Promise(async(res, rej) => {
        const response = await request({
            "method": `GET`,
            "url": `https://barter.vg/u/${uid.toString(16)}/o/json/`
        }).catch(rej);

        let json;
        try {
            json = JSON.parse(response.responseText);
        } catch (e) {
            rej({ "error": e, "data": response });
        }

        delete json[0]; // metadata
        res(json);
    });
}

function getBarterTradables(uid) {
    return new Promise(async(res, rej) => {
        const response = await request({
            "method": `GET`,
            "url": `https://barter.vg/u/${uid.toString(16)}/t/json/`
        }).catch(rej);

        let json;
        try {
            json = JSON.parse(response.responseText);
        } catch (e) {
            rej({ "error": e, "data": response });
        }

        res(json);
    });
}

function getFilteredTradables(uid) {
    return new Promise(async(res, rej) => {
        const response = await request({
            "method": `GET`,
            "url": `https://barter.vg/u/${uid.toString(16)}/t/f/${myuid}/json/`
        }).catch(rej);

        let json;
        try {
            json = JSON.parse(response.responseText);
        } catch (e) {
            rej({ "error": e, "data": response });
        }

        res(json);
    });
}

function getBarterItemInfo(itemid) {
    return new Promise(async(res, rej) => {
        const response = await request({
            "method": `GET`,
            "url": `https://barter.vg/i/${itemid}/json2/`
        }).catch(rej);

        let json;
        try {
            json = JSON.parse(response.responseText);
        } catch (e) {
            rej({ "error": e, "data": response });
        }

        res(json);
    });
}

function getBarterAppSettings(appid) {
    return new Promise(async(res, rej) => {
        const response = await request({
            "method": `GET`,
            "url": `https://barter.vg/app/${appid}/settings/`
        }).catch(rej);

        let json;
        try {
            json = JSON.parse(response.responseText);
        } catch (e) {
            rej({ "error": e, "data": response });
        }

        res(json);
    });
}

function sendBarterOffer(options) {
    return new Promise(async(res, rej) => {
        console.log(`options`, options);
        const response = await request({
            "url": `https://barter.vg/u/${myuid}/o/json/`,
            "method": `POST`,
            "headers": {
                "Content-Type": `application/x-www-form-urlencoded`
            },
            "data": $.param({
                "app_id": 2,
                "app_version": versionToInteger(GM_info.script.version),
                ...options
            })
        }).catch(rej);

        let json;
        try {
            json = JSON.parse(response.responseText);
        } catch (e) {
            rej({ "error": e, "data": response });
        }

        res(json);
    });
}

function delay(ms) {
    return new Promise((res) => setTimeout(() => res(), ms));
}

async function jqueryPost(url, data, callback) {
    const response = await request({
        "url": url,
        "method": `POST`,
        "headers": {
            "Content-Type": `application/x-www-form-urlencoded`
        },
        "data": $.param(data)
    }).catch(callback);

    if (response) {
        callback(response.responseText);
        return;
    }
}

function request(options) {
    if (options.onload) {
        GM_xmlhttpRequest(options);
        return;
    }

    return new Promise((res, rej) => GM_xmlhttpRequest({
        "timeout": 240000,
        ...options,
        "onload": res,
        "onerror": (...params) => rej({ "reason": `error`, params }),
        "ontimeout": (...params) => rej({ "reason": `timeout`, params }),
        "onabort": (...params) => rej({ "reason": `abort`, params })
    }));
}

function passesMyPreferences(game, settings, want_items, no_offers_items, limited_items, myregion) {
    let pass = want_items.includes(game.item_id) && settings.platform.includes(game.platform_id) && !no_offers_items.includes(game.line_id);

    if (pass && myregion && game.regions && game.regions.length > 0) {
        pass = pass && game.regions.includes(myregion); // their tradable region lock has my region
    }

    if (pass && game.hasOwnProperty(`extra`)) { // Number of copies
        pass = pass && game.extra > 0;
    }

    if (pass && game.hasOwnProperty(`item_type`) && settings.hasOwnProperty(`type`)) {
        pass = pass && settings.type.includes(game.item_type.toLowerCase());
    }

    if (pass && game.hasOwnProperty(`item_type`) && settings.hasOwnProperty(`minDLC`) && settings.hasOwnProperty(`maxDLC`)) {
        if (settings.minDLC === 0 && settings.maxDLC === 0) {
            pass = pass && game.item_type.toLowerCase() !== `dlc`;
        }
        if (settings.minDLC === 1 && settings.maxDLC === 1) {
            pass = pass && game.item_type.toLowerCase() === `dlc`;
        }
    }

    if (pass && settings.hasOwnProperty(`minlimited`) && settings.hasOwnProperty(`maxlimited`)) {
        if (settings.minlimited === 0 && settings.maxlimited === 0) {
            pass = pass && !limited_items.includes(game.item_id);
        }
        if (settings.minlimited === 1 && settings.maxlimited === 1) {
            pass = pass && limited_items.includes(game.item_id);
        }
    }

    if (pass && settings.hasOwnProperty(`mingivenaway`) && settings.hasOwnProperty(`maxgivenaway`)) {
        if (settings.mingivenaway === 0 && settings.maxgivenaway === 0) {
            pass = pass && (game.givenaway || 0) === 0;
        }
        if (settings.mingivenaway === 1 && settings.maxgivenaway === 1) {
            pass = pass && (game.givenaway || 0) !== 0;
        }
    }

    if (pass && settings.hasOwnProperty(`minbundles`) && settings.hasOwnProperty(`maxbundles`)) {
        pass = pass && inRange(settings.minbundles, settings.maxbundles, game.bundles_all || 0);
    }

    if (pass && settings.hasOwnProperty(`mincards`) && settings.hasOwnProperty(`maxcards`)) {
        pass = pass && inRange(settings.mincards, settings.maxcards, game.cards || 0);
    }

    if (pass && settings.hasOwnProperty(`minachievements`) && settings.hasOwnProperty(`maxachievements`)) {
        pass = pass && inRange(settings.minachievements, settings.maxachievements, game.achievements || 0);
    }

    if (pass && settings.hasOwnProperty(`minreviews`) && settings.hasOwnProperty(`maxreviews`)) {
        pass = pass && inRange(settings.minreviews, settings.maxreviews, game.reviews_total || 0);
    }

    if (pass && settings.hasOwnProperty(`minscores`) && settings.hasOwnProperty(`maxscores`)) {
        pass = pass && inRange(settings.minscores, settings.maxscores, game.reviews_positive || 0);
    }

    if (pass && settings.hasOwnProperty(`minprice`) && settings.hasOwnProperty(`maxprice`)) {
        pass = pass && inRange(settings.minprice, settings.maxprice, (game.price || 0) / 100);
    }

    if (pass && settings.hasOwnProperty(`minyear`) && settings.hasOwnProperty(`maxyear`)) {
        pass = pass && inRange(settings.minyear, settings.maxyear, game.year || 0);
    }

    if (pass && settings.hasOwnProperty(`minwishlist`) && settings.hasOwnProperty(`maxwishlist`)) {
        pass = pass && inRange(settings.minwishlist, settings.maxwishlist, game.wishlist || 0);
    }

    if (pass && settings.hasOwnProperty(`minlibrary`) && settings.hasOwnProperty(`maxlibrary`)) {
        pass = pass && inRange(settings.minlibrary, settings.maxlibrary, game.library || 0);
    }

    if (pass && settings.hasOwnProperty(`mintradables`) && settings.hasOwnProperty(`maxtradables`)) {
        pass = pass && inRange(settings.mintradables, settings.maxtradables, game.tradable || 0);
    }

    return pass;
}

function passesTheirPreferences(game, user, optins, group, offeringcount) { // game = my tradable
    let pass = user.region && game.regions && game.regions.length > 0 ? game.regions.includes(user.region) : true;

    if (pass && user.hasOwnProperty(`steam_id64_string`) && optins.hasOwnProperty(user.steam_id64_string)) {
        pass = pass && optins[user.steam_id64_string];
    }

    if (pass && user.hasOwnProperty(`wants_unowned`)) {
        if (user.wants_unowned === 0) {
            pass = pass && group === `wishlist`;
        } else if (user.wants_unowned === 1) {
            pass = pass && (group === `wishlist` || group === `unowned`);
        }
    }

    if (pass && user.hasOwnProperty(`wants_library`)) {
        if (user.wants_library === 0) {
            pass = pass && group !== `library`;
        }
    }

    if (pass && user.hasOwnProperty(`wants_tradable`)) {
        if (user.wants_tradable === 0) {
            pass = pass && group !== `tradable`;
        }
    }

    if (pass && user.hasOwnProperty(`wants_cards`) && user.wants_cards === 1) {
        pass = pass && (game.cards || 0) > 0 && (game.cards_marketable || 0) === 1;
    }

    if (pass && user.hasOwnProperty(`wants_achievements`) && user.wants_achievements === 1) {
        pass = pass && (game.achievements || 0) > 0;
    }

    if (pass && user.hasOwnProperty(`avoid_givenaway`) && user.avoid_givenaway === 1) {
        pass = pass && (game.giveaway_count || 0) === 0;
    }

    if (pass && user.hasOwnProperty(`avoid_bundles`) && user.avoid_bundles === 1) {
        pass = pass && (game.bundles_available || 0) === 0;
    }

    if (pass && game.hasOwnProperty(`user_reviews_positive`) && user.hasOwnProperty(`wants_rating`) && game.user_reviews_positive >= 0) {
        pass = pass && user.wants_rating <= game.user_reviews_positive;
    }

    if (pass && user.hasOwnProperty(`max_items_per_offer`)) {
        pass = pass && user.max_items_per_offer >= offeringcount;
    }

    if (pass && game.hasOwnProperty(`source_id`) && user.hasOwnProperty(`wants_steam_only`) && user.wants_steam_only === 1) {
        pass = pass && (game.source_id === 1 || game.source_id === 2);
    }

    if (pass && game.hasOwnProperty(`item_type`) && user.hasOwnProperty(`avoid_dlc`) && user.avoid_dlc === 1) {
        pass = pass && game.item_type.toLowerCase() !== `dlc`;
    }

    if (user.hasOwnProperty(`windows`) && user.windows === 1 && user.hasOwnProperty(`mac`) && user.mac === 1 && user.hasOwnProperty(`linux`) && user.linux === 1) { // workaround
        user.windows = 0;
        user.mac = 0;
        user.linux = 0;
    }

    if (pass && game.hasOwnProperty(`windows`) && user.hasOwnProperty(`windows`) && user.windows === 1) {
        pass = pass && game.windows === 1;
    }

    if (pass && game.hasOwnProperty(`mac`) && user.hasOwnProperty(`mac`) && user.mac === 1) {
        pass = pass && game.mac === 1;
    }

    if (pass && game.hasOwnProperty(`linux`) && user.hasOwnProperty(`linux`) && user.linux === 1) {
        pass = pass && game.linux === 1;
    }

    console.log(user.user_id, pass);
    return pass;
}

function fixObjectTypes(obj) {
    for (const key in obj) {
        if (obj.hasOwnProperty(key)) {
            const val = obj[key];
            if (val === `true`) {
                obj[key] = true;
            } else if (val === `false`) {
                obj[key] = false;
            } else if (val === ``) {
                delete obj[key];
            } else if (isFinite(val)) {
                obj[key] = Number(val);
            }
        }
    }
    return obj;
}

function filterRows(event) {
    const val = event.target.value.toLowerCase();
    $(`tbody tr:not(.platform)`).get().forEach((elem) => {
        if ($(elem).text().toLowerCase().includes(val)) {
            $(elem).show();
        } else {
            $(elem).hide();
        }
    });
}

function searchOffers(event) {
    const val = event.target.value.toLowerCase();
    $(`#offers tr:has(abbr)`).get().forEach((elem) => {
        if ($(elem).text().toLowerCase().includes(val)) {
            $(elem).show();
        } else {
            $(elem).hide();
        }
    });

    $(`#offers tr.commentPreview`).get().forEach((elem) => {
        if ($(elem).prev().is(`:hidden`)) {
            $(elem).hide();
        } else {
            $(elem).show();
        }
    });
}

function messageOffers(event) {
    event.preventDefault();

    const message = prompt(`Message:`);

    if (!message) {
        return;
    }

    $(`#offers tr:visible:has(abbr)`).get().forEach((elem) => $.post($(`a.textColor`, elem).attr(`href`), {
        "offer_message": message,
        "offer_setup": 3
    }, () => $(elem).find(`*`).css(`color`, `green`)));
}

function cancelOffers(event) {
    event.preventDefault();

    if (!confirm(`Are you sure you want to cancel all trade offers displayed below?`)) {
        return;
    }

    $(`#offers tr:visible:has(abbr)`).get().forEach((elem) => $.post($(`a.textColor`, elem).attr(`href`), {
        "offer_setup": 3,
        "cancel_offer": `☒ Cancel Offer`
    }, () => $(elem).remove()));
}

function extendExpiry(event) {
    event.preventDefault();
    const expire_days = parseInt(prompt(`Expiration:`, `15`));
    if (isNaN(expire_days)) {
        return;
    }

    $(`#offers tr:visible`).get().forEach((elem) => {
        const url = $(`a.textColor`, elem).attr(`href`);
        $.post(url, {
            "offer_setup": 3,
            "edit_offer": `✐ Edit Offer`
        }, (data) => {
            data = data.replace(/src="[^"]*"/ig, ``);
            const formdata = $(`#offer`, data).serializeObject();
            formdata.expire_days = expire_days;
            formdata.propose_offer = `Finish and Propose Offer`;
            $.post(url, formdata, () => $(elem).find(`*`).css(`color`, `green`));
        });
    });
}

function ajaxify() {
    return; // disabled for now
    // eslint-disable-next-line no-unreachable
    $(`form:not([target='_blank'], [onsubmit])`).submit(formSubmitted);
    $(`button[name], [type=submit]:not([target='_blank'], [onsubmit])`).click(submitClicked);
}

function submitClicked(event) {
    if ($(event.target).parents(`form`).length > 0) {
        $(`[type=submit]`, $(event.target).parents(`form`)).removeAttr(`clicked`);
        $(event.target).attr(`clicked`, `true`);
    } else {
        $(`form`).off(`submit`, formSubmitted);
    }
}

function formSubmitted(event) {
    event.preventDefault();

    const form = event.target;
    const submit = $(`[clicked=true]`, form);
    const action = submit.is(`[formaction]`) ? submit.attr(`formaction`) : $(form).attr(`action`);
    const method = $(form).attr(`method`) || `POST`;
    const data = $(form).serializeObject();
    const xhr = new XMLHttpRequest();

    if (submit.attr(`name`)) {
        data[submit.attr(`name`)] = submit.attr(`value`) || ``;
    }
    submit.css(`cursor`, `not-allowed`).prop(`disabled`, true);

    $.ajax({
        "url": action,
        "method": method,
        "data": data,
        "xhr": () => xhr,
        "success": parseHtml
    });
}

function parseHtml(html, status, xhr) {
    document.documentElement.innerHTML = html;
    if (xhr) {
        History.replaceState(null, document.title, xhr.url);
    }

    barterReady();
}

function serializeObject() {
    const o = {};
    const a = this.serializeArray();
    a.forEach((elem) => {
        if (o[elem.name] !== undefined) {
            if (!o[elem.name].push) {
                o[elem.name] = [o[elem.name]];
            }
            o[elem.name].push(elem.value || ``);
        } else {
            o[elem.name] = elem.value || ``;
        }
    });
    return o;
}

function shuffle(array) {
    let currentIndex = array.length,
        temporaryValue, randomIndex;
    while (0 !== currentIndex) {
        randomIndex = Math.floor(Math.random() * currentIndex);
        currentIndex -= 1;
        temporaryValue = array[currentIndex];
        array[currentIndex] = array[randomIndex];
        array[randomIndex] = temporaryValue;
    }
    return array;
}

function inRange(num1, num2, numTest) {
    const [min, max] = [parseInt(num1), parseInt(num2)].sort((a, b) => a > b);
    return numTest >= min && numTest <= max;
}

function versionToInteger(ver) {
    let i = 0;
    ver.split(`.`).reverse().forEach((n, index) => {
        i += parseInt(n) * Math.pow(10, 2 * index);
    });
    return i;
}

const stylesheet = `
    .spinner {
        -moz-animation: rotation .6s infinite linear;
        -o-animation: rotation .6s infinite linear;
        -webkit-animation: rotation .6s infinite linear;
        animation: rotation .6s infinite linear;
        border-bottom: 14px solid rgba(0, 104, 209, .15);
        border-left: 14px solid rgba(0, 104, 209, .15);
        border-radius: 100%;
        border-right: 14px solid rgba(0, 104, 209, .15);
        border-top: 14px solid rgba(0, 104, 209, .8);
        height: 70px;
        left: -moz-calc(50% - 35px);
        left: -o-calc(50% - 35px);
        left: -webkit-calc(50% - 35px);
        left: calc(50% - 35px);
        margin: 0px auto;
        position: fixed;
        top: -moz-calc(50% - 35px);
        top: -o-calc(50% - 35px);
        top: -webkit-calc(50% - 35px);
        top: calc(50% - 35px);
        width: 70px;
    }

    @-webkit-keyframes rotation {
        from { -webkit-transform: rotate(0deg); } to { -webkit-transform: rotate(359deg); }
    }

    @-moz-keyframes rotation {
        from { -moz-transform: rotate(0deg); } to { -moz-transform: rotate(359deg); }
    }

    @-o-keyframes rotation {
        from { -o-transform: rotate(0deg); } to { -o-transform: rotate(359deg); }
    }

    @keyframes rotation {
        from { transform: rotate(0deg); } to { transform: rotate(359deg); }
    }

    .strep {
        color: #777;
        margin-left: 5px;
        font-size: 90%;
    }

    .strep .plus {
        color: #090;
    }

    .strep .minus {
        color: #777;
    }

    .strep .minus.hasMinus {
        color: #d00;
        font-weight: bold;
    }

    #offerHeader {
        width: 115%;
    }

    .autocomplete {
        background: rgba(0, 0, 0, 0.6);
        display: none;
        left: 50%;
        margin-left: -20%;
        position: absolute;
        width: 45%;
    }

    .offerSlider .noUi-handle:after,
    .offerSlider .noUi-handle:before {
        display: none;
    }

    .offerSlider .noUi-tooltip {
        display: none;
        border: none;
        border-radius: unset;
        background: none;
        color: inherit;
        padding: unset;
        margin-bottom: -6px;
    }

    .offerSlider:hover .noUi-tooltip {
        display: block;
    }

    .offerSlider .noUi-connect {
        background-color: #fff;
    }

    .offerSlider .noUi-handle {
        border: 1px solid rgb(153, 17, 187);
        box-shadow: none;
        height: 20px;
        outline: none;
        width: 20px;
    }

    .offerSlider.noUi-target {
        background-color: rgba(153, 17, 187, 0.1);
        border: 1px solid rgb(153, 17, 187);
        box-shadow: none;
        float: right;
        height: 10px;
        margin: 7px;
        width: 70%;
    }

    .offerSlider.on {
        background-color: #fff;
    }
`;

init();
// ==/Code==