您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
On "Fill" click autofills market item price with lowest market price currently minus $1 (can be customised), fills max quantity for items, marks checkboxes for guns.
当前为
// ==UserScript== // @name Torn Market Filler // @namespace https://github.com/SOLiNARY // @version 0.3.1 // @description On "Fill" click autofills market item price with lowest market price currently minus $1 (can be customised), fills max quantity for items, marks checkboxes for guns. // @author Ramin Quluzade, Silmaril [2665762] // @license MIT License // @match https://www.torn.com/page.php?sid=ItemMarket* // @match https://*.torn.com/page.php?sid=ItemMarket* // @icon https://www.google.com/s2/favicons?sz=64&domain=torn.com // @run-at document-idle // @grant GM_addStyle // @grant GM_registerMenuCommand // ==/UserScript== (async function() { 'use strict'; try { GM_registerMenuCommand('Set Price Delta', setPriceDelta); GM_registerMenuCommand('Set Api Key', function() { checkApiKey(false); }); } catch (error) { console.log('[TornMarketFiller] Tampermonkey not detected!'); } const itemUrl = "https://api.torn.com/torn/{itemId}?selections=items&key={apiKey}&comment=MarketFiller"; const marketUrl = "https://api.torn.com/v2/market/{itemId}?selections=itemMarket&key={apiKey}&comment=MarketFiller"; let priceDeltaRaw = localStorage.getItem("silmaril-torn-bazaar-filler-price-delta") ?? '-1[0]'; let apiKey = localStorage.getItem("silmaril-torn-bazaar-filler-apikey") ?? '###PDA-APIKEY###'; let GM_addStyle = function (s) { let style = document.createElement("style"); style.type = "text/css"; style.innerHTML = s; document.head.appendChild(style); }; GM_addStyle(`#item-market-root [class^=addListingWrapper___] [class^=panels___] [class^=priceInputWrapper___] > .input-money-group > .input-money {border-bottom-left-radius: 0 !important;border-top-left-radius: 0 !important;}#item-market-root [class^=viewListingWrapper___] [class^=priceInputWrapper___] > .input-money-group > .input-money {border-bottom-left-radius: 0 !important;border-top-left-radius: 0 !important;}`); const pages = { "AddItems": 10, "ViewItems": 20, "Other": 0}; let currentPage = pages.Other; let holdTimer; const CLICK_TO_AUTOFILL = 'Click to autofill the price and quantity if not set already'; const LOADING_THE_PRICES = 'Loading the prices...'; const isMobileView = window.innerWidth <= 784; const observerTarget = document.querySelector("#item-market-root"); const observerConfig = { attributes: false, childList: true, characterData: false, subtree: true }; const observer = new MutationObserver(function(mutations) { mutations.forEach(mutationRaw => { let mutation = mutationRaw.target; currentPage = getCurrentPage(); if (currentPage == pages.AddItems){ if (mutation.id.startsWith('headlessui-tabs-panel-')) { mutation.querySelectorAll('[class*=itemRowWrapper___]:not(.silmaril-market-filler-processed) > [class*=itemRow___]:not([class*=grayedOut___]) [class^=priceInputWrapper___]').forEach(x => AddFillButton(x)); } if (mutation.className.indexOf('priceInputWrapper___') > -1){ AddFillButton(mutation); } } else if (currentPage == pages.ViewItems){ if (mutation.className.startsWith('viewListingWrapper___')) { mutation.querySelectorAll('[class*=itemRowWrapper___]:not(.silmaril-market-filler-processed) > [class*=itemRow___]:not([class*=grayedOut___]) [class^=priceInputWrapper___]').forEach(x => AddFillButton(x)); } } }); }); observer.observe(observerTarget, observerConfig); function AddFillButton(itemPriceElement){ if (itemPriceElement.querySelector('.silmaril-market-filler-button') != null){ return; } const wrapperParent = findParentByCondition(itemPriceElement, (el) => el.className.indexOf('itemRowWrapper___') > -1); wrapperParent.classList.add('silmaril-market-filler-processed'); let itemIdString = wrapperParent.querySelector('[class^=itemRow___] [type=button][class^=viewInfoButton___]').getAttribute('aria-controls'); let itemImage = wrapperParent.querySelector('[class*=viewInfoButton] img'); let itemId = currentPage == pages.AddItems ? getItemIdFromString(itemIdString) : getItemIdFromImage(itemImage); const span = document.createElement('span'); span.title = CLICK_TO_AUTOFILL; span.className = 'silmaril-market-filler-button input-money-symbol'; span.setAttribute('data-action-flag', 'fill'); span.addEventListener('click', async () => {await handleFillClick(itemId)}); span.addEventListener('mousedown', startHold); span.addEventListener('touchstart', startHold); span.addEventListener('mouseup', cancelHold); span.addEventListener('mouseleave', cancelHold); span.addEventListener('touchend', cancelHold); span.addEventListener('touchcancel', cancelHold); const input = document.createElement('input'); // input.title = 'Click to autofill the price & quantity if not set already'; input.type = 'button'; input.className = 'wai-btn'; span.appendChild(input); itemPriceElement.querySelector('.input-money-group').prepend(span); } async function GetPrices(itemId){ let requestUrl = priceDeltaRaw.indexOf('[market]') != -1 ? itemUrl : marketUrl; requestUrl = requestUrl .replace("{itemId}", itemId) .replace("{apiKey}", apiKey); return fetch(requestUrl) .then(response => response.json()) .then(data => { if (data.error != null){ switch (data.error.code){ case 2: apiKey = null; localStorage.setItem("silmaril-torn-bazaar-filler-apikey", null); console.error("[TornMarketFiller] Incorrect Api Key:", data); return {"price": 'Wrong API key!', "amount": 0}; case 9: console.warn("[TornMarketFiller] The API is temporarily disabled, please try again later"); return {"price": 'API is OFF!', "amount": 0}; default: console.error("[TornMarketFiller] Error:", data.error.error); return {"price": data.error.error, "amount": 0}; } } if (priceDeltaRaw.indexOf('[market]') != -1){ return {"price": data.items[itemId].market_value, "amount": 1}; } else { if (data.itemmarket.listings[0].price == null){ console.warn("[TornMarketFiller] The API is temporarily disabled, please try again later"); return {"price": 'API is OFF!', "amount": 0}; } return data.itemmarket.listings; } }) .catch(error => { console.error("[TornMarketFiller] Error fetching data:", error); return 'Failed!'; }); } function GetPrice(prices){ if (prices == null){ return 'No prices loaded'; } if (prices.amount == 0){ console.log('prices', prices); return prices.price; } if (priceDeltaRaw.indexOf('[market]') != -1) { prices = Array(prices); let priceDelta = priceDeltaRaw.indexOf('[') == -1 ? priceDeltaRaw : priceDeltaRaw.substring(0, priceDeltaRaw.indexOf('[')); return Math.round(performOperation(prices[0].price, priceDelta)); } else { let marketSlotOffset = priceDeltaRaw.indexOf('[') == -1 ? 0 : parseInt(priceDeltaRaw.substring(priceDeltaRaw.indexOf('[') + 1, priceDeltaRaw.indexOf(']'))); let priceDeltaWithoutMarketOffset = priceDeltaRaw.indexOf('[') == -1 ? priceDeltaRaw : priceDeltaRaw.substring(0, priceDeltaRaw.indexOf('[')); return Math.round(performOperation(prices[Math.min(marketSlotOffset, prices.length - 1)].price, priceDeltaWithoutMarketOffset)); } } async function handleFillClick(itemId){ let target = event.target; let tooltipId = target.getAttribute('aria-describedby'); let tooltip = document.getElementById(tooltipId).querySelector('.ui-tooltip-content'); target.title = LOADING_THE_PRICES; if (tooltip != null){ tooltip.innerHTML = tooltip.innerHTML.replace(CLICK_TO_AUTOFILL, LOADING_THE_PRICES); } let inputEvent = new Event("input", {bubbles: true}); let action = target.getAttribute('data-action-flag'); let prices = await GetPrices(itemId); const breakdown = GetPricesBreakdown(prices); target.title = breakdown; if (tooltip != null){ tooltip.innerHTML = tooltip.innerHTML.replace(LOADING_THE_PRICES, breakdown); } let price = action == 'fill' ? GetPrice(prices) : ''; switchActionFlag(target); let parentRow = findParentByCondition(target, (el) => el.className.indexOf('info___') > -1); let quantityInputs = parentRow.querySelectorAll('[class^=amountInputWrapper___] .input-money-group > .input-money'); if (quantityInputs.length > 0){ if (quantityInputs[0].value.length == 0 || parseInt(quantityInputs[0].value) < 1){ quantityInputs[0].value = action == 'fill' ? Number.MAX_SAFE_INTEGER : 0; quantityInputs[1].value = action == 'fill' ? Number.MAX_SAFE_INTEGER : 0; } else { quantityInputs[0].value = action == 'clear' ? '' : quantityInputs[0].value; quantityInputs[1].value = action == 'clear' ? '' : quantityInputs[1].value; } quantityInputs[0].dispatchEvent(inputEvent); } else { let checkbox = parentRow.querySelector('[class^=checkboxWrapper___] > [class^=checkboxContainer___] [type=checkbox]'); if (action == 'fill' && !checkbox.checked || action == 'clear' && checkbox.checked){ checkbox.click(); } } let priceInputs = target.parentNode.querySelectorAll('input.input-money'); priceInputs.forEach(x => {x.value = price}); priceInputs[0].dispatchEvent(inputEvent); } function getItemIdFromString(string){ const match = string.match(/-(\d+)-/); if (match) { const number = match[1]; return number; } else { console.error("[TornMarketFiller] ItemId not found!"); return -1; } } function getItemIdFromImage(image){ let numberPattern = /\/(\d+)\//; let match = image.src.match(numberPattern); if (match) { return parseInt(match[1], 10); } else { console.error("[TornMarketFiller] ItemId not found!"); return -1; } } function switchActionFlag(target){ switch (target.getAttribute('data-action-flag')){ case 'fill': target.setAttribute('data-action-flag', 'clear'); break; case 'clear': default: target.setAttribute('data-action-flag', 'fill'); break; } } function findParentByCondition(element, conditionFn){ let currentElement = element; while (currentElement !== null) { if (conditionFn(currentElement)) { return currentElement; } currentElement = currentElement.parentElement; } return null; } function setPriceDelta() { let userInput = prompt('Enter price delta formula (default: -1[market]):', priceDeltaRaw); if (userInput !== null) { priceDeltaRaw = userInput; localStorage.setItem("silmaril-torn-bazaar-filler-price-delta", userInput); } else { console.error("[TornMarketFiller] User cancelled the Price Delta input."); } } function GetPricesBreakdown(prices){ if (prices[0] == undefined){ prices = Array(prices); } const sb = new StringBuilder(); for (let i = 0; i < Math.min(prices.length, 5); i++){ sb.append(`${prices[i].amount} x ${formatNumberWithCommas(prices[i].price)}`); if (i < Math.min(prices.length+1, 5)){ sb.append('<br>'); } } return sb.toString(); } 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 formatNumberWithCommas(number) { return new Intl.NumberFormat('en-US').format(number); } function checkApiKey(checkExisting = true) { if (!checkExisting || apiKey === null || apiKey.indexOf('PDA-APIKEY') > -1 || 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("[TornMarketFiller] User cancelled the Api Key input."); } } } function getCurrentPage(){ if (window.location.href.indexOf('#/addListing') > -1){ return pages.AddItems; } else if (window.location.href.indexOf('#/viewListing') > -1){ return pages.ViewItems; } else { return pages.Other; } } const startHold = () => { holdTimer = setTimeout(() => { setPriceDelta(); checkApiKey(false); }, 2000); }; const cancelHold = () => { clearTimeout(holdTimer); }; class StringBuilder { constructor() { this.parts = []; } append(str) { this.parts.push(str); return this; } toString() { return this.parts.join(''); } } })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址