Subeta Sorting

This script sorts NPC shops prices by profit.

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         Subeta Sorting
// @namespace    http://artees.pw/
// @description  This script sorts NPC shops prices by profit.
// @version      1.8
// @author       Artees
// @match        *://subeta.net/shop.php?shop*
// @require      https://greasyfork.org/scripts/30529-subeta-searching/code/Subeta%20Searching.js?version=203734
// @grant        none
// @icon         https://subeta.net/favicon.ico
// @icon64       https://img.subeta.net/items/bottle_apothecary_06.gif
// ==/UserScript==

const KEY_MIN_PROFIT = "minProfit",
    KEY_MAX_ITEMS = "maxItems",
    KEY_BUYING = "buying",
    KEY_SHOP_URL = "shopUrl";

var curItem = parseItemsForSorting(),
    sortedItems = [],
    minProfit = getFromStorage(localStorage, KEY_MIN_PROFIT, 0),
    maxItems = getFromStorage(localStorage, KEY_MAX_ITEMS, 10),
    buyCountdown = null,
    isBuying = getFromStorage(localStorage, KEY_BUYING, false),
    buyingIntervalId = 0,
    buyingTimeoutId = 0,
    isSortingComplete = false,
    pricedCount = 0;

function parseItemsForSorting() {
    var itemDivs = document.getElementsByClassName("two wide column");
    if (itemDivs.length === 0 || itemDivs[0].getElementsByTagName("form").length === 0) return null;
    function getPriceField(itemDiv) {
        return new PriceField(itemDiv);
    }
    return parseItems(itemDivs, getPriceField);
}

function Item(itemDiv, priceField) {
    var start = itemDiv.innerHTML.indexOf("<br>"),
        end = itemDiv.innerHTML.indexOf("<br>", start + 4);
    this.itemDiv = itemDiv;
    this.name = replaceAll(itemDiv.innerHTML.substring(start + 4, end), "	", "");
    this.name = replaceAll(this.name, "\n", "");
    this.cacheKey = "c " + this.name;
    this.next = null;
    this.isPriced = false;
    this.priceField = priceField;
    this.resetPrice = function () {
        var end = this.priceField.value.indexOf(CY) + CY.length;
        return this.priceField.value.substring(0, end);
    };
    this.priceField.value = this.resetPrice();
    this.getPrice = function () {
        var priceString = replaceAll(this.resetPrice(), ",", "");
        return parseInt(priceString);
    };
    this.actualPrice = this.getPrice();
    var isComplete = true;
    this.onSelect = function() {
        isComplete = true;
        forEachItem(getNext);
        if (isComplete) {
            isSortingComplete = true;
            switchBuying();
        }
        return !isComplete;
    };
    function getNext(i) {
        if (i === null || !i.isPriced) {
            curItem = i;
            isComplete = false;
            return false;
        }
        return true;
    }
}

function PriceField(itemDiv) {
    var label = itemDiv.getElementsByTagName("b")[0];
    return {
        get value() {
            return label.innerText;
        },
        set value(value) {
            label.innerText = value;
        },
        get style() {
            return label.style;
        },
        set style(value) {
            label.style = value;
        }
    };
}

createUI();

function createUI() {
    var s = document.getElementsByClassName("shop_item_container")[0];
    if (s === undefined) {
        goBackAfterBuying();
        return;
    }
    var ui = s.insertBefore(document.createElement("div"), s.firstChild);
    ui.appendChild(document.createTextNode("Delay: "));
    var itemDelayInput = document.createElement("INPUT");
    itemDelayInput.value = itemDelay + " sec.";
    itemDelayInput.onchange = function () {
        itemDelay = parseInt(this.value);
        localStorage[KEY_ITEM_DELAY] = itemDelay;
    };
    ui.appendChild(itemDelayInput);
    ui.appendChild(createSpace());
    var button = document.createElement("a");
    button.className = "ui button tiny";
    button.innerText = "Purge Cache";
    button.onclick = function () {
        forEachItem(function (i) {
            localStorage.removeItem(i.cacheKey);
            location.reload();
            return true;
        });
    };
    ui.appendChild(button);
    ui.appendChild(document.createElement("br"));
    var autobuy = document.createElement("input");
    autobuy.type = "checkbox";
    autobuy.checked = isBuying;
    autobuy.onchange = function () {
        isBuying = this.checked;
        localStorage[KEY_BUYING] = isBuying;
        switchBuying();
    };
    ui.appendChild(autobuy);
    ui.appendChild(document.createTextNode(" Autobuy"));
    ui.appendChild(createSpace());
    ui.appendChild(document.createTextNode("Min profit: "));
    var minProfitInput = document.createElement("INPUT");
    minProfitInput.value = minProfit + CY;
    minProfitInput.onchange = function () {
        minProfit = parseInt(this.value);
        localStorage[KEY_MIN_PROFIT] = minProfit;
        switchBuying();
    };
    ui.appendChild(minProfitInput);
    ui.appendChild(createSpace());
    ui.appendChild(document.createTextNode("Number of items: "));
    var maxItemsInput = document.createElement("INPUT");
    maxItemsInput.value = maxItems;
    maxItemsInput.onchange = function () {
        maxItems = parseInt(this.value);
        localStorage[KEY_MAX_ITEMS] = maxItems;
        switchBuying();
    };
    ui.appendChild(maxItemsInput);
    ui.appendChild(createSpace());
    buyCountdown = document.createTextNode("Disabled");
    ui.appendChild(buyCountdown);
}

loadCache();

function loadCache() {
    forEachItem(function (i) {
        if (i === null) return false;
        if (localStorage[i.cacheKey] !== undefined) {
            var profit = getFromStorage(localStorage, i.cacheKey, 0) - i.getPrice();
            setProfit(i, profit);
        }
        return true;
    });
    sortPrices();
}

function setProfit(item, profit) {
    item.priceField.value = profit + CY;
    item.priceField.style.color = profit > 0 ? "green" : "red";
    item.isPriced = true;
    pricedCount++;
}

if (curItem !== null && curItem.onSelect()) {
    search();
}

function filterPrices(prices) {
    if (prices.length > 0) {
        for (var i = 0; i < prices.length; i++) {
            var price = prices[i];
            if (price.isNPC()) continue;
            localStorage[curItem.cacheKey] = price.valueOf();
            var profit = price.valueOf() - curItem.getPrice();
            setProfit(curItem, profit);
            break;
        }
    } else {
        curItem.priceField.value = "(!) " + curItem.resetPrice() + CY;
        curItem.priceField.style.color = "orange";
    }
    sortPrices();
}

function sortPrices() {
    sortedItems = [];
    forEachItem(function (i) {
        if (i === null) return false;
        sortedItems.push(i);
        return true;
    });
    sortedItems.sort(comparePrices);
    function comparePrices(a, b) {
        if (!b.isPriced) return -1e100;
        if (!a.isPriced) return 1e100;
        return b.getPrice() - a.getPrice();
    }
    for (var i = 0; i < sortedItems.length; i++) {
        sortedItems[i].itemDiv.parentNode.appendChild(sortedItems[i].itemDiv);
    }
    switchBuying();
}

function switchBuying() {
    if (buyCountdown === null) return;
    highlightItems();
    function highlightItems() {
        for (var i = 0; i < sortedItems.length; i++) {
            var item = sortedItems[i];
            item.priceField.style.fontWeight =
                (item.isPriced && item.getPrice() < minProfit) ? "normal" : "bold";
        }
    }
    var item = getMostProfitableItem();
    function getMostProfitableItem() {
        var spText = document.getElementsByClassName("widget-login-sp")[0].innerText,
            sp = parseInt(replaceAll(spText, ",", ""));
        for (var i = 0; i < sortedItems.length; i++) {
            if (sortedItems[i].actualPrice <= sp) {
                return sortedItems[i];
            }
        }
        return null;
    }
    if (!isBuying || !isSortingComplete || maxItems <= 0 || item === null ||
        item.getPrice() < minProfit) {
        clearInterval(buyingIntervalId);
        clearTimeout(buyingTimeoutId);
        if (isBuying && isSortingComplete && maxItems > 0 && item !== null) {
            buyCountdown.data = "Go to random shop...";
            goToNextShop();
        }
        else {
            buyCountdown.data =
                (isSortingComplete || !isBuying) ? "Disabled" : getProgress();
        }
        if (item === null) {
            buyCountdown.data = "Not enough sP!";
        }
        return;
    }
    function getProgress() {
        var k = 8,
            p = Math.floor(pricedCount/k),
            l = Math.floor(sortedItems.length/k) - p;
        return "Sorting " + new Array(p).join("█") + new Array(l).join("░");
    }
    var count = 5;
    buyCountdown.data = count.toString();
    buyingIntervalId = setInterval(buyItem, 1000);
    function buyItem() {
        count--;
        buyCountdown.data = count.toString();
        if (count > 0) return;
        clearInterval(buyingIntervalId);
        buyCountdown.data = "Buying...";
        sessionStorage[KEY_SHOP_URL] = location.href;
        buyingTimeoutId = setTimeout(click, 1000);
        function click() {
            maxItems--;
            localStorage[KEY_MAX_ITEMS] = maxItems;
            item.itemDiv.getElementsByClassName("ui image")[0].click();
        }
    }
}

function goToNextShop() {
    var shops = [36, 31, 28, 30, 32, 41, 11, 14, 34, 6, 29, 37, 16, 5, 19, 2, 22, 46, 49, 40,
            24, 45, 26, 23, 12, 44, 27, 39, 42, 9, 21, 4, 17, 20, 47, 25],
        url = location.href.split("="),
        iNext = Math.floor(Math.random()*shops.length),//shops.indexOf(parseInt(url[1])) + 1,
        next = url[0] + "=" + shops[(iNext >= shops.length) ? 0 : iNext].toString();
    open(next, "_self");
}

function goBackAfterBuying() {
    var url = sessionStorage[KEY_SHOP_URL];
    if (url === undefined) return;
    sessionStorage.removeItem(KEY_SHOP_URL);
    open(url, "_self");
}

onComplete();

function onComplete() {
    if (document.body.textContent.indexOf("There are no items stocked") > -1) {
        goToNextShop();
    }
}