Torn Price Filler [Market+Bazaar] by Rosti powered by Weav3r.dev

On "Fill" click on the Item Market or Bazaar, autofills the price with the lowest market price minus $1 (customizable), and shows a price popup.

当前为 2025-07-07 提交的版本,查看 最新版本

// ==UserScript==
// @name         Torn Price Filler [Market+Bazaar] by Rosti powered by Weav3r.dev
// @namespace    https://github.com/Rosti-dev
// @version      0.9.6
// @description  On "Fill" click on the Item Market or Bazaar, autofills the price with the lowest market price minus $1 (customizable), and shows a price popup.
// @author       Rosti
// @license      MIT License
// @match        https://www.torn.com/page.php?sid=ItemMarket*
// @match        https://www.torn.com/bazaar.php*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=torn.com
// @connect      weav3r.dev
// @require      https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js
// @run-at       document-idle
// @grant        GM_addStyle
// @grant        GM_registerMenuCommand
// @grant        GM.xmlHttpRequest
// @grant        GM_setValue
// @grant        GM_getValue
// ==/UserScript==

//Credits:
// -Silmaril [2665762] for the Bazaar filler script and the Item Market Price filler (MIT License)
// -Weav3r for providing the Bazaar lisitings API (Without API keys)
// -Chedburn for absolutely making the user experience worse by a huge margin due to his "vision" forcing people to use scripts or burn 5% of every purchase.

(async function() {
    'use strict';

    // --- SHARED CONFIGURATION AND STATE ---
    const itemUrl = "https://api.torn.com/torn/{itemId}?selections=items&key={apiKey}&comment=PriceFiller";
    const marketUrlV2 = "https://api.torn.com/v2/market?id={itemId}&selections=itemMarket&key={apiKey}&comment=PriceFiller";
    const bazaarUrl = "https://weav3r.dev/api/marketplace/{itemId}";

    let priceDeltaRaw = GM_getValue("rosti-torn-price-filler-price-delta", '-1[0]');
    let apiKey; // Will be set by checkApiKey() before use
    let showPricesPopup = GM_getValue("rosti-torn-price-filler-show-prices-popup", true);
    let keepPopupOpen = localStorage.getItem("rosti-torn-price-filler-keep-popup-open") === 'true';

    let recentFilledInput = null;
    let popupOffsetX = localStorage.getItem("rosti-torn-price-filler-popup-offset-x") ?? 0;
    let popupOffsetY = 0;
    let isDragging = false;
    let startX, startY;
    let closePopupTimer = null;
    let refreshInterval = null;

    const LOADING_THE_PRICES = 'Loading the prices...';

    // --- SHARED UTILITIES ---

    const RateLimiter = {
        requestQueue: [],
        maxRequests: 60,
        interval: 60 * 1000,
        async processRequest(requestFn) {
            const now = Date.now();
            this.requestQueue = this.requestQueue.filter(timestamp => now - timestamp < this.interval);
            if (this.requestQueue.length >= this.maxRequests) {
                const oldestRequest = this.requestQueue[0];
                const timeToWait = this.interval - (now - oldestRequest);
                console.warn(`[PriceFiller] Rate limit reached. Waiting ${timeToWait}ms.`);
                await new Promise(resolve => setTimeout(resolve, timeToWait + 100));
                return this.processRequest(requestFn);
            }
            this.requestQueue.push(Date.now());
            return requestFn();
        }
    };

    function GM_addStyle_TornPDA(s) {
        let style = document.createElement("style");
        style.type = "text/css";
        style.innerHTML = s;
        document.head.appendChild(style);
    };

    class StringBuilder {
        constructor() { this.parts = []; }
        append(str) { this.parts.push(str); return this; }
        toString() { return this.parts.join(''); }
    }

    function formatNumberWithCommas(number) {
        return new Intl.NumberFormat('en-US').format(number);
    }

    function performOperation(number, operation) {
        const match = operation.match(/^([-+]?)(\d+(?:\.\d+)?)(%)?$/);
        if (!match) throw new Error('Invalid operation string');
        const [, operator, operand, isPercentage] = match;
        const operandValue = parseFloat(operand);
        const adjustedOperand = isPercentage ? (number * operandValue) / 100 : operandValue;
        switch (operator) {
            case '+': return number + adjustedOperand;
            case '-': return number - adjustedOperand;
            default: return number + adjustedOperand;
        }
    }

    function findParentByCondition(element, conditionFn) {
        let currentElement = element;
        while (currentElement) {
            if (conditionFn(currentElement)) return currentElement;
            currentElement = currentElement.parentElement;
        }
        return null;
    }

    function getApiKey() {
        const keySources = [
            'tornpda_api_key',
            'rosti-torn-price-filler'
        ];
        for (const key of keySources) {
            const value = localStorage.getItem(key);
            if (value) return value;
        }
        return null;
    }

    function checkApiKey() {
        apiKey = getApiKey();
        if (!apiKey || apiKey.length !== 16) {
            let userInput = prompt("Please enter a PUBLIC Torn API Key:");
            if (userInput && userInput.length === 16) {
                apiKey = userInput;
                localStorage.setItem("rosti-torn-price-filler", userInput);
            } else {
                alert("Invalid API Key. Please provide a valid 16-character key.");
                return false;
            }
        }
        return true;
    }

    function forceNewApiKey() {
        let userInput = prompt("Your Torn API key seems to be invalid. Please enter a new PUBLIC Torn API key:");
        if (userInput && userInput.length === 16) {
            apiKey = userInput;
            localStorage.setItem("rosti-torn-price-filler", userInput);
            alert("New API key saved. Retrying price fetch...");
            return true;
        } else if (userInput) {
            alert("Invalid API Key. Must be 16 characters long. The old key will be used for now.");
        }
        return false;
    }

    // --- API CALLS ---

    async function GetPrices(itemId) {
        if (!apiKey) {
            if (!checkApiKey()) return [];
        }

        let requestUrl = priceDeltaRaw.includes('[market]') ? itemUrl : marketUrlV2;
        requestUrl = requestUrl.replace("{itemId}", itemId).replace("{apiKey}", apiKey);

        try {
            const response = await fetch(requestUrl);
            const data = await response.json();

            if (data.error) {
                console.error("[PriceFiller] Torn API error:", data.error.error);
                if (forceNewApiKey()) {
                    return GetPrices(itemId); // Retry with the new key
                } else {
                    alert("Cannot fetch prices without a valid Torn API key.");
                    return []; // User cancelled or entered invalid key
                }
            }

            if (priceDeltaRaw.includes('[market]')) {
                if (data.items && data.items[itemId]) {
                    return [{ "price": data.items[itemId].market_value, "amount": 1 }];
                } else {
                     console.error("[PriceFiller] Torn API did not return market value for item:", itemId);
                     return [];
                }
            }
            return data.itemmarket?.listings || [];
        } catch (error) {
            console.error("[PriceFiller] Network error during Torn API fetch:", error);
            return [];
        }
    }

    async function GetBazaarPrices(itemId) {
        return RateLimiter.processRequest(() => new Promise((resolve) => {
            const url = bazaarUrl.replace('{itemId}', itemId);
            GM.xmlHttpRequest({
                method: "GET",
                url: url,
                onload: function(response) {
                    try {
                        const data = JSON.parse(response.responseText);
                        resolve(data?.listings?.map(l => ({ price: l.price, amount: l.quantity })) || []);
                    } catch (e) {
                        console.error("[PriceFiller] Error parsing bazaar data:", e);
                        resolve([]);
                    }
                },
                onerror: function(error) {
                    console.error("[PriceFiller] Error fetching bazaar data:", error);
                    resolve([]);
                }
            });
        }));
    }

    // --- POPUP UI ---

    function addCustomFillPopup() {
        const popup = document.createElement('div');
        popup.className = 'rosti-price-filler-popup';
        popup.style.display = 'none';
        popup.innerHTML = `
            <div class="rosti-price-filler-popup-close" title="Close">&times;</div>
            <b class="rosti-price-filler-popup-draggable" style="cursor: move; user-select: none;">Drag from here</b>
            <div class="rosti-price-filler-popup-body" style="margin-top: 2px;"></div>
            <div class="rosti-price-filler-popup-footer" style="margin-top: 4px;">
                <input type="checkbox" id="rosti-price-filler-keep-open" style="margin: 0; vertical-align: middle;" />
                <label for="rosti-price-filler-keep-open" style="margin-left: 4px; vertical-align: middle;">Keep open</label>
            </div>
        `;
        popup.querySelector('.rosti-price-filler-popup-close').onclick = hideAllFillPopups;
        document.body.appendChild(popup);

        const dragHandle = popup.querySelector('.rosti-price-filler-popup-draggable');

        const startDrag = (e) => {
            isDragging = true;
            const clientX = e.clientX || e.touches[0].clientX;
            const clientY = e.clientY || e.touches[0].clientY;
            startX = clientX - popup.offsetLeft;
            startY = clientY - popup.offsetTop;
            e.preventDefault();
        };

        const drag = (e) => {
            if (isDragging) {
                const clientX = e.clientX || e.touches[0].clientX;
                const clientY = e.clientY || e.touches[0].clientY;
                popup.style.left = (clientX - startX) + "px";
                popup.style.top = (clientY - startY) + "px";
            }
        };

        const endDrag = () => {
            if (isDragging) {
                isDragging = false;
                localStorage.setItem("rosti-torn-price-filler-popup-offset-x", popup.style.left);
                localStorage.setItem("rosti-torn-price-filler-popup-offset-y", popup.style.top);
            }
        };

        dragHandle.addEventListener("mousedown", startDrag);
        document.addEventListener("mousemove", drag);
        document.addEventListener("mouseup", endDrag);

        dragHandle.addEventListener("touchstart", startDrag);
        document.addEventListener("touchmove", drag);
        document.addEventListener("touchend", endDrag);
    }


    function showCustomFillPopup(contentHTML) {
        const popup = document.querySelector('.rosti-price-filler-popup');
        popup.querySelector('.rosti-price-filler-popup-body').innerHTML = contentHTML;
        popup.querySelectorAll('.rosti-price-filler-popup-price').forEach(row => {
            row.addEventListener('click', (e) => {
                if (recentFilledInput) {
                    recentFilledInput.forEach(x => { x.value = parseInt(e.target.getAttribute('data-price')) - 1 });
                    recentFilledInput[0].dispatchEvent(new Event("input", { bubbles: true }));
                }
            });
        });
    }

    function hideAllFillPopups() {
        const popup = document.querySelector('.rosti-price-filler-popup');
        if (popup) popup.style.display = 'none';
        clearInterval(refreshInterval);
    }

    function GetPricesBreakdown(marketPrices, bazaarPrices) {
        const marketTaxFactor = 1 - 0.05;
        const sb = new StringBuilder();
        sb.append('<div class="rosti-price-filler-popup-table">');
        const buildColumn = (title, prices, includeTax) => {
            sb.append(`<div class="rosti-price-filler-popup-col"><b>${title}</b>`);
            if (prices && prices.length > 0) {
                for (let i = 0; i < Math.min(prices.length, 5); i++) {
                    const item = prices[i];
                    if (typeof item !== "object" || item.amount === undefined || item.price === undefined) continue;
                    let priceText = `${item.amount} x ${formatNumberWithCommas(item.price)}`;
                    if (includeTax) {
                        priceText += ` (${formatNumberWithCommas(Math.round(item.price * marketTaxFactor))})`;
                    }
                    sb.append(`<span class="rosti-price-filler-popup-price" data-price=${item.price}>${priceText}</span>`);
                }
            } else {
                sb.append('<span>No listings found.</span>');
            }
            sb.append('</div>');
        };
        buildColumn('Item Market', marketPrices, true);
        buildColumn('Bazaar', bazaarPrices, false);
        sb.append('</div>');
        return sb.toString();
    }

    async function handleFillClick(event, itemId, priceInputs, quantityCallback) {
        if (!checkApiKey()) return;
        clearTimeout(closePopupTimer);
        clearInterval(refreshInterval);

        recentFilledInput = priceInputs;
        const popup = document.querySelector('.rosti-price-filler-popup');

        if (popup && showPricesPopup) {
            const rect = event.currentTarget.getBoundingClientRect();
            const savedX = localStorage.getItem("rosti-torn-price-filler-popup-offset-x");
            const savedY = localStorage.getItem("rosti-torn-price-filler-popup-offset-y");

            popup.style.left = savedX ? savedX : `${window.scrollX + rect.left - 250}px`;
            popup.style.top = savedY ? savedY : `${window.scrollY + rect.top + 4}px`;
            popup.style.display = 'block';
            popup.querySelector('.rosti-price-filler-popup-body').innerHTML = LOADING_THE_PRICES;
        }


        const updatePopupContent = async () => {
            let [marketPrices, bazaarPrices] = await Promise.all([GetPrices(itemId), GetBazaarPrices(itemId)]);
            if (showPricesPopup) {
                const breakdown = GetPricesBreakdown(marketPrices, bazaarPrices);
                showCustomFillPopup(breakdown);
            }
            return { marketPrices, bazaarPrices };
        };

        let { marketPrices, bazaarPrices } = await updatePopupContent();

        if (showPricesPopup) {
            const startRefresh = () => {
                clearInterval(refreshInterval);
                refreshInterval = setInterval(updatePopupContent, 60000);
            };

            const keepOpenCheckbox = popup.querySelector('#rosti-price-filler-keep-open');
            keepOpenCheckbox.checked = keepPopupOpen;

            if (keepOpenCheckbox.checked) {
                startRefresh();
            } else {
                closePopupTimer = setTimeout(hideAllFillPopups, 2000);
            }

            keepOpenCheckbox.onchange = () => {
                keepPopupOpen = keepOpenCheckbox.checked;
                localStorage.setItem("rosti-torn-price-filler-keep-popup-open", keepPopupOpen);
                if (keepPopupOpen) {
                    clearTimeout(closePopupTimer);
                    startRefresh();
                } else {
                    clearInterval(refreshInterval);
                    closePopupTimer = setTimeout(hideAllFillPopups, 3000);
                }
            };
        }


        const GetPrice = (prices) => {
            if (!prices || prices.length === 0) return '';
            if (priceDeltaRaw.includes('[median]')) {
                const sortedPrices = prices.map(p => p.price).sort((a, b) => a - b);
                const mid = Math.floor(sortedPrices.length / 2);
                const median = sortedPrices.length % 2 === 0 ? (sortedPrices[mid - 1] + sortedPrices[mid]) / 2 : sortedPrices[mid];
                let priceDelta = priceDeltaRaw.substring(0, priceDeltaRaw.indexOf('['));
                return Math.round(performOperation(median, priceDelta));
            } else if (priceDeltaRaw.includes('[market]')) {
                let priceDelta = priceDeltaRaw.substring(0, priceDeltaRaw.indexOf('['));
                return Math.round(performOperation(prices[0].price, priceDelta));
            } else {
                let marketSlotOffset = priceDeltaRaw.includes('[') ? parseInt(priceDeltaRaw.substring(priceDeltaRaw.indexOf('[') + 1, priceDeltaRaw.indexOf(']'))) : 0;
                let priceDeltaWithoutMarketOffset = priceDeltaRaw.includes('[') ? priceDeltaRaw.substring(0, priceDeltaRaw.indexOf('[')) : priceDeltaRaw;
                return Math.round(performOperation(prices[Math.min(marketSlotOffset, prices.length - 1)].price, priceDeltaWithoutMarketOffset));
            }
        };

        const isBazaar = window.location.href.includes("bazaar.php");
        const pricesToUse = isBazaar ? bazaarPrices : marketPrices;
        let price = GetPrice(pricesToUse);
        priceInputs.forEach(x => { x.value = price });
        priceInputs[0].dispatchEvent(new Event("input", { bubbles: true }));

        if (quantityCallback) {
            quantityCallback();
        }
    }

    // --- PAGE-SPECIFIC INITIALIZERS ---
    function addDisablePopupCheckbox(container, insertBeforeElement, isMarket) {
        if (document.getElementById('disable-popup-checkbox-container')) return;

        const checkboxContainer = document.createElement('div');
        checkboxContainer.id = 'disable-popup-checkbox-container';
        checkboxContainer.style.display = 'inline-flex';
        checkboxContainer.style.alignItems = 'center';
        if(isMarket) {
            checkboxContainer.style.marginLeft = '10px';
        } else {
            checkboxContainer.style.marginRight = '10px';
        }


        const checkbox = document.createElement('input');
        checkbox.type = 'checkbox';
        checkbox.id = 'disable-popup-checkbox';
        checkbox.checked = showPricesPopup;
        checkbox.style.margin = '0';

        const label = document.createElement('label');
        label.htmlFor = 'disable-popup-checkbox';
        label.textContent = 'Show Popup';
        label.style.marginLeft = '5px';
        label.style.fontWeight = 'normal';


        checkbox.addEventListener('change', () => {
            showPricesPopup = checkbox.checked;
            GM_setValue("rosti-torn-price-filler-show-prices-popup", showPricesPopup);
        });

        checkboxContainer.appendChild(checkbox);
        checkboxContainer.appendChild(label);

        if (insertBeforeElement) {
            container.insertBefore(checkboxContainer, insertBeforeElement);
        } else {
            container.appendChild(checkboxContainer);
        }
    }


    function initMarketPage() {
        const observerTarget = document.querySelector("#item-market-root");
        if (!observerTarget) return;

        const observer = new MutationObserver(mutations => {
            mutations.forEach(mutationRaw => {
                const mutation = mutationRaw.target;
                const isAddItems = window.location.href.includes('#/addListing');
                const isViewItems = window.location.href.includes('#/viewListing');

                if (isAddItems || isViewItems) {
                    const selector = '[class*=itemRowWrapper___]:not(.price-filler-processed) > [class*=itemRow___]:not([class*=grayedOut___]) [class^=priceInputWrapper___]';
                    mutation.querySelectorAll(selector).forEach(x => addMarketFillButton(x));
                }
            });
            // Add checkbox for item market
            const controlsContainer = document.querySelector('[class*="controls___"]');
            if (controlsContainer) {
                addDisablePopupCheckbox(controlsContainer, null, true);
            }
        });
        observer.observe(observerTarget, { childList: true, subtree: true });
    }

    function addMarketFillButton(itemPriceElement) {
        if (itemPriceElement.querySelector('.price-filler-button')) return;

        const wrapperParent = findParentByCondition(itemPriceElement, (el) => String(el.className).includes('itemRowWrapper___'));
        if (!wrapperParent) return;
        wrapperParent.classList.add('price-filler-processed');

        const itemIdString = wrapperParent.querySelector('[class^=itemRow___] [type=button][class^=viewInfoButton___]')?.getAttribute('aria-controls');
        const itemImage = wrapperParent.querySelector('[class*=viewInfoButton] img');
        const itemId = window.location.href.includes('#/addListing')
            ? (itemIdString?.match(/-(\d+)-/)?.[1] || -1)
            : (itemImage?.src.match(/\/(\d+)\//)?.[1] || -1);

        if (itemId === -1) return;

        const span = document.createElement('span');
        span.className = 'price-filler-button input-money-symbol';
        span.style.position = "relative";
        span.onclick = (e) => {
            const priceInputs = Array.from(itemPriceElement.querySelectorAll('input.input-money'));
            const quantityInputs = findParentByCondition(e.target, (el) => String(el.className).includes('info___'))
                ?.querySelectorAll('[class^=amountInputWrapper___] .input-money-group > .input-money');

            handleFillClick(e, itemId, priceInputs, () => {
                if (quantityInputs && quantityInputs.length > 0) {
                    if (quantityInputs[0].value.length === 0 || parseInt(quantityInputs[0].value) < 1) {
                        quantityInputs[0].value = Number.MAX_SAFE_INTEGER;
                        quantityInputs[1].value = Number.MAX_SAFE_INTEGER;
                    }
                    quantityInputs[0].dispatchEvent(new Event("input", { bubbles: true }));
                }
            });
        };

        const input = document.createElement('input');
        input.type = 'button';
        input.className = 'wai-btn';
        span.appendChild(input);
        itemPriceElement.querySelector('.input-money-group').prepend(span);
    }

    function initBazaarPage() {
        const processBazaarItems = () => {
            // For #/add page
            $("ul.items-cont li.clearfix").each(function() {
                const targetElement = $(this).find("div.title-wrap div.name-wrap")[0];
                if (targetElement && !$(this).hasClass("disabled") && !targetElement.querySelector('.torn-bazaar-fill-qty-price')) {
                    addBazaarFillButtons(targetElement);
                }
            });

            // For #/manage page
            $("div[class*=row___]").each(function() {
                const targetElement = $(this).find("div[class*=item___] div[class*=desc___]")[0];
                if (targetElement && !targetElement.querySelector('.torn-bazaar-fill-qty-price')) {
                    addBazaarFillButtons(targetElement);
                }
            });

            // Add checkbox for bazaar on manage page
            if (window.location.hash.includes('#/manage')) {
                const linksContainer = document.querySelector('.linksContainer___LiOTN');
                const addItemsLink = document.querySelector('a[href="#/add"]');
                if (linksContainer && addItemsLink && !document.getElementById('disable-popup-checkbox-container')) {
                    addDisablePopupCheckbox(linksContainer, addItemsLink, false);
                }
            }
        };

        const observerTarget = document.querySelector(".content-wrapper");
        if (!observerTarget) return;

        const observer = new MutationObserver(processBazaarItems);
        observer.observe(observerTarget, { childList: true, subtree: true });
        window.addEventListener('hashchange', () => {
            // A brief delay to allow the page to render after hash change
            setTimeout(processBazaarItems, 100);
        });
        processBazaarItems(); // Initial run
    }

    function addBazaarFillButtons(element) {
        const outerSpanFill = document.createElement('span');
        outerSpanFill.className = 'btn-wrap torn-bazaar-fill-qty-price';
        const innerSpanFill = document.createElement('span');
        innerSpanFill.className = 'btn';
        const inputElementFill = document.createElement('input');
        inputElementFill.type = 'button';
        inputElementFill.value = "Fill";
        inputElementFill.className = 'torn-btn';

        innerSpanFill.appendChild(inputElementFill);
        outerSpanFill.appendChild(innerSpanFill);
        element.append(outerSpanFill);

        $(outerSpanFill).on("click", "input", function(event) {
            event.stopPropagation();
            const itemRow = $(this).closest('li.clearfix, div[class*=row___]');
            const image = itemRow.find("div.image-wrap img, div.imgContainer___tEZeE img")[0];
            const itemId = image.src.match(/\/(\d+)\//)?.[1];
            if (!itemId) return;

            const fillPriceAndQuantity = () => {
                let priceInputs;
                const isManageMobile = window.location.hash.includes('#/manage') && window.innerWidth <= 784;

                if (isManageMobile) {
                    priceInputs = Array.from(itemRow.find('.priceMobile___cpt8p .input-money-group input'));
                } else {
                    priceInputs = Array.from(itemRow.find("div.price div input, div[class*=price___] div.input-money-group input"));
                }

                const quantityInput = itemRow.find("div.amount input")[0];
                const quantityElement = itemRow.find('span.t-hide span:last-child');
                const quantity = quantityElement.length > 0 ? quantityElement.text().trim() : 1;

                handleFillClick(event, itemId, priceInputs, () => {
                    if (quantityInput) {
                        quantityInput.value = quantity;
                        quantityInput.dispatchEvent(new Event("keyup", { bubbles: true }));
                    }
                });
            };

            // On manage page, expand the item if it's not already expanded
            if (window.location.hash.includes('#/manage')) {
                const itemContainer = itemRow.find('div[class*=item___]');
                if (itemContainer.length > 0 && !itemContainer.hasClass('active___OTFsm')) {
                    const manageButton = itemRow.find('button[aria-label="Manage"]');
                    if (manageButton.length > 0) {
                        manageButton.click();
                        // Wait for expansion animation before filling
                        setTimeout(fillPriceAndQuantity, 150);
                        return;
                    }
                }
            }

            // If not on manage page or item is already expanded, fill immediately
            fillPriceAndQuantity();
        });
    }

    // --- SCRIPT INITIALIZATION ---

    function init() {
        GM_addStyle_TornPDA(`
            .rosti-price-filler-popup { background: var(--tooltip-bg-color); padding: 5px 10px; border-radius: 5px; border: 1px solid #888; box-shadow: 0 2px 8px 0 #0006; color: var(--info-msg-font-color); z-index: 99999; position: absolute; font-size: 0.9em; line-height: 1.2; pointer-events: auto; }
            .rosti-price-filler-popup-close { position: absolute; top: 1px; right: 5px; font-size: 1.2em; color: #aaa; cursor: pointer; }
            .rosti-price-filler-popup-price { cursor: pointer; display: block; margin-bottom: 1px; }
            .rosti-price-filler-popup-table { display: flex; gap: 15px; }
            .rosti-price-filler-popup-col { display: flex; flex-direction: column; gap: 2px; }
            .price-filler-button { position: relative; }
            .btn-wrap.torn-bazaar-fill-qty-price { float: right; margin-left: auto; z-index: 99999; }
        `);
        addCustomFillPopup();

        const href = window.location.href;
        if (href.includes("page.php?sid=ItemMarket")) {
            initMarketPage();
        } else if (href.includes("bazaar.php")) {
            initBazaarPage();
        }

        GM_registerMenuCommand("Change Price Delta", () => {
            const newPriceDelta = prompt("Enter new price delta (e.g., -1, -10%, -1[median], -1[market]):", priceDeltaRaw);
            if (newPriceDelta) {
                priceDeltaRaw = newPriceDelta;
                GM_setValue("rosti-torn-price-filler-price-delta", newPriceDelta);
            }
        });
    }

    init();

})();

QingJ © 2025

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