Enhanced Barter

This userscript aims to enhance your experience at barter.vg

当前为 2018-02-08 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 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    Royalgamer06
// @author       Royalgamer06
// @version      0.9.14.0
// @description  This userscript aims to enhance your experience at barter.vg
// @include      https://barter.vg/*
// @include      https://www.steamtrades.com/user/*?do=postcomments&message=*
// @connect      steamcommunity.com
// @connect      store.steampowered.com
// @connect      steamtrades.com
// @connect      royalgamer06.ga
// @grant        GM_xmlhttpRequest
// @grant        GM_setClipboard
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_info
// @grant        GM_addStyle
// @grant        unsafeWindow
// @run-at       document-start
// @require      https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js
// @homepageURL  https://github.com/Royalgamer06/EnhancedBarter/
// @supportURL   https://github.com/Royalgamer06/EnhancedBarter/issues
// ==/UserScript==

// ==Code==
this.$ = this.jQuery = jQuery.noConflict(true);
var requests = [],
    requestRateLimit = 300,
    myuid = null,
    mysid = null;

function init() {
    if (location.host === "barter.vg") {
        setTimeout(initBarter, 0);
    } else if (location.host === "www.steamtrades.com" && getURIParam("do") == "postcomments") {
        setTimeout(initSteamTrades, 0);
    }
}

function initBarter() {
    unsafeWindow.cancelOffers = cancelOffers;
    unsafeWindow.remindPendingOffers = remindPendingOffers;
    unsafeWindow.remindCompletedOffers = remindCompletedOffers;
    unsafeWindow.fixIgnored = fixIgnored;
    unsafeWindow.showIgnored = showIgnored;
    unsafeWindow.findIgnored = findIgnored;
    unsafeWindow.findAllIgnored = findAllIgnored;
    unsafeWindow.syncLibrary = syncLibrary;
    unsafeWindow.extendExpiry = extendExpiry;
    unsafeWindow.massSendOffers = massSendOffers;
    unsafeWindow.massSendMutualOffers = massSendMutualOffers;
    unsafeWindow.removeOwnedGamesFromPending = removeOwnedGamesFromPending;

    GM_addStyle(`.spinner {
        position: fixed;
        left: -webkit-calc(50% - 35px);
        left: -moz-calc(50% - 35px);
        left: -o-calc(50% - 35px);
        left: calc(50% - 35px);
        top: -webkit-calc(50% - 35px);
        top: -moz-calc(50% - 35px);
        top: -o-calc(50% - 35px);
        top: calc(50% - 35px);
        height: 70px;
        width: 70px;
        margin: 0px auto;
        -webkit-animation: rotation .6s infinite linear;
        -moz-animation: rotation .6s infinite linear;
        -o-animation: rotation .6s infinite linear;
        animation: rotation .6s infinite linear;
        border-left: 14px solid rgba(0, 104, 209, .15);
        border-right: 14px solid rgba(0, 104, 209, .15);
        border-bottom: 14px solid rgba(0, 104, 209, .15);
        border-top: 14px solid rgba(0, 104, 209, .8);
        border-radius: 100%;
    }
    @-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); }
    }`);

    setInterval(handleRequests, requestRateLimit);
    $.ajaxSetup({
        beforeSend: function(xhr, options) {
            $("body").prepend('<div class="spinner"></div>');
            var request = function() {
                options.beforeSend = function() {
                    return true;
                };
                $.ajax(options);
            };
            requests.push(request);
            return false;
        },
        complete: $(".spinner").remove
    });

    $.fn.serializeObject = serializeObject;
    $.fn.zip = zip;

    $(document).ready(barterReady);
}

function barterReady() {
    setTimeout(ajaxify, 0);
    myuid = $("#signin").attr("href").split("/")[4];
    mysid = $("abbr+ a:has(.icon)").length > 0 ? $("abbr+ a:has(.icon)").attr("href").split("/")[4] : 0;
    if (location.pathname.includes("/u/")) {
        if (location.pathname.includes("/o/")) {
            if (location.pathname.includes(myuid) && $("input[value='Completed Offer']").length > 0) {
                $("input[value='Completed Offer']").click(function(event) {
                    event.preventDefault();
                    var steamid = $("a[href*='/steamcommunity.com/profiles/']:not([href*=" + mysid + "])").attr("href").split("/")[4];
                    var name = $("th:contains('Next Step')+ > a[href*='/steamcommunity.com/profiles/']").text().replace(" on Steam", "");
                    var msg = "+REP " + name + " is an amazing trader, recommended! We successfully completed this trade: " + location.href + ". Thanks a lot for the trade!";
                    leaveFeedback(this, msg, steamid);
                });
            } else if (location.pathname.includes(myuid) && $(".activity").length > 0) {
                $(".activity").first().before(`<br>
                        <strong>Or...  </strong>
                        <input id="automatedoffer" type="submit" value="Begin Automated Offer"></input>
                        <input id="removeowned" type="submit" value="Remove Owned Games From Pending Offers" style="float:right;"></input>`);
                $("#automatedoffer").click(setupAutomatedOffer);
                $("#removeowned").click(confirmRemoveOwned);
                $(".showMoreArea").after(`<p>
                        <label for="offersearch">
                            <strong>Filter: </strong>
                        </label>
                        <input id="offersearch" type="text" placeholder="Search in displayed offers..." style="width: 260px;">
                        <input id="canceloffers" type="submit" value="Cancel Offers">
                        <input id="messageoffers" type="submit" value="Message Offers">
                        <input id="extendoffers" type="submit" value="Extend Expiration">
                    </p>`);
                $("#offersearch").on("change keyup paste", function() {
                    var val = $(this).val();
                    $("#offers tr").each(function() {
                        if ($(this).text().includes(val)) {
                            $(this).show();
                        } else {
                            $(this).hide();
                        }
                    });
                });
                $("#canceloffers").click(cancelOffers);
                $("#messageoffers").click(messageOffers);
                $("#extendoffers").click(extendExpiry);
            }
            [3, 7].forEach(function(i) {
                $("#exchanges > fieldset:nth-child(" + i + ")").append('<input type="submit" value="Invert all checkboxes" style="width:100%;height:2em;margin-bottom:.7em;" id="checkall' + i + '">');
                $("#checkall" + i).on("click", function() {
                    $("#exchanges > fieldset:nth-child(" + i + ") input[type=checkbox]").each(function() {
                        this.checked = !this.checked;
                    });
                    return false;
                });
                $("#exchanges > fieldset:nth-child(" + i + ")").append('<input type="submit" value="Enable disabled tradables" style="width:100%;height:2em;margin-bottom:.7em;" id="enable' + i + '">');
                $("#enable" + i).on("click", function(event) {
                    event.preventDefault();
                    if (confirm("Are you sure you want to enable disabled tradables? Make sure the other party is okay with this!")) {
                        setTimeout(function() {
                            $("#exchanges > fieldset:nth-child(" + i + ") input[disabled]").each(function() {
                                this.removeAttribute("disabled");
                                this.removeAttribute("title");
                                this.name = "add_to_offer_" + (i > 3 ? "2" : "1") + "[]";
                                this.id = $(this).find("+").attr("for");
                                this.value = $(this).parent().find("a").attr("href").split("/")[4] + "," + $(this).find("+").attr("for").replace("edit", "");
                            });
                        }, 0);
                    }
                    $("#enable").hide();
                });
            });
        }
    }
}

function initSteamTrades() {
    var steamid = getURIParam("steamid");
    var msg = applySentenceCase(getURIParam("message").replace(/\+/g, ' '));
    var profile_id = $("[name=profile_id]").val();
    var xsrf_token = $("[name=xsrf_token]").val();
    if (profile_id !== undefined) {
        var data = {
            do: "review_insert",
            xsrf_token: xsrf_token,
            profile_id: profile_id,
            rating: 1,
            description: msg
        };
        $.ajax({
            url: "https://www.steamtrades.com/ajax.php",
            method: "POST",
            data: data,
            success: function() {
                unsafeWindow.close();
            }
        });
    } else {
        console.log("Already left feedback previously");
        unsafeWindow.close();
    }
}

function handleRequests() {
    if (requests.length > 0) {
        if (requests.length === 1) {
            setTimeout(function() {
                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.");
        var request = requests.shift();
        if (typeof request === "function") {
            request();
        }
    }
}

function syncLibrary(callback) {
    $.post("/u/" + myuid + "/l/", {
        sync_list: "↻ Sync List",
        type: 1
    });
    var v = parseInt(GM_getValue("dsudv", 1));
    v++;
    GM_setValue("dsudv", v);
    GM_xmlhttpRequest({
        method: "GET",
        url: "http://store.steampowered.com/dynamicstore/userdata/?v=" + v,
        onload: function(response) {
            var json = JSON.parse(response.responseText);
            var ownedApps = json.rgOwnedApps;
            $.post("/u/" + myuid + "/l/e/", {
                bulk_AppIDs: ownedApps.join(","),
                add_AppIDs: "+ Add AppIDs",
                action: "Edit",
                change_attempted: 1,
                add_from: "AppIDs"
            }, function() {
                if (callback) callback();
            });
        }
    });
}

function fixIgnored() {
    var y = $.unique(GM_getValue("ignored").split(","));
    y.splice($.inArray(myuid, y), 1);
    GM_setValue("ignored", y.join(","));
}

function showIgnored() {
    fixIgnored();
    console.log(GM_getValue("ignored").slice(0, -1).split(","));
}

function findIgnored() {
    $.get("/u/" + myuid + "/o/", function(data) {
        console.log("fdlkdsklfkld");
        data = data.replace(/src="[^"]*"/ig, "");
        var users = [];
        $("[href*='/u/']", data).each(function() {
            let url = this.href;
            if (/^https?:\/\/barter\.vg\/u\/[a-zA-Z0-9]{1,5}\/$/.test(url) && $.inArray(url, users) == -1) {
                $.get(url, function(data) {
                    data = data.replace(/src="[^"]*"/ig, "");
                    var ignored = $.unique(GM_getValue("ignored", "").split(","));
                    if ($("form[action*='/o/']", data).length === 0 && $.inArray(url.split("/")[4], ignored) == -1 && url.split("/")[4] !== myuid) {
                        GM_setValue("ignored", ignored.join(",") + url.split("/")[4] + ",");
                    }
                });
            }
        });
        fixIgnored();
    });
}

function findAllIgnored() {
    $.getJSON("/u/json/", function(json) {
        for (var uid in json) {
            if (json[uid].active > -1) {
                isIgnored("/u/" + uid + "/");
            }
        }
    });
}

function isIgnored(url) {
    $.get(url, function(data) {
        data = data.replace(/src="[^"]*"/ig, "");
        var ignored = $.unique(GM_getValue("ignored", "").split(","));
        if ($("form[action*='/o/']", data).length === 0 && $.inArray(url.split("/")[4], ignored) == -1 && url.split("/")[4] !== myuid) {
            GM_setValue("ignored", ignored.join(",") + url.split("/")[4] + ",");
        }
    });
}

function remindPendingOffers() {
    var reminder = "Hey, I saw that you looked at the offer. Would you please be so kind and respond to it as well? I'd appreciate that very much! Thank you!";
    $("tr.active [title*=Opened]+:contains('pending')").each(function() {
        $.post(this.href, {
            offer_message: reminder,
            offer_setup: 3
        });
    });
}

function remindCompletedOffers() {
    var steammsg = "Hello, please mark our trade as complete on barter. I would appreciate that! Thank you!";
    var bartermsg = "Hello, please mark our trade as complete. I would appreciate that! Thank you!";
    var steamids = [];
    var ajaxDone = 0;
    $.post("/u/" + myuid + "/o/", {
        filter_by_status: "completed"
    }, function(data) {
        data = data.replace(/src="[^"]*"/ig, "");
        var count = $(".active:contains('completed'):not(.completed)", data).length;
        $($(".active:contains('completed'):not(.completed)", data).get().reverse()).each(function() {
            let url = $(this).find("td+ td a").attr("href");
            $.post(url, {
                offer_message: bartermsg,
                offer_setup: 3
            });
        });
    });
}

function leaveFeedback(elem, msg, steamid) {
    $(elem).val("Completing trade, sending feedback and syncing library...").attr("disabled", true);
    $.post($(elem).parents("form").attr("action"), $(elem).parents("form").serializeObject());
    var steamids = [steamid];
    //postSteamComment(msg, steamids);
    postSteamTradesComment(msg, steamid);
    setPostTradeClipboard();
    syncLibrary(function() {
        location.reload();
    });
}

function setPostTradeClipboard() {
    var uids = [];
    $("#offerHeader > tbody > tr > td:nth-child(2) > strong > a").each(function() {
        uids.push(this.href.split("/")[4]);
    });
    uids.splice($.inArray(myuid, uids), 1);
    var uid = uids[0];
    var tradelink = location.href.split(myuid)[0] + uid + location.href.split(myuid)[1];
    var sgprofile = "https://www.steamtrades.com/user/" + mysid;
    var 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" + tradelink + "\n" + sgprofile;
    GM_setClipboard(msg);
}

function postSteamTradesComment(msg, steamid) {
    window.open("https://www.steamtrades.com/user/" + steamid + "?do=postcomments&message=" + msg, "_blank");
}

function setupAutomatedOffer() {
    var settings = {};
    settings.offering_titles = prompt("Offering (max ~= 6 games):", "GameTitle1, GameTitle2, ...").split(",").map(Function.prototype.call, String.prototype.trim);
    settings.offering_to_group = prompt("Make offers to group:", "wishlist, unowned").split(",").map(Function.prototype.call, String.prototype.trim); //tradable, wishlist, unowned (, library, blacklist)
    settings.max_offers = prompt("Maximum number of trade offers you want to send (no limit = all):", "all");
    settings.want_from_group = prompt("I want games/DLC from group:", "wishlist, unowned").split(",").map(Function.prototype.call, String.prototype.trim); //tradable, wishlist, unowned, library
    settings.max_want = prompt("Maximum number of games/DLC I want to ask (no limit = all):", "25");
    settings.ratio = prompt("Ratio of offering (mine) against want (yours) (all = all):", "1:1");
    settings.expire_days = parseInt(prompt("Days until offer expires (max = 15):", "15"));
    settings.trading_cards_only = confirm("Do you only want games with trading cards? (cancel = no)");
    settings.unbundled_only = confirm("Do you only want unbundled games/DLC? (cancel = no)");
    settings.exclude_givenaway = confirm("Do you want to exclude given away (free) games? (cancel = no)");
    settings.min_rating = parseInt(prompt("Minimum game/DLC rating (unrated games/DLC will be included):", "0"));
    if (settings.min_rating === null || isNaN(settings.min_rating) || !isFinite(settings.min_rating)) {
        settings.min_rating = false;
    }
    settings.exclude_title_containing = prompt("Exclude games/DLC containing the term(s):", "DLC, OST, pack, soundtrack");
    if (settings.exclude_title_containing !== null && settings.exclude_title_containing !== "") {
        settings.exclude_title_containing = settings.exclude_title_containing.split(",").map(Function.prototype.call, String.prototype.trim);
    } else {
        settings.exclude_title_containing = false;
    }
    settings.message = prompt("Message included in each offer (blank = no message):", "");
    if (confirm("Are you sure you want to send this automated offer?\nBeware that this may cause traders to add you to their ignore list!")) {
        $("#automatedoffer").prop("disabled", true).val("Sending mass offers...");
        console.log("Sending mass offers...", settings);
        alert("Initiated process of sending automated offers!\nCheck the javascript console (F12) and network tab for details.\nPlease don't close this tab until all offers have finished sending.\nThis may take a few minutes!");
        massSendOffers(settings);
    }
}

function massSendOffers(settings) {
    if (settings.offering_titles.length === 0) return alert("No argument \"offering_titles\". Example: \"The Forest\"");
    settings.offering_titles = settings.offering_titles ? new RegExp(settings.offering_titles.join("|"), "gi") : new RegExp(".^");
    $.getJSON("/u/" + myuid + "/t/json/", function(mytradables) {
        var offering = [];
        var ato1 = [];
        for (var platformid in mytradables.by_platform) {
            for (var tradeid in mytradables.by_platform[platformid]) {
                let tradable = mytradables.by_platform[platformid][tradeid];
                if (containsInTradableTitle(tradable, settings.offering_titles)) {
                    console.log("Tradable:", tradable);
                    tradable.users = [];
                    offering.push(tradable);
                    ato1.push(tradable.item_id + "," + tradable.line_id);
                }
            }
        }
        if (offering.length === 0) return alert("Could not find tradable(s)!\nPlease provide a more accurate name of your tradable and make sure this item is in your tradables list.");
        syncLibrary(function() {
            GM_xmlhttpRequest({
                method: "GET",
                url: "https://royalgamer06.ga/barter/json.php",
                onload: function(response) {
                    var optins = JSON.parse(response.responseText);
                    /*
                    TODO:
                    - Add platform exclusion support
                    - Add tag exclusion support
                    - Add tradable-wishlist-settings.ratio support
                    - Add counter preference support
                    - Add custom message support
                    - Add better UI
                    - Don't include owned games in multi-offers
                    */

                    //SETUP
                    settings.offering_to_group = settings.offering_to_group ? settings.offering_to_group : ["wishlist"]; //tradable, wishlist, unowned, library, blacklist
                    if (settings.offering_to_group.includes("unowned") && !settings.offering_to_group.includes("wishlist")) {
                        settings.offering_to_group.push("wishlist"); // All users in wishlist belong to unowned
                    }
                    settings.max_offers = settings.max_offers ? settings.max_offers : Number.MAX_SAFE_INTEGER;
                    settings.max_offers = parseInt(settings.max_offers.replace(/all/gi, Number.MAX_SAFE_INTEGER));
                    settings.want_from_group = settings.want_from_group ? settings.want_from_group.filter(function(group) {
                        return !!group.toLowerCase().match(/^(tradable|wishlist|unowned|library)$/g);
                    }) : ["wishlist"]; //tradable, wishlist, library, blacklist, unowned
                    settings.max_want = settings.max_want ? settings.max_want : Number.MAX_SAFE_INTEGER;
                    settings.max_want = parseInt(settings.max_want.replace(/all/gi, Number.MAX_SAFE_INTEGER));
                    settings.ratio = settings.ratio ? settings.ratio.toLowerCase().trim() : "all:1";
                    settings.ratio = settings.ratio.replace(/all/gi, "0").split(":");
                    settings.ratio[0] = parseInt(settings.ratio[0]);
                    settings.ratio[1] = parseInt(settings.ratio[1]);
                    settings.ratio[0] = Math.min(settings.ratio[0], offering.length);
                    settings.expire_days = settings.expire_days ? settings.expire_days : 1000;
                    settings.trading_cards_only = typeof settings.trading_cards_only !== "undefined" ? settings.trading_cards_only : false;
                    settings.unbundled_only = typeof settings.unbundled_only !== "undefined" ? settings.unbundled_only : false;
                    settings.exclude_givenaway = typeof settings.exclude_givenaway !== "undefined" ? settings.exclude_givenaway : false;
                    //exclude_combined = typeof exclude_combined !== "undefined" ? exclude_combined : false;
                    settings.min_rating = settings.min_rating ? settings.min_rating : 0;
                    settings.exclude_title_containing = settings.exclude_title_containing ? new RegExp(settings.exclude_title_containing.join("|"), "gi") : new RegExp(".^");
                    settings.message = settings.message ? settings.message : "";
                    var done = 0;
                    offering.forEach(function(o, i) {
                        let index = i;
                        $.getJSON("/i/" + o.item_id + "/json2/", function(gameinfo) {
                            //EXECUTION
                            shuffle(settings.offering_to_group).forEach(function(group) {
                                group = group.toLowerCase();
                                if (gameinfo.users[group]) {
                                    var users = shuffle(Object.keys(gameinfo.users[group]));
                                    users.forEach(function(userid) {
                                        let uid = parseInt(userid);
                                        let user = gameinfo.users[group][uid];
                                        console.log(user,
                                            (optins.hasOwnProperty(user.steam_id64_string) ? optins[user.steam_id64_string] : true),
                                            user.tradeable_count >= parseInt(settings.ratio[1]),
                                            //user.wants_rating <= o.rating ,
                                            (user.wants_unowned === 0 ? group === "wishlist" : true),
                                            (user.wants_cards === 1 ? o.cards > 0 : true),
                                            (user.avoid_givenaway === 1 ? o.givenaway === 0 : true),
                                            (user.avoid_bundles === 1 ? o.bundles_available === 0 : true));
                                        if ((optins.hasOwnProperty(user.steam_id64_string) ? optins[user.steam_id64_string] : true) &&
                                            user.tradeable_count >= parseInt(settings.ratio[1]) &&
                                            //user.wants_rating <= o.rating &&
                                            (user.wants_unowned === 0 ? group === "wishlist" : true) &&
                                            (user.wants_cards === 1 ? o.cards > 0 : true) &&
                                            (user.avoid_givenaway === 1 ? o.givenaway === 0 : true) &&
                                            (user.avoid_bundles === 1 ? o.bundles_available === 0 : true)) {
                                            offering[index].users.push(uid);
                                        }
                                    });
                                }
                            });
                            done++;
                            if (done >= offering.length) {
                                var users = [];
                                console.log(settings.ratio[0], offering.length, settings.ratio[0] === 0 || settings.ratio[0] >= offering.length);
                                if (settings.ratio[0] === 0 || settings.ratio[0] >= offering.length) {
                                    console.log("intersect");
                                    users = offering[0].users;
                                    offering.forEach(function(o, i) {
                                        if (i !== 0) users = $(users).filter(o.users).get();
                                    });
                                } else {
                                    console.log("union");
                                    offering.forEach(function(o) {
                                        console.log(users, o.users);
                                        users = users.concat(o.users);
                                    });
                                }
                                users = [...new Set(users)]; //unique
                                console.log(users);
                                var trade_count = 0;
                                shuffle(users).forEach(function(uid) {
                                    if (trade_count <= settings.max_offers) {
                                        $.getJSON("/u/" + uid.toString(16) + "/t/f/" + myuid + "/json/", function(tradable_groups) {
                                            if (trade_count <= settings.max_offers) {
                                                $.getJSON("/u/" + uid.toString(16) + "/t/json/", function(t) {
                                                    var ato2 = [];
                                                    var tradables = {};
                                                    for (platformid in t.by_platform) {
                                                        // add platform restrictions here
                                                        tradables = $.extend(tradables, t.by_platform[platformid]);
                                                    }
                                                    var tradeids = shuffle(Object.keys(tradables));
                                                    tradeids.forEach(function(tradeid) {
                                                        let tradable = tradables[tradeid];
                                                        if (ato2.length < settings.max_want &&
                                                            tradable.extra > 0 &&
                                                            isInWantGroup(tradable_groups, settings.want_from_group, tradable.item_id) &&
                                                            !containsInTradableTitle(tradable, settings.exclude_title_containing) &&
                                                            (settings.trading_cards_only ? tradable.cards > 0 : true) &&
                                                            (settings.unbundled_only ? tradable.bundles_all === 0 : true) &&
                                                            (settings.exclude_givenaway ? tradable.givenaway === 0 : true) &&
                                                            (tradable.reviews_positive ? tradable.reviews_positive > settings.min_rating : true)) {
                                                            ato2.push(tradable.item_id + "," + tradeid);
                                                        }
                                                    });
                                                    if (ato2.length >= parseInt(settings.ratio[1]) && trade_count <= settings.max_offers) {
                                                        trade_count++;
                                                        var trade_data = {
                                                            "app_id": 2,
                                                            "app_version": versionToInteger(GM_info.script.version),
                                                            "to": uid,
                                                            "from_and_or": settings.ratio[0],
                                                            "to_and_or": settings.ratio[1],
                                                            "expire": settings.expire_days,
                                                            "counter_preference": 1,
                                                            "add_to_offer_from[]": ato1,
                                                            "add_to_offer_to[]": ato2,
                                                            "message": settings.message
                                                        };
                                                        console.log(uid, trade_data, trade_count);
                                                        $.post("/u/" + myuid + "/o/json/", trade_data);
                                                    }
                                                });
                                            }
                                        });
                                    }
                                });
                            }
                        });
                    });
                }
            });
        });
    });
}

function containsInTradableTitle(tradable, pattern) {
    return (tradable.title ? !!tradable.title.match(pattern) : false) ||
        (tradable.title_alt ? !!tradable.title_alt.match(pattern) : false) ||
        (tradable.title_formatted ? !!tradable.title_formatted.match(pattern) : false) ||
        (tradable.title_extra ? !!tradable.title_extra.match(pattern) : false);
}

function isInWantGroup(tradable_groups, want_from_group, item_id) {
    var isInWantGroup = false;
    want_from_group.forEach(function(group) {
        if ($.inArray(item_id, tradable_groups[group]) > -1) {
            isInWantGroup = true;
        }
    });
    return isInWantGroup;
}

function massSendMutualOffers(ratio, expire_days, trade_game, only_offer_trade_game) {
    ratio = ratio ? ratio : "all:1";
    ratio = ratio.replace(/all/g, "0").split(":");
    expire_days = expire_days ? expire_days : 1000;
    trade_game = trade_game ? trade_game : 0;
    only_offer_trade_game = only_offer_trade_game ? only_offer_trade_game : false;
    syncLibrary();
    console.log(ratio, expire_days, trade_game, only_offer_trade_game);
    $.post("/u/" + myuid + "/t/m/?", {
        tradeGame: trade_game,
        tradeUser: -2,
        expandAll: "on",
        openNewTab: "on",
        findMatches: "Mutual Matches"
    }, function(data) {
        data = data.replace(/src="[^"]*"/ig, "");
        console.log($("#mutualMatches tr:has([name=offer_setup])", data).zip("#mutualMatches tr:has([href*='/i/'])", data));
        $("#mutualMatches tr:has([name=offer_setup])", data).zip($("#mutualMatches tr:has([href*='/i/'])", data)).each(function() {
            console.log(this);
            let form = $(this).find("form:has([name=offer_setup])");
            console.log(form);
            let offering = [];
            if (only_offer_trade_game) {
                offering.push(trade_game);
            } else {
                $(this).find(".mh > a").each(function() {
                    offering.push(this.href.split("/")[4]);
                });
            }
            console.log(offering);
            let want = [];
            $(this).find(".mw > a").each(function() {
                want.push(this.href.split("/")[4]);
            });
            console.log(want);
            console.log($(form).attr("action"));
            console.log($(form).serializeObject());
            $.post($(form).attr("action"), $(form).serializeObject(), function(data) {
                data = data.replace(/src="[^"]*"/ig, "");
                let url = $("#offerForm", data).attr("action").split("#")[0];
                console.log(url);
                let ato1 = [];
                $(offering).each(function() {
                    ato1.push($("#exchanges > fieldset:nth-child(3) input[value*='" + this + ",']", data).val());
                });
                console.log(ato1);
                let ato2 = [];
                $(want).each(function() {
                    ato2.push($("#exchanges > fieldset:nth-child(7) input[value*='" + this + ",']", data).val());
                });
                console.log(ato2);
                $.post(url, {
                    offer_setup: [3, 2, 2],
                    "add_to_offer_1[]": ato1,
                    "add_to_offer_2[]": ato2,
                    add_to_offer: "+ Add to Offer"
                }, function() {
                    $.post(url, {
                        offer_setup: [3, 2, 2, 3],
                        from_and_or: parseInt(ratio[0]),
                        to_and_or: parseInt(ratio[1]),
                        expire_days: expire_days,
                        counter_preference: 1,
                        propose_offer: "Finish and Propose Offer"
                    });
                });
            });
        });
    });
}


function messageOffers() {
    var message = prompt("Message:");
    $("#offers tr:visible").each(function() {
        var url = $(this).find("abbr+ a[href*='/u/" + myuid + "/o/']").attr("href");
        $.post(url, {
            offer_message: message,
            offer_setup: 3
        });
    });
}

function cancelOffers(searchquery) {
    if (!searchquery) {
        if (confirm("Are you sure you want to cancel all trade offers displayed below?")) {
            $("#offers tr:visible").each(function() {
                $.post($(this).find("abbr+ a[href*='/u/" + myuid + "/o/']").attr("href"), {
                    offer_setup: 3,
                    cancel_offer: "☒ Cancel Offer"
                });
            });
        }
    } else {
        $.get("/u/" + myuid + "/o/", function(data) {
            data = data.replace(/src="[^"]*"/ig, "");
            $("tr:has(:contains('" + searchquery + "'))", data).each(function() {
                $.post($(this).find("abbr+ a[href*='/u/" + myuid + "/o/']").attr("href"), {
                    offer_setup: 3,
                    cancel_offer: "☒ Cancel Offer"
                });
            });
        });
    }
}

function extendExpiry(expire_days) { //Old solution: SHOULD UPDATE THIS!
    if (!expire_days) {
        expire_days = parseInt(prompt("Expiration:", "15"));
        $("#offers tr:visible").each(function() {
            var url = $(this).find("abbr+ a[href*='/u/" + myuid + "/o/']").attr("href");
            $.post(url, {
                offer_setup: 3,
                edit_offer: "✐ Edit Offer"
            }, function(data) {
                data = data.replace(/src="[^"]*"/ig, "");
                var formdata = $("[name=propose_offer]", data).parents("form").serializeObject();
                formdata.expire_days = expire_days;
                formdata.propose_offer = "Finish and Propose Offer";
                $.post(url, formdata);
            });
        });
    } else {
        $.post("/u/" + myuid + "/o/", {
            filter_by_status: "pending"
        }, function(data) {
            data = data.replace(/src="[^"]*"/ig, "");
            $(".active a:contains('pending')", data).each(function() {
                let url = this.href;
                $.post(url, {
                    offer_setup: 3,
                    edit_offer: "✐ Edit Offer"
                }, function(data) {
                    data = data.replace(/src="[^"]*"/ig, "");
                    var formdata = $("[name=propose_offer]", data).parents("form").serializeObject();
                    formdata.expire_days = expire_days;
                    formdata.propose_offer = "Finish and Propose Offer";
                    $.post(url, formdata);
                });
            });
        });
    }
}

function confirmRemoveOwned() {
    if (confirm("Are you sure you want to remove owned games from pending offers?")) {
        removeOwnedGamesFromPending();
        alert("Initiated process of removing owned games from pending offers!\nCheck the javascript console (F12) for details.\nPlease don't close this tab until all offers have finished updating.");
    }
}

function removeOwnedGamesFromPending() {
    syncLibrary(function() {
        $.post("/u/" + myuid + "/o/", {
            filter_by_status: "pending"
        }, function(data) {
            data = data.replace(/src="[^"]*"/ig, "");
            $(".active a:contains('pending')", data).each(function() {
                let url = this.href;
                $.post(url, {
                    offer_setup: 3,
                    edit_offer: "✐ Edit Offer"
                }, function(data) {
                    data = data.replace(/src="[^"]*"/ig, "");
                    var formdata = $("[name=propose_offer]", data).parents("form").serializeObject();
                    to_remove = [];
                    $("div+ .tradables_items_list li:nth-child(1) , .bold+ li", data).each(function() {
                        if ($(this).find("[alt*=avatar]:first").nextUntil("[alt*=avatar]").text().includes("library")) {
                            to_remove.push($(this).find("[name='checked[]']").val());
                        }
                    });
                    if (to_remove.length > 0) {
                        if ($("div+ .tradables_items_list li:nth-child(1) , .bold+ li", data).length <= to_remove.length) {
                            $.post(url, {
                                offer_setup: 3,
                                cancel_offer: "☒ Cancel Offer"
                            });
                        } else {
                            formdata.remove_offer_items = "➖ Remove Selected Tradables";
                            formdata["checked[]"] = to_remove;
                            $.post(url, formdata, function() {
                                formdata.propose_offer = "Finish and Propose Offer";
                                $.post(url, formdata);
                            });
                        }
                    } else {
                        formdata.propose_offer = "Finish and Propose Offer";
                    }
                });
            });
        });
    });
}


function ajaxify() {
    $("form").on("submit", formSubmitted);
    $("[type=submit]").click(submitClicked);
}

function submitClicked(event) {
    $("input[type=submit]", $(event.target).parents("form")).removeAttr("clicked");
    $(event.target).attr("clicked", "true");
}

function formSubmitted(event) {
    event.preventDefault();
    var form = event.target;
    var action = $(form).attr("action");
    var data = $(form).serializeObject();
    var submit = $("[type=submit][clicked=true]", form);
    data[submit.attr("name")] = submit.attr("value");
    submit.css("cursor", "not-allowed").prop("disabled", true);
    $.post(action, data, parseHtml);
}

function parseHtml(html) {
    document.documentElement.innerHTML = html;
    initBarter();
}

function serializeObject() {
    var o = {};
    var a = this.serializeArray();
    $.each(a, function() {
        if (o[this.name] !== undefined) {
            if (!o[this.name].push) {
                o[this.name] = [o[this.name]];
            }
            o[this.name].push(this.value || "");
        } else {
            o[this.name] = this.value || "";
        }
    });
    return o;
}

function zip(s) {
    var o = $(s);
    return this.map(function(i, e) {
        return $(e).add($(o[i]));
    });
}

function shuffle(array) {
    var 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 getURIParam(sParam) {
    var sPageURL = decodeURIComponent(window.location.search.substring(1)),
        sURLVariables = sPageURL.split("&"),
        sParameterName,
        i;
    for (i = 0; i < sURLVariables.length; i++) {
        sParameterName = sURLVariables[i].split("=");
        if (sParameterName[0].toLowerCase() === sParam) {
            return sParameterName[1] === undefined ? true : sParameterName[1];
        }
    }
    return false;
}

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

function applySentenceCase(str) {
    return str.replace(/.+?[\.\?\!](\s|$)/g, function(txt) {
        return txt.charAt(0).toUpperCase() + txt.substr(1);
    });
}

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