// ==UserScript== // @name Neopets - Trading Post Item Pricer // @namespace https://scriptneo.com/ // @version 2.7.0 // @description Trading Post item pricing buttons that undercut the lowest Instant Buy by 1 NP. // @author Scriptneo // @match https://www.neopets.com/* // @grant none // @run-at document-end // @downloadURL https://www.scriptneo.com/scripts/download.php?id=42 // @updateURL https://www.scriptneo.com/scripts/download.php?id=42 // ==/UserScript== (function () { "use strict"; const API_URL = "https://www.neopets.com/np-templates/ajax/island/tradingpost/tradingpost-list.php"; const TP_INVENTORY_URL = "https://www.neopets.com/np-templates/ajax/island/tradingpost/inventory-list.php"; const TP_PROCESS_URL = "https://www.neopets.com/np-templates/ajax/island/tradingpost/process-tradingpost.php"; const DEFAULTS = { sort: "newest", minPrice: 1, maxPrice: 0, priceMode: "range", nextPricePercent: 60, openLinksNewTab: true, autoOpenOnTradingPost: true, autoSearchOnEnter: true, mode: "single", batchDelayMs: 350, batchMaxItems: 10000, batchReturn: "best", batchSort: "price", criteria: "item_exact", sellerUndercutNP: 1, sellerListInstantBuy: true, sellerWishlistTemplate: "Lowest TP Instant Buy - {UNDERCUT} NP" }; let state = { running: false, abort: null, isOpen: false, drag: { active: false, offsetX: 0, offsetY: 0 }, liveBatchRows: [], liveBatchStats: { processed: 0, totalItems: 0, foundItems: 0, totalIBLots: 0 } }; function safeText(s) { return String(s ?? ""); } function formatNP(n) { const num = Number(n || 0); if (!Number.isFinite(num)) return "0"; return num.toLocaleString("en-US"); } function el(tag, attrs = {}, children = []) { const node = document.createElement(tag); for (const [k, v] of Object.entries(attrs)) { if (k === "class") { node.className = v; } else if (k === "style") { node.setAttribute("style", v); } else if (k.startsWith("on") && typeof v === "function") { node.addEventListener(k.slice(2), v); } else { node.setAttribute(k, String(v)); } } for (const child of children) { if (child == null) continue; if (typeof child === "string") node.appendChild(document.createTextNode(child)); else node.appendChild(child); } return node; } function sleep(ms, signal) { return new Promise((resolve, reject) => { const t = setTimeout(resolve, ms); if (signal) { signal.addEventListener("abort", () => { clearTimeout(t); reject(new Error("Aborted")); }, { once: true }); } }); } function stopRun() { if (state.abort) { try { state.abort.abort(); } catch (e) {} } state.abort = null; state.running = false; setBusy(false); } function clamp(n, min, max) { return Math.max(min, Math.min(max, n)); } function isTradingPostPage() { return /\/island\/tradingpost\.phtml/i.test(location.pathname); } function setBusy(busy) { const btn1 = document.getElementById("tp-ibf-search"); const btn2 = document.getElementById("tp-ibf-batch-run"); const btn3 = document.getElementById("tp-ibf-seller-list"); if (btn1) btn1.disabled = !!busy; if (btn2) btn2.disabled = !!busy; if (btn3) btn3.disabled = !!busy; } async function tpSearchExactItem(itemName, signal) { const payload = { type: "browse", criteria: DEFAULTS.criteria, search_string: itemName, sort: DEFAULTS.sort, page: 1 }; const res = await fetch(API_URL, { method: "POST", headers: { "Content-Type": "application/json", "X-Requested-With": "XMLHttpRequest" }, credentials: "include", body: JSON.stringify(payload), signal }); if (!res.ok) { throw new Error("HTTP " + res.status + " " + res.statusText); } const data = await res.json(); if (!data || data.success !== true) { throw new Error("API returned success=false"); } return data; } function injectStylesOnce() { if (document.getElementById("tp-ibf-style")) return; const css = ` #tp-ibf-launcher{ position:fixed;right:16px;bottom:16px;z-index:999999; border:1px solid #1f6feb;background:#1f6feb;color:#fff;border-radius:999px; padding:10px 14px;font-family:system-ui,-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif; font-size:13px;font-weight:900;cursor:pointer;box-shadow:0 10px 25px rgba(0,0,0,0.22); } #tp-ibf-backdrop{position:fixed;inset:0;background:rgba(0,0,0,0.35);z-index:999998;} #tp-ibf-modal{ position:fixed;left:50%;top:14%;transform:translateX(-50%); width:560px;max-width:calc(100vw - 32px);max-height:calc(100vh - 32px); background:#fff;border:1px solid #d0d7de;border-radius:14px; box-shadow:0 18px 50px rgba(0,0,0,0.26);z-index:999999;overflow:hidden; font-family:system-ui,-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif; } #tp-ibf-header{ display:flex;align-items:center;justify-content:space-between; padding:12px;border-bottom:1px solid #eaeef2;background:#f6f8fa; user-select:none;cursor:move; } #tp-ibf-title{display:flex;flex-direction:column;gap:2px;} #tp-ibf-title .h1{font-weight:950;font-size:14px;color:#111;line-height:1.1;} #tp-ibf-title .h2{font-size:12px;color:#57606a;} #tp-ibf-header .btns{display:flex;gap:8px;align-items:center;} .tp-ibf-btn{ border:1px solid #d0d7de;background:#fff;border-radius:10px; padding:6px 10px;cursor:pointer;font-size:12px;font-weight:900; } .tp-ibf-body{padding:12px;overflow:auto;max-height:calc(100vh - 200px);} .tp-ibf-row{display:flex;gap:8px;align-items:center;margin-bottom:10px;} .tp-ibf-input{ flex:1;border:1px solid #d0d7de;border-radius:12px;padding:10px 12px; font-size:13px;outline:none; } .tp-ibf-search{ border:1px solid #1f6feb;background:#1f6feb;color:#fff;border-radius:12px; padding:10px 12px;font-size:13px;font-weight:950;cursor:pointer;white-space:nowrap; } .tp-ibf-search:disabled{opacity:0.6;cursor:not-allowed;} .tp-ibf-box{border:1px solid #eaeef2;border-radius:12px;padding:10px;margin-bottom:10px;background:#fff;} .tp-ibf-grid{display:flex;gap:10px;flex-wrap:wrap;align-items:flex-end;} .tp-ibf-field{display:flex;flex-direction:column;gap:6px;min-width:0;} .tp-ibf-label{font-size:11px;color:#57606a;font-weight:900;} .tp-ibf-num{ width:160px;border:1px solid #d0d7de;border-radius:10px;padding:8px 10px; font-size:12px;outline:none; } .tp-ibf-wide{width:220px;} .tp-ibf-help{font-size:11px;color:#57606a;line-height:1.35;margin-top:6px;} .tp-ibf-check{ display:flex;gap:8px;align-items:center;font-size:12px;color:#24292f; cursor:pointer;user-select:none; } .tp-ibf-tabs{display:flex;gap:8px;flex-wrap:wrap;align-items:center;margin-bottom:10px;} .tp-ibf-tab{ border:1px solid #d0d7de;background:#fff;border-radius:999px;padding:6px 10px; cursor:pointer;font-size:12px;font-weight:950;color:#24292f; } .tp-ibf-tab.active{border-color:#1f6feb;color:#1f6feb;} #tp-ibf-status{margin:8px 0 10px 0;font-size:12px;color:#57606a;} #tp-ibf-results .summary{font-size:12px;color:#24292f;font-weight:950;margin-bottom:10px;} .tp-ibf-card{border:1px solid #d0d7de;border-radius:14px;padding:12px;background:#fff;margin-bottom:10px;} .tp-ibf-live-empty{ border:1px dashed #d0d7de;background:#f6f8fa;color:#57606a;border-radius:12px; padding:12px;font-size:12px;line-height:1.35;margin-bottom:10px; } .tp-ibf-live-found{ animation:tpIbfFoundFlash 0.8s ease-out 1; } @keyframes tpIbfFoundFlash{ 0%{box-shadow:0 0 0 3px rgba(26,127,55,0.22);} 100%{box-shadow:none;} } .tp-ibf-top{display:flex;justify-content:space-between;gap:10px;align-items:flex-start;} .tp-ibf-lotid{font-weight:950;font-size:13px;color:#111;word-break:break-word;} .tp-ibf-meta{font-size:12px;color:#57606a;display:flex;gap:8px;flex-wrap:wrap;align-items:center;margin-top:4px;} .tp-ibf-badge{display:inline-block;padding:2px 8px;border-radius:999px;font-size:11px;font-weight:900;border:1px solid rgba(0,0,0,0.06);} .tp-ibf-price{font-weight:950;font-size:14px;color:#1a7f37;text-align:right;} .tp-ibf-open{ display:inline-block;text-decoration:none;border:1px solid #1f6feb;background:#1f6feb;color:#fff; border-radius:12px;padding:8px 10px;font-size:12px;font-weight:950;margin-top:6px;float:right; } .tp-ibf-items{margin-top:10px;} .tp-ibf-items .ttl{font-size:11px;color:#57606a;font-weight:950;margin-bottom:6px;} .tp-ibf-items .list{border:1px solid #eaeef2;border-radius:12px;padding:10px;background:#fff;} .tp-ibf-items .line{font-size:12px;color:#24292f;line-height:1.35;margin:2px 0;word-break:break-word;} .tp-ibf-error{border:1px solid #ffebe9;background:#fff1f0;color:#cf222e;border-radius:12px;padding:10px;} .tp-ibf-error .t1{font-weight:950;margin-bottom:4px;} .tp-ibf-error .t2{font-size:12px;line-height:1.35;} .tp-ibf-mini{ font-size:12px;color:#57606a;line-height:1.35; border:1px solid #eaeef2;background:#f6f8fa;border-radius:12px;padding:10px;margin-bottom:10px; } .tp-ibf-smallsel{ border:1px solid #d0d7de;background:#fff;border-radius:10px;padding:8px 10px; font-size:12px;outline:none; } .tp-ibf-seller-panel{ border:1px solid #d0d7de;background:#f6f8fa;border-radius:14px;padding:10px;margin-bottom:10px; } .tp-ibf-seller-title{ font-size:13px;font-weight:950;color:#111;margin-bottom:8px; } .tp-ibf-seller-actions{ display:flex;gap:8px;align-items:center;justify-content:space-between;flex-wrap:wrap;margin-top:8px; } .tp-ibf-success{ border:1px solid #aceebb;background:#dafbe1;color:#116329;border-radius:12px;padding:10px;font-size:12px;line-height:1.35; } .tp-ibf-warnbox{ border:1px solid #fff8c5;background:#fff8c5;color:#7d4e00;border-radius:12px;padding:10px;font-size:12px;line-height:1.35;margin-bottom:10px; } .tp-ibf-inline-wrap{ display:flex;flex-direction:column;align-items:center;gap:4px;margin-top:6px; } .tp-ibf-inline-price-btn{ border:1px solid #1f6feb;background:#1f6feb;color:#fff;border-radius:999px; padding:4px 8px;font-size:10px;font-weight:950;line-height:1.1;cursor:pointer; box-shadow:0 2px 7px rgba(0,0,0,0.12); } .tp-ibf-inline-price-btn:hover{filter:brightness(0.96);} .tp-ibf-inline-price-btn:disabled{opacity:0.65;cursor:not-allowed;} .tp-ibf-inline-status{ font-size:10px;font-weight:900;line-height:1.2;text-align:center;max-width:100%;word-break:break-word; } .tp-ibf-inline-status.ok{color:#1a7f37;} .tp-ibf-inline-status.warn{color:#9a6700;} .tp-ibf-inline-status.err{color:#cf222e;} `.trim(); const style = document.createElement("style"); style.id = "tp-ibf-style"; style.textContent = css; document.head.appendChild(style); } function buildLauncher() { if (document.getElementById("tp-ibf-launcher")) return; const btn = document.createElement("button"); btn.id = "tp-ibf-launcher"; btn.type = "button"; btn.textContent = "Instant Buy Finder"; btn.addEventListener("click", () => openModal()); document.body.appendChild(btn); } function buildModal() { if (document.getElementById("tp-ibf-modal")) return; const backdrop = el("div", { id: "tp-ibf-backdrop" }); backdrop.addEventListener("click", () => closeModal()); const modal = el("div", { id: "tp-ibf-modal", role: "dialog", "aria-modal": "true" }); const header = el("div", { id: "tp-ibf-header" }, [ el("div", { id: "tp-ibf-title" }, [ el("div", { class: "h1" }, ["Instant Buy Finder"]), el("div", { class: "h2" }, ["Exact match only. Instant Buy only. Batch results appear live."]) ]), el("div", { class: "btns" }, [ el("button", { class: "tp-ibf-btn", id: "tp-ibf-stop", type: "button" }, ["Stop"]), el("button", { class: "tp-ibf-btn", id: "tp-ibf-reset", type: "button" }, ["Reset"]), el("button", { class: "tp-ibf-btn", id: "tp-ibf-close", type: "button" }, ["Close"]) ]) ]); const tabs = el("div", { class: "tp-ibf-tabs" }, [ el("button", { id: "tp-ibf-tab-single", class: "tp-ibf-tab", type: "button" }, ["Single mode"]), el("button", { id: "tp-ibf-tab-batch", class: "tp-ibf-tab", type: "button" }, ["Batch mode"]) ]); const singleHint = el("div", { id: "tp-ibf-hint-single", class: "tp-ibf-mini" }, [ "Single mode: type the exact item name. Example: \"Varia Stamp\" will not match \"Big Bang Varia Stamp\"." ]); const batchHint = el("div", { id: "tp-ibf-hint-batch", class: "tp-ibf-mini", style: "display:none;" }, [ "Batch mode: one exact item name per line. Found lots appear immediately as each item finishes searching." ]); const singleRow = el("div", { id: "tp-ibf-row-single", class: "tp-ibf-row" }, [ el("input", { id: "tp-ibf-query", class: "tp-ibf-input", type: "text", placeholder: "Exact item name, for example: Varia Stamp" }), el("button", { id: "tp-ibf-search", class: "tp-ibf-search", type: "button" }, ["Search"]) ]); const batchRow = el("div", { id: "tp-ibf-row-batch", style: "display:none;margin-bottom:10px;" }, [ el("textarea", { id: "tp-ibf-batch", style: "width:100%;min-height:120px;border:1px solid #d0d7de;border-radius:12px;padding:10px 12px;font-size:13px;outline:none;resize:vertical;" }, [""]), el("div", { style: "display:flex;gap:8px;align-items:center;justify-content:space-between;margin-top:8px;flex-wrap:wrap;" }, [ el("div", { style: "display:flex;gap:8px;align-items:center;flex-wrap:wrap;" }, [ el("div", { class: "tp-ibf-field" }, [ el("div", { class: "tp-ibf-label" }, ["Return"]), el("select", { id: "tp-ibf-batch-return", class: "tp-ibf-smallsel" }, [ el("option", { value: "best" }, ["Best (cheapest per item)"]), el("option", { value: "all" }, ["All lots (can be long)"]) ]) ]), el("div", { class: "tp-ibf-field" }, [ el("div", { class: "tp-ibf-label" }, ["Sort batch by"]), el("select", { id: "tp-ibf-batch-sort", class: "tp-ibf-smallsel" }, [ el("option", { value: "price" }, ["Price (low to high)"]), el("option", { value: "name" }, ["Item name (A-Z)"]) ]) ]) ]), el("button", { id: "tp-ibf-batch-run", class: "tp-ibf-search", type: "button" }, ["Run batch"]) ]) ]); const settingsBox = el("div", { class: "tp-ibf-box" }, [ el("div", { class: "tp-ibf-grid" }, [ el("div", { class: "tp-ibf-field" }, [ el("div", { class: "tp-ibf-label" }, ["Min NP"]), el("input", { id: "tp-ibf-minprice", class: "tp-ibf-num", type: "number", min: "0", value: String(DEFAULTS.minPrice) }) ]), el("div", { class: "tp-ibf-field" }, [ el("div", { class: "tp-ibf-label" }, ["Max NP"]), el("input", { id: "tp-ibf-maxprice", class: "tp-ibf-num", type: "number", min: "0", value: String(DEFAULTS.maxPrice) }) ]), el("div", { class: "tp-ibf-field" }, [ el("div", { class: "tp-ibf-label" }, ["Pricing filter"]), el("select", { id: "tp-ibf-price-mode", class: "tp-ibf-smallsel tp-ibf-wide" }, [ el("option", { value: "range" }, ["Min/Max NP only"]), el("option", { value: "next_percent" }, ["Deal % vs next higher price"]) ]) ]), el("div", { class: "tp-ibf-field" }, [ el("div", { class: "tp-ibf-label" }, ["Max % of next price"]), el("input", { id: "tp-ibf-next-percent", class: "tp-ibf-num", type: "number", min: "1", max: "100", value: String(DEFAULTS.nextPricePercent) }), el("div", { class: "tp-ibf-help" }, [ "Example: 60 means show 600k only if the next higher lot is 1m or more." ]) ]), el("label", { class: "tp-ibf-check", style: "margin-top:18px;" }, [ el("input", { id: "tp-ibf-newtab", type: "checkbox" }), el("span", {}, ["Open links in new tab"]) ]) ]) ]); const sellerPanel = el("div", { class: "tp-ibf-seller-panel" }, [ el("div", { class: "tp-ibf-seller-title" }, ["Seller Auto-Lister"]), el("div", { class: "tp-ibf-warnbox" }, [ "This searches the exact item, calculates the lowest Instant Buy price, subtracts your undercut, finds the matching item in your inventory, then creates the lot after you confirm." ]), el("div", { class: "tp-ibf-grid" }, [ el("div", { class: "tp-ibf-field", style: "flex:1;min-width:220px;" }, [ el("div", { class: "tp-ibf-label" }, ["Item name to list"]), el("input", { id: "tp-ibf-seller-item", class: "tp-ibf-input", type: "text", placeholder: "Exact inventory item name" }) ]), el("div", { class: "tp-ibf-field" }, [ el("div", { class: "tp-ibf-label" }, ["Lowest minus NP"]), el("input", { id: "tp-ibf-seller-undercut", class: "tp-ibf-num", type: "number", min: "0", value: String(DEFAULTS.sellerUndercutNP) }) ]), el("label", { class: "tp-ibf-check", style: "margin-top:18px;" }, [ el("input", { id: "tp-ibf-seller-instant", type: "checkbox" }), el("span", {}, ["Enable Instant Buy"]) ]), el("div", { class: "tp-ibf-field", style: "flex:1;min-width:220px;" }, [ el("div", { class: "tp-ibf-label" }, ["Wishlist text"]), el("input", { id: "tp-ibf-seller-wishlist", class: "tp-ibf-input", type: "text", value: DEFAULTS.sellerWishlistTemplate }), el("div", { class: "tp-ibf-help" }, [ "Tokens: {PRICE}, {LOWEST}, {UNDERCUT}, {ITEM}. Leave blank if you only want Instant Buy." ]) ]) ]), el("div", { class: "tp-ibf-seller-actions" }, [ el("button", { id: "tp-ibf-seller-copy-query", class: "tp-ibf-btn", type: "button" }, ["Use current search item"]), el("button", { id: "tp-ibf-seller-list", class: "tp-ibf-search", type: "button" }, ["Find Lowest & List Item"]) ]) ]); const body = el("div", { class: "tp-ibf-body" }, [ tabs, singleHint, batchHint, singleRow, batchRow, settingsBox, sellerPanel, el("div", { id: "tp-ibf-status" }, ["Idle."]), el("div", { id: "tp-ibf-results" }) ]); modal.appendChild(header); modal.appendChild(body); document.body.appendChild(backdrop); document.body.appendChild(modal); const newTab = document.getElementById("tp-ibf-newtab"); if (newTab) newTab.checked = DEFAULTS.openLinksNewTab; const batchReturn = document.getElementById("tp-ibf-batch-return"); if (batchReturn) batchReturn.value = DEFAULTS.batchReturn; const batchSort = document.getElementById("tp-ibf-batch-sort"); if (batchSort) batchSort.value = DEFAULTS.batchSort; const priceMode = document.getElementById("tp-ibf-price-mode"); if (priceMode) priceMode.value = DEFAULTS.priceMode; const sellerInstant = document.getElementById("tp-ibf-seller-instant"); if (sellerInstant) sellerInstant.checked = DEFAULTS.sellerListInstantBuy; setMode(DEFAULTS.mode); document.getElementById("tp-ibf-close")?.addEventListener("click", () => closeModal()); document.getElementById("tp-ibf-reset")?.addEventListener("click", () => resetUI()); document.getElementById("tp-ibf-stop")?.addEventListener("click", () => { stopRun(); setStatus("Stopped.", "warn"); }); document.getElementById("tp-ibf-tab-single")?.addEventListener("click", () => setMode("single")); document.getElementById("tp-ibf-tab-batch")?.addEventListener("click", () => setMode("batch")); document.getElementById("tp-ibf-search")?.addEventListener("click", () => runSingle()); document.getElementById("tp-ibf-batch-run")?.addEventListener("click", () => runBatch()); document.getElementById("tp-ibf-seller-list")?.addEventListener("click", () => runAutoList()); document.getElementById("tp-ibf-seller-copy-query")?.addEventListener("click", () => { const query = (document.getElementById("tp-ibf-query")?.value ?? "").trim(); if (query) { const item = document.getElementById("tp-ibf-seller-item"); if (item) item.value = query; setStatus("Copied current search item into Seller Auto-Lister.", "ok"); } else { setStatus("Enter a Single mode item first, then click Use current search item.", "err"); } }); document.getElementById("tp-ibf-query")?.addEventListener("keydown", (e) => { if (!DEFAULTS.autoSearchOnEnter) return; if (e.key === "Enter") { e.preventDefault(); runSingle(); } }); enableDrag(modal, header); } function openModal() { injectStylesOnce(); buildModal(); const backdrop = document.getElementById("tp-ibf-backdrop"); const modal = document.getElementById("tp-ibf-modal"); if (backdrop) backdrop.style.display = "block"; if (modal) modal.style.display = "block"; state.isOpen = true; setTimeout(() => { if (getMode() === "single") { document.getElementById("tp-ibf-query")?.focus(); } else { document.getElementById("tp-ibf-batch")?.focus(); } }, 50); } function closeModal() { stopRun(); const backdrop = document.getElementById("tp-ibf-backdrop"); const modal = document.getElementById("tp-ibf-modal"); if (backdrop) backdrop.style.display = "none"; if (modal) modal.style.display = "none"; state.isOpen = false; } function resetUI() { const q = document.getElementById("tp-ibf-query"); const ta = document.getElementById("tp-ibf-batch"); const minp = document.getElementById("tp-ibf-minprice"); const maxp = document.getElementById("tp-ibf-maxprice"); const priceMode = document.getElementById("tp-ibf-price-mode"); const nextPercent = document.getElementById("tp-ibf-next-percent"); const results = document.getElementById("tp-ibf-results"); if (q) q.value = ""; if (ta) ta.value = ""; if (minp) minp.value = String(DEFAULTS.minPrice); if (maxp) maxp.value = String(DEFAULTS.maxPrice); if (priceMode) priceMode.value = DEFAULTS.priceMode; if (nextPercent) nextPercent.value = String(DEFAULTS.nextPricePercent); if (results) results.innerHTML = ""; state.liveBatchRows = []; state.liveBatchStats = { processed: 0, totalItems: 0, foundItems: 0, totalIBLots: 0 }; setStatus("Idle.", "warn"); } function setStatus(text, kind) { const status = document.getElementById("tp-ibf-status"); if (!status) return; let color = "#57606a"; if (kind === "ok") color = "#1a7f37"; if (kind === "warn") color = "#9a6700"; if (kind === "err") color = "#cf222e"; status.textContent = text; status.style.color = color; } function badge(text, bg, fg) { return el("span", { class: "tp-ibf-badge", style: "background:" + bg + ";color:" + fg + ";" }, [text]); } function setMode(mode) { const m = mode === "batch" ? "batch" : "single"; DEFAULTS.mode = m; const tabSingle = document.getElementById("tp-ibf-tab-single"); const tabBatch = document.getElementById("tp-ibf-tab-batch"); const hintSingle = document.getElementById("tp-ibf-hint-single"); const hintBatch = document.getElementById("tp-ibf-hint-batch"); const rowSingle = document.getElementById("tp-ibf-row-single"); const rowBatch = document.getElementById("tp-ibf-row-batch"); if (tabSingle) tabSingle.classList.toggle("active", m === "single"); if (tabBatch) tabBatch.classList.toggle("active", m === "batch"); if (hintSingle) hintSingle.style.display = m === "single" ? "block" : "none"; if (hintBatch) hintBatch.style.display = m === "batch" ? "block" : "none"; if (rowSingle) rowSingle.style.display = m === "single" ? "flex" : "none"; if (rowBatch) rowBatch.style.display = m === "batch" ? "block" : "none"; const results = document.getElementById("tp-ibf-results"); if (results) results.innerHTML = ""; setStatus("Mode: " + (m === "single" ? "Single" : "Batch") + ".", "warn"); } function getMode() { return DEFAULTS.mode === "batch" ? "batch" : "single"; } function enableDrag(modal, handle) { if (!modal || !handle) return; const onPointerDown = (e) => { if (e.button !== 0 && e.pointerType !== "touch") return; state.drag.active = true; const rect = modal.getBoundingClientRect(); modal.style.transform = "none"; modal.style.left = rect.left + "px"; modal.style.top = rect.top + "px"; state.drag.offsetX = e.clientX - rect.left; state.drag.offsetY = e.clientY - rect.top; try { handle.setPointerCapture(e.pointerId); } catch (err) {} e.preventDefault(); }; const onPointerMove = (e) => { if (!state.drag.active) return; const modalW = modal.offsetWidth || 560; const modalH = modal.offsetHeight || 320; const x = clamp(e.clientX - state.drag.offsetX, 8, window.innerWidth - modalW - 8); const y = clamp(e.clientY - state.drag.offsetY, 8, window.innerHeight - modalH - 8); modal.style.left = x + "px"; modal.style.top = y + "px"; }; const onPointerUp = (e) => { if (!state.drag.active) return; state.drag.active = false; try { handle.releasePointerCapture(e.pointerId); } catch (err) {} }; handle.addEventListener("pointerdown", onPointerDown); window.addEventListener("pointermove", onPointerMove); window.addEventListener("pointerup", onPointerUp); } function readPriceFilters() { const minPrice = Number(document.getElementById("tp-ibf-minprice")?.value ?? DEFAULTS.minPrice) || 0; const maxPrice = Number(document.getElementById("tp-ibf-maxprice")?.value ?? DEFAULTS.maxPrice) || 0; const priceMode = document.getElementById("tp-ibf-price-mode")?.value || DEFAULTS.priceMode; const nextPricePercentRaw = Number(document.getElementById("tp-ibf-next-percent")?.value ?? DEFAULTS.nextPricePercent) || DEFAULTS.nextPricePercent; const nextPricePercent = clamp(nextPricePercentRaw, 1, 100); return { minPrice, maxPrice, priceMode: priceMode === "next_percent" ? "next_percent" : "range", nextPricePercent }; } function decorateNextPriceDeals(sortedLots, nextPricePercent) { const pct = clamp(Number(nextPricePercent || DEFAULTS.nextPricePercent), 1, 100); const multiplier = pct / 100; for (let i = 0; i < sortedLots.length; i++) { const lot = sortedLots[i]; const nextLot = sortedLots[i + 1] || null; const price = Number(lot.instant_buy_amount || 0); const nextPrice = Number(nextLot?.instant_buy_amount || 0); delete lot._tpIbfNextPrice; delete lot._tpIbfDealPercent; delete lot._tpIbfDealSavings; if (nextLot && nextPrice > 0) { lot._tpIbfNextPrice = nextPrice; lot._tpIbfDealPercent = Math.round((price / nextPrice) * 1000) / 10; lot._tpIbfDealSavings = Math.max(0, nextPrice - price); lot._tpIbfPassesNextPercent = price <= (nextPrice * multiplier); } else { lot._tpIbfPassesNextPercent = false; } } return sortedLots; } function filterAndSortInstantBuy(lots, minPrice, maxPrice, priceMode, nextPricePercent) { let ibLots = (Array.isArray(lots) ? lots : []).filter(l => Number(l.instant_buy_amount || 0) > 0); ibLots = ibLots.filter(l => { const p = Number(l.instant_buy_amount || 0); if (p < minPrice) return false; if (maxPrice > 0 && p > maxPrice) return false; return true; }); ibLots.sort((a, b) => { const ap = Number(a.instant_buy_amount || 0); const bp = Number(b.instant_buy_amount || 0); if (ap !== bp) return ap - bp; return Number(b.lot_id || 0) - Number(a.lot_id || 0); }); ibLots = decorateNextPriceDeals(ibLots, nextPricePercent); if (priceMode === "next_percent") { ibLots = ibLots.filter(l => !!l._tpIbfPassesNextPercent); } return ibLots; } function renderNoLots(label) { const results = document.getElementById("tp-ibf-results"); if (!results) return; results.innerHTML = ""; results.appendChild(el("div", { class: "tp-ibf-error" }, [ el("div", { class: "t1" }, ["No Instant Buy lots found"]), el("div", { class: "t2" }, [ "Exact search: \"" + safeText(label) + "\"." ]) ])); } function renderSingleResults(lots, query) { const results = document.getElementById("tp-ibf-results"); if (!results) return; results.innerHTML = ""; if (!lots.length) { renderNoLots(query); return; } results.appendChild(el("div", { class: "summary" }, [ "Found " + lots.length + " Instant Buy lot" + (lots.length === 1 ? "" : "s") + " (low to high)" ])); for (const lot of lots) { results.appendChild(renderLotCard(lot, false)); } } function setupLiveBatchResults(totalItems) { const results = document.getElementById("tp-ibf-results"); if (!results) return; state.liveBatchRows = []; state.liveBatchStats = { processed: 0, totalItems, foundItems: 0, totalIBLots: 0 }; results.innerHTML = ""; results.appendChild(el("div", { id: "tp-ibf-batch-summary", class: "summary" }, [ "Batch started. Searching 0/" + totalItems + ". Found 0 item(s)." ])); results.appendChild(el("div", { id: "tp-ibf-batch-empty", class: "tp-ibf-live-empty" }, [ "No matching Instant Buy lots found yet. Results will appear here as soon as each item finishes." ])); results.appendChild(el("div", { id: "tp-ibf-batch-live-list" })); } function updateLiveBatchSummary() { const summary = document.getElementById("tp-ibf-batch-summary"); if (!summary) return; const stats = state.liveBatchStats; summary.textContent = "Batch progress: " + stats.processed + "/" + stats.totalItems + ". Found " + stats.foundItems + " item" + (stats.foundItems === 1 ? "" : "s") + " with " + stats.totalIBLots + " Instant Buy lot" + (stats.totalIBLots === 1 ? "" : "s") + "."; } function sortLiveBatchRows(modeSort) { state.liveBatchRows.sort((a, b) => { if (modeSort === "name") { return safeText(a.item).localeCompare(safeText(b.item)); } const aLot = a.bestLot || (Array.isArray(a.lots) ? a.lots[0] : null); const bLot = b.bestLot || (Array.isArray(b.lots) ? b.lots[0] : null); const ap = Number(aLot?.instant_buy_amount || 0); const bp = Number(bLot?.instant_buy_amount || 0); if (ap !== bp) return ap - bp; return safeText(a.item).localeCompare(safeText(b.item)); }); } function addLiveBatchRow(row, modeSort) { const empty = document.getElementById("tp-ibf-batch-empty"); const list = document.getElementById("tp-ibf-batch-live-list"); if (!list) return; if (empty) empty.style.display = "none"; state.liveBatchRows.push(row); sortLiveBatchRows(modeSort); list.innerHTML = ""; for (const liveRow of state.liveBatchRows) { const card = renderBatchCard(liveRow); card.classList.add("tp-ibf-live-found"); list.appendChild(card); } } function renderBatchResults(rows, label) { const results = document.getElementById("tp-ibf-results"); if (!results) return; results.innerHTML = ""; if (!rows.length) { renderNoLots(label); return; } results.appendChild(el("div", { class: "summary" }, [ "Batch results: " + rows.length + " item" + (rows.length === 1 ? "" : "s") ])); for (const row of rows) { results.appendChild(renderBatchCard(row)); } } function renderBatchCard(row) { const modeReturn = document.getElementById("tp-ibf-batch-return")?.value || DEFAULTS.batchReturn; const card = el("div", { class: "tp-ibf-card" }, [ el("div", { class: "tp-ibf-lotid" }, [safeText(row.item)]) ]); if (modeReturn === "best") { card.appendChild(el("div", { style: "margin-top:8px;" }, [ renderLotCard(row.bestLot, true) ])); } else { card.appendChild(el("div", { style: "margin-top:8px;" }, [ el("div", { style: "font-size:12px;color:#57606a;font-weight:900;margin-bottom:6px;" }, [ "Lots (" + row.lots.length + ")" ]) ])); for (const lot of row.lots) { card.appendChild(renderLotCard(lot, true)); } } return card; } function renderLotCard(lot, compact) { const openNewTab = document.getElementById("tp-ibf-newtab")?.checked ?? true; const link = safeText(lot.link || ""); const href = link.startsWith("//") ? ("https:" + link) : (link.startsWith("http") ? link : ("https://www.neopets.com" + link.replace(/^\/+/, "/"))); const price = Number(lot.instant_buy_amount || 0); const items = (lot.items || []).map(it => { const amount = Number(it.amount || 1); const name = safeText(it.name); const sub = safeText(it.sub_name); return (amount > 1 ? (amount + "x ") : "") + name + (sub ? (" " + sub) : ""); }); const owner = safeText(lot.owner); const isPremium = !!lot.premium_owner; const outer = el("div", { class: compact ? "" : "tp-ibf-card", style: compact ? "border:1px solid #eaeef2;border-radius:12px;padding:10px;margin-top:8px;background:#fff;" : "" }, [ el("div", { class: "tp-ibf-top" }, [ el("div", {}, [ el("div", { class: "tp-ibf-lotid" }, ["Lot #" + safeText(lot.lot_id)]), el("div", { class: "tp-ibf-meta" }, [ el("span", {}, ["Owner: " + owner]), badge( isPremium ? "Premium" : "Standard", isPremium ? "#ddf4ff" : "#f6f8fa", isPremium ? "#0969da" : "#57606a" ), badge("Offers: " + safeText(lot.offer_count ?? 0), "#fff8c5", "#9a6700"), lot._tpIbfNextPrice ? badge("Next: " + formatNP(lot._tpIbfNextPrice) + " NP", "#f6f8fa", "#57606a") : null, lot._tpIbfDealPercent ? badge(lot._tpIbfDealPercent + "% of next", "#dafbe1", "#1a7f37") : null, lot._tpIbfDealSavings ? badge("Gap: " + formatNP(lot._tpIbfDealSavings) + " NP", "#ddf4ff", "#0969da") : null ]) ]), el("div", {}, [ el("div", { class: "tp-ibf-price" }, [formatNP(price) + " NP"]), el("a", { class: "tp-ibf-open", href, target: openNewTab ? "_blank" : "_self", rel: "noopener noreferrer" }, ["Open Lot"]) ]) ]), el("div", { class: "tp-ibf-items" }, [ el("div", { class: "ttl" }, ["Items"]), el("div", { class: "list" }, items.length ? items.map(s => el("div", { class: "line" }, [s])) : [el("div", { class: "line", style: "color:#57606a;" }, ["(No items listed)"])]) ]) ]); return outer; } function toRawNP(value) { const raw = safeText(value).replace(/[^\d]/g, ""); const n = Number(raw); if (!Number.isFinite(n)) return 0; return Math.max(0, Math.floor(n)); } function getRefCk() { try { if (typeof window.getCK === "function") { const ck = window.getCK(); if (ck) return String(ck); } } catch (e) {} const candidates = Array.from(document.scripts || []) .map(s => s.textContent || "") .join("\n"); const match = candidates.match(/function\s+getCK\s*\(\)\s*\{\s*return\s+['"]([^'"]+)['"]/i) || candidates.match(/["_']_ref_ck["_']\s*:\s*['"]([^'"]+)['"]/i); return match ? match[1] : ""; } async function tpLoadInventoryItems(signal) { const params = new URLSearchParams({ itemType: "", alpha: "newest" }); const res = await fetch(TP_INVENTORY_URL + "?" + params.toString(), { method: "GET", headers: { "X-Requested-With": "XMLHttpRequest" }, credentials: "include", signal }); if (!res.ok) { throw new Error("Inventory HTTP " + res.status + " " + res.statusText); } const data = await res.json(); if (!data || data.success !== true || !Array.isArray(data.items)) { throw new Error(data?.message || "Inventory API returned no items"); } return data.items; } function normalizeItemName(name) { return safeText(name).replace(/\s+/g, " ").trim().toLowerCase(); } function findInventoryItemByExactName(items, itemName) { const target = normalizeItemName(itemName); return items.find(item => normalizeItemName(item.itemName) === target) || items.find(item => normalizeItemName(item.itemName).includes(target)); } function makeWishlistText(template, data) { return safeText(template) .replaceAll("{PRICE}", formatNP(data.price)) .replaceAll("{LOWEST}", formatNP(data.lowest)) .replaceAll("{UNDERCUT}", formatNP(data.undercut)) .replaceAll("{ITEM}", safeText(data.itemName)) .slice(0, 500); } function renderSellerSuccess(itemName, price, result) { const results = document.getElementById("tp-ibf-results"); if (!results) return; results.innerHTML = ""; results.appendChild(el("div", { class: "tp-ibf-success" }, [ el("strong", {}, ["Lot created successfully."]), el("div", {}, [ "Listed \"" + safeText(itemName) + "\" with Instant Buy at " + formatNP(price) + " NP." ]), result?.data?.lot_id ? el("div", {}, ["Lot ID: " + safeText(result.data.lot_id)]) : null ])); } async function createTradingPostLotFromScript(selectedItemIds, wishlist, instantBuyAmount, signal) { const refCk = getRefCk(); if (!refCk) { throw new Error("Could not find _ref_ck. Open the Trading Post page once, then try again."); } const res = await fetch(TP_PROCESS_URL, { method: "POST", headers: { "Content-Type": "application/json", "X-Requested-With": "XMLHttpRequest" }, credentials: "include", body: JSON.stringify({ type: "create", selected_items: selectedItemIds, wishlist: wishlist, instant_buy_amount: instantBuyAmount ? String(instantBuyAmount) : null, _ref_ck: refCk }), signal }); if (!res.ok) { throw new Error("Create lot HTTP " + res.status + " " + res.statusText); } const result = await res.json(); if (!result || result.success !== true) { throw new Error(result?.error || result?.message || "Trading Post create returned success=false"); } return result; } function nativeSetValue(input, value) { if (!input) return false; const proto = input instanceof HTMLTextAreaElement ? HTMLTextAreaElement.prototype : HTMLInputElement.prototype; const descriptor = Object.getOwnPropertyDescriptor(proto, "value"); if (descriptor && typeof descriptor.set === "function") { descriptor.set.call(input, String(value)); } else { input.value = String(value); } input.dispatchEvent(new Event("input", { bubbles: true })); input.dispatchEvent(new Event("change", { bubbles: true })); return true; } function findInstantBuySection() { const sections = Array.from(document.querySelectorAll(".tp-section, section, div")); return sections.find(section => { const text = safeText(section.textContent).replace(/\s+/g, " ").trim(); return /Instant Buy/i.test(text) && /immediately purchase|Input the amount|Neopoints/i.test(text); }) || null; } function findInstantBuyAmountInput() { const byPlaceholder = Array.from(document.querySelectorAll("input, textarea")) .find(input => /input the amount/i.test(input.getAttribute("placeholder") || "")); if (byPlaceholder) return byPlaceholder; const section = findInstantBuySection(); if (!section) return null; return Array.from(section.querySelectorAll("input, textarea")) .find(input => { const type = safeText(input.getAttribute("type") || "text").toLowerCase(); return !["checkbox", "radio", "hidden", "submit", "button"].includes(type); }) || null; } function enableInstantBuyToggle() { const section = findInstantBuySection(); if (!section) return false; const checkbox = Array.from(section.querySelectorAll('input[type="checkbox"]'))[0] || null; if (checkbox) { if (!checkbox.checked) checkbox.click(); checkbox.dispatchEvent(new Event("change", { bubbles: true })); return true; } const switches = Array.from(section.querySelectorAll('[role="switch"], button, .toggle, [class*="toggle"], [class*="switch"]')); const likelySwitch = switches.find(node => { const text = safeText(node.textContent).trim(); const ariaChecked = node.getAttribute("aria-checked"); const cls = safeText(node.className); return ariaChecked !== "true" || /toggle|switch/i.test(cls) || text === ""; }) || null; if (likelySwitch) { const ariaChecked = likelySwitch.getAttribute("aria-checked"); const className = safeText(likelySwitch.className); const alreadyOn = ariaChecked === "true" || /active|checked|enabled|on/i.test(className); if (!alreadyOn) likelySwitch.click(); likelySwitch.dispatchEvent(new Event("change", { bubbles: true })); return true; } return false; } function selectTradingPostCard(card) { if (!card || card.classList.contains("bg-item-selection")) return; card.click(); } function setInlineStatus(card, message, type) { if (!card) return; const status = card.querySelector(".tp-ibf-inline-status"); if (!status) return; status.className = "tp-ibf-inline-status " + (type || "warn"); status.textContent = message; } async function priceTradingPostCard(card, button) { const nameEl = card?.querySelector(".tp-grid-item-name"); const itemName = safeText(nameEl?.textContent).replace(/\s+/g, " ").trim(); if (!itemName) { setInlineStatus(card, "Missing item name", "err"); return; } if (button) { button.disabled = true; button.textContent = "Pricing..."; } const controller = new AbortController(); try { setInlineStatus(card, "Searching lowest IB...", "warn"); const data = await tpSearchExactItem(itemName, controller.signal); const ibLots = filterAndSortInstantBuy(data.lots, 1, 0, "range", DEFAULTS.nextPricePercent); if (!ibLots.length) { setInlineStatus(card, "No IB found", "err"); return; } const lowest = Number(ibLots[0].instant_buy_amount || 0); const price = Math.max(1, lowest - 1); selectTradingPostCard(card); enableInstantBuyToggle(); await sleep(80); const amountInput = findInstantBuyAmountInput(); if (!amountInput) { setInlineStatus(card, "Could not find IB box", "err"); return; } if (amountInput.disabled) { enableInstantBuyToggle(); await sleep(120); } nativeSetValue(amountInput, String(price)); amountInput.focus(); amountInput.dispatchEvent(new Event("blur", { bubbles: true })); setInlineStatus(card, "Set " + formatNP(price) + " NP", "ok"); } catch (e) { const msg = e && e.message ? e.message : "Pricing failed"; setInlineStatus(card, msg, "err"); } finally { if (button) { button.disabled = false; button.textContent = "Price -1 NP"; } } } function injectInlineTradingPostPriceButtons() { const cards = Array.from(document.querySelectorAll(".tp-grid-item")); for (const card of cards) { if (card.dataset.tpIbfInlinePricer === "1") continue; const nameEl = card.querySelector(".tp-grid-item-name"); if (!nameEl) continue; card.dataset.tpIbfInlinePricer = "1"; const wrap = document.createElement("div"); wrap.className = "tp-ibf-inline-wrap"; const btn = document.createElement("button"); btn.type = "button"; btn.className = "tp-ibf-inline-price-btn"; btn.textContent = "Price -1 NP"; btn.title = "Find the lowest exact Instant Buy price, undercut by 1 NP, select this item, enable Instant Buy, and fill the price."; const status = document.createElement("div"); status.className = "tp-ibf-inline-status"; status.textContent = ""; btn.addEventListener("click", (e) => { e.preventDefault(); e.stopPropagation(); priceTradingPostCard(card, btn); }); wrap.appendChild(btn); wrap.appendChild(status); nameEl.insertAdjacentElement("afterend", wrap); } } function startInlineTradingPostPricerObserver() { injectInlineTradingPostPriceButtons(); const observer = new MutationObserver(() => { injectInlineTradingPostPriceButtons(); }); observer.observe(document.body, { childList: true, subtree: true }); } async function runAutoList() { const itemName = (document.getElementById("tp-ibf-seller-item")?.value ?? "").trim(); const undercut = toRawNP(document.getElementById("tp-ibf-seller-undercut")?.value ?? DEFAULTS.sellerUndercutNP); const instantEnabled = document.getElementById("tp-ibf-seller-instant")?.checked ?? true; const wishlistTemplate = document.getElementById("tp-ibf-seller-wishlist")?.value ?? DEFAULTS.sellerWishlistTemplate; if (!itemName) { setStatus("Enter the exact inventory item name you want to list.", "err"); return; } if (!instantEnabled) { setStatus("Instant Buy must be enabled for lowest minus pricing.", "err"); return; } stopRun(); state.running = true; state.abort = new AbortController(); setBusy(true); try { setStatus("Searching Trading Post for the lowest Instant Buy price for \"" + itemName + "\" ...", "warn"); const data = await tpSearchExactItem(itemName, state.abort.signal); const ibLots = filterAndSortInstantBuy(data.lots, 1, 0, "range", DEFAULTS.nextPricePercent); if (!ibLots.length) { setStatus("No Instant Buy lots found for exact \"" + itemName + "\". I did not list your item.", "err"); renderNoLots(itemName); return; } const lowest = Number(ibLots[0].instant_buy_amount || 0); const listPrice = Math.max(1, lowest - undercut); renderSingleResults(ibLots, itemName); setStatus("Lowest Instant Buy is " + formatNP(lowest) + " NP. Loading your inventory ...", "warn"); const inventoryItems = await tpLoadInventoryItems(state.abort.signal); const invItem = findInventoryItemByExactName(inventoryItems, itemName); if (!invItem || !invItem.objId) { setStatus("Could not find \"" + itemName + "\" in your inventory. I did not list anything.", "err"); return; } const wishlist = makeWishlistText(wishlistTemplate, { itemName, lowest, undercut, price: listPrice }); const ok = window.confirm( "Create Trading Post lot?\n\n" + "Item: " + safeText(invItem.itemName) + "\n" + "Inventory ID: " + safeText(invItem.objId) + "\n" + "Lowest TP Instant Buy: " + formatNP(lowest) + " NP\n" + "Your Instant Buy price: " + formatNP(listPrice) + " NP\n\n" + "This will submit the lot now." ); if (!ok) { setStatus("Cancelled before listing. Nothing was submitted.", "warn"); return; } setStatus("Creating lot for \"" + safeText(invItem.itemName) + "\" at " + formatNP(listPrice) + " NP ...", "warn"); const result = await createTradingPostLotFromScript([invItem.objId], wishlist, listPrice, state.abort.signal); setStatus("Lot created. Listed at " + formatNP(listPrice) + " NP.", "ok"); renderSellerSuccess(invItem.itemName, listPrice, result); } catch (e) { const msg = e && e.message ? e.message : "Auto-list failed"; setStatus("Auto-list error: " + msg + ".", "err"); } finally { state.running = false; state.abort = null; setBusy(false); } } async function runSingle() { const query = (document.getElementById("tp-ibf-query")?.value ?? "").trim(); const { minPrice, maxPrice, priceMode, nextPricePercent } = readPriceFilters(); if (!query) { setStatus("Enter an item name to search.", "err"); renderNoLots(""); return; } stopRun(); state.running = true; state.abort = new AbortController(); setBusy(true); setStatus("Exact searching \"" + query + "\" ...", "warn"); try { const data = await tpSearchExactItem(query, state.abort.signal); const ibLots = filterAndSortInstantBuy(data.lots, minPrice, maxPrice, priceMode, nextPricePercent); if (!ibLots.length) { setStatus("No Instant Buy lots found for exact \"" + query + "\".", "err"); } else { setStatus( "Done. Found " + ibLots.length + " Instant Buy lot" + (ibLots.length === 1 ? "" : "s") + ". Sorted low to high.", "ok" ); } renderSingleResults(ibLots, query); } catch (e) { const msg = e && e.message ? e.message : "Request failed"; setStatus("Error: " + msg + ".", "err"); renderNoLots(query); } finally { state.running = false; state.abort = null; setBusy(false); } } function parseBatchLines(raw) { const lines = safeText(raw) .split(/\r?\n/g) .map(s => s.trim()) .filter(s => s.length > 0); const seen = new Set(); const out = []; for (const line of lines) { const key = line.toLowerCase(); if (seen.has(key)) continue; seen.add(key); out.push(line); } return out; } async function runBatch() { const raw = document.getElementById("tp-ibf-batch")?.value ?? ""; const items = parseBatchLines(raw); const { minPrice, maxPrice, priceMode, nextPricePercent } = readPriceFilters(); const modeReturn = document.getElementById("tp-ibf-batch-return")?.value || DEFAULTS.batchReturn; const modeSort = document.getElementById("tp-ibf-batch-sort")?.value || DEFAULTS.batchSort; if (!items.length) { setStatus("Add item names in Batch mode (one per line).", "err"); renderBatchResults([], "Empty list"); return; } if (items.length > DEFAULTS.batchMaxItems) { setStatus("Too many items. Limit is " + DEFAULTS.batchMaxItems + ".", "err"); renderBatchResults([], "Too many items"); return; } stopRun(); state.running = true; state.abort = new AbortController(); setBusy(true); setupLiveBatchResults(items.length); try { for (let i = 0; i < items.length; i++) { if (state.abort.signal.aborted) { throw new Error("Aborted"); } const item = items[i]; setStatus("Batch " + (i + 1) + "/" + items.length + ": exact \"" + item + "\"", "warn"); const data = await tpSearchExactItem(item, state.abort.signal); const ibLots = filterAndSortInstantBuy(data.lots, minPrice, maxPrice, priceMode, nextPricePercent); state.liveBatchStats.processed += 1; state.liveBatchStats.totalIBLots += ibLots.length; if (ibLots.length) { state.liveBatchStats.foundItems += 1; if (modeReturn === "best") { addLiveBatchRow({ item, bestLot: ibLots[0] }, modeSort); } else { addLiveBatchRow({ item, lots: ibLots }, modeSort); } } updateLiveBatchSummary(); if (i < items.length - 1) { await sleep(DEFAULTS.batchDelayMs, state.abort.signal); } } if (!state.liveBatchRows.length) { setStatus("Batch done. No Instant Buy lots found (exact match).", "err"); } else { const label = modeReturn === "best" ? "Batch done. Best price per item. Items found: " + state.liveBatchRows.length + "." : "Batch done. Lots found: " + state.liveBatchStats.totalIBLots + " across " + state.liveBatchRows.length + " item(s)."; setStatus(label, "ok"); } updateLiveBatchSummary(); } catch (e) { const msg = e && e.message ? e.message : "Request failed"; if (String(msg).toLowerCase().includes("aborted")) { setStatus("Stopped. Results found before stopping are still shown.", "warn"); } else { setStatus("Error: " + msg + ".", "err"); if (!state.liveBatchRows.length) { renderBatchResults([], "Error"); } } } finally { state.running = false; state.abort = null; setBusy(false); } } function boot() { injectStylesOnce(); buildLauncher(); startInlineTradingPostPricerObserver(); if (DEFAULTS.autoOpenOnTradingPost && isTradingPostPage()) { openModal(); } } boot(); })();