Torn Bazaar Filler

On "Fill" click autofills bazaar item price with lowest bazaar price currently minus $1 (can be customised), shows current price coefficient compared to 3rd lowest, fills max quantity for items, marks checkboxes for guns.

目前为 2023-09-21 提交的版本。查看 最新版本

作者
SOLiNARY
评分
0 0 0
版本
0.8.1
创建于
2023-08-19
更新于
2023-09-21
大小
21.4 KB
许可证
MIT
适用于

// ==UserScript== // @name Torn Bazaar Filler // @namespace https://github.com/SOLiNARY // @version 0.8 // @description On "Fill" click autofills bazaar item price with lowest bazaar price currently minus $1 (can be customised), shows current price coefficient compared to 3rd lowest, fills max quantity for items, marks checkboxes for guns. // @author Ramin Quluzade, Silmaril [2665762] // @license MIT License // @match https://www.torn.com/bazaar.php* // @icon https://www.google.com/s2/favicons?sz=64&domain=torn.com // @require https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js // @run-at document-idle // @grant GM_addStyle // @grant GM_registerMenuCommand // ==/UserScript==

(function() { 'use strict';

const bazaarUrl = "https://api.torn.com/market/{itemId}?selections=bazaar&key={apiKey}";
let priceDeltaRaw = localStorage.getItem("silmaril-torn-bazaar-filler-price-delta") ?? '-1';
let apiKey = localStorage.getItem("silmaril-torn-bazaar-filler-apikey");

try {
    GM_registerMenuCommand('Set Price Delta', setPriceDelta);
    GM_registerMenuCommand('Set Api Key', function() { checkApiKey(false); });
} catch (error) {
    console.log('[TornBazaarFiller] Tampermonkey not detected!');
}

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

GM_addStyle(`

.btn-wrap.torn-bazaar-fill-qty-price { float: right; margin-left: auto; z-index: 99999; }

.btn-wrap.torn-bazaar-clear-qty-price { z-index: 99999; }

div.title-wrap div.name-wrap { display: flex; justify-content: flex-end; }

.wave-animation { position: relative; overflow: hidden; }

.wave { pointer-events: none; position: absolute; width: 100%; height: 33px; background-color: transparent; opacity: 0; transform: translateX(-100%); animation: waveAnimation 1s cubic-bezier(0, 0, 0, 1); }

@keyframes waveAnimation { 0% { opacity: 1; transform: translateX(-100%); } 100% { opacity: 0; transform: translateX(100%); } }

.overlay-percentage { position: absolute; top: 0px; background-color: rgba(0, 0, 0, 0.9); padding: 0px 5px; border-radius: 15px; font-size: 10px; }

.overlay-percentage-add { right: -30px; }

.overlay-percentage-manage { right: 0px; } `);

const pages = { "AddItems": 10, "ManageItems": 20};
const addItemsLabels = ["Fill", "Clear"];
const updateItemsLabels = ["Update", "Clear"];

const viewPortWidthPx = window.innerWidth;
const isMobileView = viewPortWidthPx <= 784;

const observerTarget = $(".content-wrapper")[0];
const observerConfig = { attributes: false, childList: true, characterData: false, subtree: true };

const observer = new MutationObserver(function(mutations) {
    let mutation = mutations[0].target;
    if (mutation.classList.contains("items-cont") || mutation.classList.contains("core-layout___uf3LW")) {
        $("ul.ui-tabs-nav").on("click", "li:not(.ui-state-active):not(.ui-state-disabled):not(.m-show)", function() {
            observer.observe(observerTarget, observerConfig);
        });
        $("div.topSection___U7sVi").on("click", "div.linksContainer___LiOTN a[aria-labelledby=add-items]", function(){
            observer.observe(observerTarget, observerConfig);
        });
        $("div.topSection___U7sVi").on("click", "div.listItem___Q3FFU a[aria-labelledby=add-items]", function(){
            observer.observe(observerTarget, observerConfig);
        });
        $("div.topSection___U7sVi").on("click", "div.linksContainer___LiOTN a[aria-labelledby=manage-items]", function(){
            observer.observe(observerTarget, observerConfig);
        });
        $("div.topSection___U7sVi").on("click", "div.listItem___Q3FFU a[aria-labelledby=manage-items]", function(){
            observer.observe(observerTarget, observerConfig);
        });

        let containerItems = $("ul.items-cont li.clearfix");
        containerItems.find("div.title-wrap div.name-wrap").each(function(){
            let isParentRowDisabled = this.parentElement.parentElement.classList.contains("disabled");
            let alreadyHasFillBtn = this.querySelector(".btn-wrap.torn-bazaar-fill-qty-price") != null;
            if (!alreadyHasFillBtn && !isParentRowDisabled){
                insertFillAndWaveBtn(this, addItemsLabels, pages.AddItems);
            }
        });

        let containerItemsManage = $("div.row___n2Uxh");
        containerItemsManage.find("div.item___jLJcf div.desc___VJSNQ").each(function(){
            let alreadyHasUpdateBtn = this.querySelector(".btn-wrap.torn-bazaar-fill-qty-price") != null;
            if (!alreadyHasUpdateBtn) {
                insertFillAndWaveBtn(this, updateItemsLabels, pages.ManageItems);
            }
        });

        if($("div.amount").length || $("div.desc___VJSNQ").length) {
            observer.disconnect();
        }
    }
});
observer.observe(observerTarget, observerConfig);

function insertFillAndWaveBtn(element, buttonLabels, pageType){
    const waveDiv = document.createElement('div');
    waveDiv.className = 'wave';

    const outerSpanFill = document.createElement('span');
    outerSpanFill.className = 'btn-wrap torn-bazaar-fill-qty-price';
    const outerSpanClear = document.createElement('span');
    outerSpanClear.className = 'btn-wrap torn-bazaar-clear-qty-price';

    const innerSpanFill = document.createElement('span');
    innerSpanFill.className = 'btn';
    const innerSpanClear = document.createElement('span');
    innerSpanClear.className = 'btn';
    innerSpanClear.style.display = 'none';

    const inputElementFill = document.createElement('input');
    inputElementFill.type = 'button';
    inputElementFill.value = buttonLabels[0];
    inputElementFill.className = 'torn-btn';
    const inputElementClear = document.createElement('input');
    inputElementClear.type = 'button';
    inputElementClear.value = buttonLabels[1];
    inputElementClear.className = 'torn-btn';

    innerSpanFill.appendChild(inputElementFill);
    innerSpanClear.appendChild(inputElementClear);
    outerSpanFill.appendChild(innerSpanFill);
    outerSpanClear.appendChild(innerSpanClear);

    element.append(outerSpanFill, outerSpanClear, waveDiv);

    switch(pageType) {
        case pages.AddItems:
            $(outerSpanFill).on("click", "input", function(event) {
                checkApiKey();
                this.parentNode.style.display = "none";
                fillQuantityAndPrice(this, pageType);
                event.stopPropagation();
            });

            $(outerSpanClear).on("click", "input", function(event) {
                this.parentNode.style.display = "none";
                clearQuantityAndPrice(this);
                event.stopPropagation();
            });
            break;
        case pages.ManageItems:
            $(outerSpanFill).on("click", "input", function(event) {
                checkApiKey();
                // this.parentNode.style.display = "none";
                updatePrice(this);
                event.stopPropagation();
            });

            // $(outerSpanClear).on("click", "input", function(event) {
            //     this.parentNode.style.display = "none";
            //     clearQuantity(this, pageType);
            //     event.stopPropagation();
            // });
            break;
    }

}

function insertPercentageSpan(element){
    let moneyGroupDiv = element.querySelector("div.price div.input-money-group");

    if (moneyGroupDiv.querySelector("span.overlay-percentage") === null) {
        const percentageSpan = document.createElement('span');
        percentageSpan.className = 'overlay-percentage overlay-percentage-add';
        moneyGroupDiv.appendChild(percentageSpan);
    }

    return moneyGroupDiv.querySelector("span.overlay-percentage");
}

function insertPercentageManageSpan(element){
    let moneyGroupDiv = element.querySelector("div.input-money-group");

    if (moneyGroupDiv.querySelector("span.overlay-percentage") === null) {
        const percentageSpan = document.createElement('span');
        percentageSpan.className = 'overlay-percentage overlay-percentage-manage';
        moneyGroupDiv.appendChild(percentageSpan);
    }

    return moneyGroupDiv.querySelector("span.overlay-percentage");
}

function fillQuantityAndPrice(element, pageType){
    let amountDiv = element.parentElement.parentElement.parentElement.parentElement.parentElement.querySelector("div.amount-main-wrap");
    let priceInputs = amountDiv.querySelectorAll("div.price div input");
    let keyupEvent = new Event("keyup", {bubbles: true});
    let inputEvent = new Event("input", {bubbles: true});

    let image = element.parentElement.parentElement.parentElement.parentElement.querySelector("div.image-wrap img");
    let numberPattern = /\/(\d+)\//;
    let match = image.src.match(numberPattern);
    let extractedItemId = 0;
    if (match) {
        extractedItemId = parseInt(match[1], 10);
    } else {
        console.error("[TornBazaarFiller] ItemId not found!");
    }

    let requestUrl = bazaarUrl
    .replace("{itemId}", extractedItemId)
    .replace("{apiKey}", apiKey);

    let wave = element.parentElement.parentElement.parentElement.querySelector("div.wave");
    fetch(requestUrl)
        .then(response => response.json())
        .then(data => {
        if (data.error != null && data.error.code === 2){
            apiKey = null;
            localStorage.setItem("silmaril-torn-bazaar-filler-apikey", null);
            wave.style.backgroundColor = "red";
            wave.style.animationDuration = "5s";
            console.error("[TornBazaarFiller] Incorrect Api Key:", data);
            return;
        }
        let lowBallPrice = Math.round(performOperation(data.bazaar[0].cost, priceDeltaRaw));
        let price3rd = data.bazaar[Math.min(2, data.bazaar.length - 1)].cost;
        let priceCoefficient = ((lowBallPrice / price3rd) * 100).toFixed(0);

        let percentageOverlaySpan = insertPercentageSpan(amountDiv);
        if (priceCoefficient <= 95){
            percentageOverlaySpan.style.display = "block";
            if (priceCoefficient <= 50){
                percentageOverlaySpan.style.color = "red";
                wave.style.backgroundColor = "red";
                wave.style.animationDuration = "5s";
            } else if (priceCoefficient <= 75){
                percentageOverlaySpan.style.color = "yellow";
                wave.style.backgroundColor = "yellow";
                wave.style.animationDuration = "3s";
            } else {
                percentageOverlaySpan.style.color = "green";
                wave.style.backgroundColor = "green";
            }
            percentageOverlaySpan.innerText = priceCoefficient + "%";
        } else {
            percentageOverlaySpan.style.display = "none";
            wave.style.backgroundColor = "green";
        }

        priceInputs[0].value = lowBallPrice;
        priceInputs[1].value = lowBallPrice;
        priceInputs[0].dispatchEvent(inputEvent);
    })
        .catch(error => {
        wave.style.backgroundColor = "red";
        wave.style.animationDuration = "5s";
        console.error("[TornBazaarFiller] Error fetching data:", error);
    })
        .finally(() => {
        element.parentNode.parentNode.parentNode.querySelector("span.btn-wrap.torn-bazaar-clear-qty-price span.btn").style.display = "inline-block";
    });
    wave.style.animation = 'none';
    wave.offsetHeight;
    wave.style.animation = null;
    wave.style.backgroundColor = "transparent";
    wave.style.animationDuration = "1s";

    let isQuantityCheckbox = amountDiv.querySelector("div.amount.choice-container") !== null;
    if (isQuantityCheckbox){
        amountDiv.querySelector("div.amount.choice-container input").click();
    } else {
        let quantityInput = amountDiv.querySelector("div.amount input");
        quantityInput.value = getQuantity(element, pageType);
        quantityInput.dispatchEvent(keyupEvent);
    }
}

function updatePrice(element){
    let moneyGroupDiv;
    let parentNode4 = element.parentNode.parentNode.parentNode.parentNode;
    if (isMobileView){
        if (parentNode4.querySelector(".menuActivators___STzEc button.iconContainer___H3dWv[aria-label=Manage] span.active___OTFsm") == null) {
            parentNode4.querySelector(".menuActivators___STzEc button.iconContainer___H3dWv[aria-label=Manage]").click();
        }
        moneyGroupDiv = parentNode4.parentNode.querySelector(".bottomMobileMenu___CCSjc .priceMobile___cpt8p");
    } else {
        moneyGroupDiv = element.parentNode.parentNode.parentNode.parentNode.querySelector("div.price___DoKP7");
    }
    let priceInputs = moneyGroupDiv.querySelectorAll("div.input-money-group input");
    let inputEvent = new Event("input", {bubbles: true});

    let image = element.parentElement.parentElement.parentElement.parentElement.querySelector("div.imgContainer___tEZeE img");
    let extractedItemId = getItemIdFromImage(image);

    let requestUrl = bazaarUrl
    .replace("{itemId}", extractedItemId)
    .replace("{apiKey}", apiKey);

    let wave = element.parentElement.parentElement.parentElement.querySelector("div.wave");
    fetch(requestUrl)
        .then(response => response.json())
        .then(data => {
        if (data.error != null && data.error.code === 2){
            apiKey = null;
            localStorage.setItem("silmaril-torn-bazaar-filler-apikey", null);
            wave.style.backgroundColor = "red";
            wave.style.animationDuration = "5s";
            console.error("[TornBazaarFiller] Incorrect Api Key:", data);
            return;
        }
        let lowBallPrice = Math.round(performOperation(data.bazaar[0].cost, priceDeltaRaw));;
        let price3rd = data.bazaar[Math.min(2, data.bazaar.length - 1)].cost;
        let priceCoefficient = ((lowBallPrice / price3rd) * 100).toFixed(0);

        let percentageOverlaySpan = insertPercentageManageSpan(moneyGroupDiv);
        if (priceCoefficient <= 95){
            percentageOverlaySpan.style.display = "block";
            if (priceCoefficient <= 50){
                percentageOverlaySpan.style.color = "red";
                wave.style.backgroundColor = "red";
                wave.style.animationDuration = "5s";
            } else if (priceCoefficient <= 75){
                percentageOverlaySpan.style.color = "yellow";
                wave.style.backgroundColor = "yellow";
                wave.style.animationDuration = "3s";
            } else {
                percentageOverlaySpan.style.color = "green";
                wave.style.backgroundColor = "green";
            }
            percentageOverlaySpan.innerText = priceCoefficient + "%";
        } else {
            percentageOverlaySpan.style.display = "none";
            wave.style.backgroundColor = "green";
        }

        priceInputs[0].value = lowBallPrice;
        priceInputs[1].value = lowBallPrice;
        priceInputs[0].dispatchEvent(inputEvent);
    })
        .catch(error => {
        wave.style.backgroundColor = "red";
        wave.style.animationDuration = "5s";
        console.error("[TornBazaarFiller] Error fetching data:", error);
    })
        .finally(() => {
        // element.parentNode.parentNode.parentNode.querySelector("span.btn-wrap.torn-bazaar-clear-qty-price span.btn").style.display = "inline-block";
    });
    wave.style.animation = 'none';
    wave.offsetHeight;
    wave.style.animation = null;
    wave.style.backgroundColor = "transparent";
    wave.style.animationDuration = "1s";
}

function clearQuantityAndPrice(element){
    let amountDiv = element.parentElement.parentElement.parentElement.parentElement.parentElement.querySelector("div.amount-main-wrap");
    let priceInputs = amountDiv.querySelectorAll("div.price div input");
    let keyupEvent = new Event("keyup", {bubbles: true});
    let inputEvent = new Event("input", {bubbles: true});

    let wave = element.parentElement.parentElement.parentElement.querySelector("div.wave");
    wave.style.backgroundColor = "white";

    let isQuantityCheckbox = amountDiv.querySelector("div.amount.choice-container") !== null;
    if (isQuantityCheckbox){
        amountDiv.querySelector("div.amount.choice-container input").click();
    } else {
        let quantityInput = amountDiv.querySelector("div.amount input");
        quantityInput.value = "";
        quantityInput.dispatchEvent(keyupEvent);
    }

    priceInputs[0].value = "";
    priceInputs[1].value = "";
    priceInputs[0].dispatchEvent(inputEvent);

    wave.style.animation = 'none';
    wave.offsetHeight;
    wave.style.animation = null;

    element.parentNode.parentNode.parentNode.querySelector("span.btn-wrap.torn-bazaar-fill-qty-price span.btn").style.display = "inline-block";
}

//     function clearQuantity(element, pageType){
//         let itemRow = element.parentNode.parentNode.parentNode.parentNode;
//         let moneyGroupDiv = itemRow.querySelector("div.price___DoKP7");
//         let keyupEvent = new Event("keyup", {bubbles: true});

//         let wave = element.parentElement.parentElement.parentElement.querySelector("div.wave");
//         wave.style.backgroundColor = "white";

//         let quantityInput = itemRow.querySelector("div.remove___R4eVW input");
//         quantityInput.value = getQuantity(element, pageType);
//         quantityInput.dispatchEvent(keyupEvent);

//         wave.style.animation = 'none';
//         wave.offsetHeight;
//         wave.style.animation = null;

//         element.parentNode.parentNode.parentNode.querySelector("span.btn-wrap.torn-bazaar-fill-qty-price span.btn").style.display = "inline-block";
//     }

function getQuantity(element, pageType){
    let rgx = /x(\d+)$/;
    let rgxMobile = /^x(\d+)/
    let quantityText = 0;
    switch(pageType){
        case pages.AddItems:
            quantityText = element.parentNode.parentNode.parentNode.innerText;
            break;
        case pages.ManageItems:
            quantityText = element.parentNode.parentNode.parentNode.querySelector("span").innerText;
            break;
    }
    let match = isMobileView ? rgxMobile.exec(quantityText) : rgx.exec(quantityText);
    let quantity = match === null ? 1 : match[1];
    return quantity;
}

function getItemIdFromImage(image){
    let numberPattern = /\/(\d+)\//;
    let match = image.src.match(numberPattern);
    if (match) {
        return parseInt(match[1], 10);
    } else {
        console.error("[TornBazaarFiller] ItemId not found!");
    }
}

function performOperation(number, operation) {
    // Parse the operation string to extract the operator and value
    const match = operation.match(/^([-+]?)(\d+(?:\.\d+)?)(%)?$/);

    if (!match) {
        throw new Error('Invalid operation string');
    }

    const [, operator, operand, isPercentage] = match;
    const operandValue = parseFloat(operand);

    // Check for percentage and convert if necessary
    const adjustedOperand = isPercentage ? (number * operandValue) / 100 : operandValue;

    // Perform the operation based on the operator
    switch (operator) {
        case '':
        case '+':
            return number + adjustedOperand;
        case '-':
            return number - adjustedOperand;
        default:
            throw new Error('Invalid operator');
    }
}

function setPriceDelta() {
    let userInput = prompt('Enter price delta formula (default: -1):', priceDeltaRaw);
    if (userInput !== null) {
        priceDeltaRaw = userInput;
        localStorage.setItem("silmaril-torn-bazaar-filler-price-delta", userInput);
    } else {
        console.error("[TornBazaarFiller] User cancelled the Price Delta input.");
    }
}

function checkApiKey(checkExisting = true) {
    if (!checkExisting || apiKey === null || apiKey.length != 16){
        let userInput = prompt("Please enter a PUBLIC Api Key, it will be used to get current bazaar prices:", apiKey ?? '');
        if (userInput !== null && userInput.length == 16) {
            apiKey = userInput;
            localStorage.setItem("silmaril-torn-bazaar-filler-apikey", userInput);
        } else {
            console.error("[TornBazaarFiller] User cancelled the Api Key input.");
        }
    }
}

})();

QingJ © 2025

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