// ==UserScript== // @name Neopets Trading Post Instant Buy Finder (Exact Match + Draggable + Single/Batch) // @namespace https://scriptneo.com/ // @version 2.3.0 // @description Draggable modal to search Trading Post by exact item name using criteria=item_exact. Single mode searches one item. Batch mode searches many items. Instant Buy lots only, sorted lowest to highest. One request per item, no pagination. // @author Derek // @match https://www.neopets.com/island/tradingpost.phtml* // @grant none // @run-at document-end // @downloadURL https://www.scriptneo.com/scripts/download.php?id=24 // @updateURL https://www.scriptneo.com/scripts/download.php?id=24 // ==/UserScript== (function () { "use strict"; const API_URL = "https://www.neopets.com/np-templates/ajax/island/tradingpost/tradingpost-list.php"; const DEFAULTS = { sort: "newest", minPrice: 1, maxPrice: 0, // 0 = no max openLinksNewTab: true, autoOpenOnTradingPost: true, autoSearchOnEnter: true, // Mode mode: "single", // "single" | "batch" // Batch tuning batchDelayMs: 350, batchMaxItems: 500, batchReturn: "best", // "best" | "all" batchSort: "price", // "price" | "name" // IMPORTANT: exact match API criteria criteria: "item_exact" }; let state = { running: false, abort: null, isOpen: false, drag: { active: false, offsetX: 0, offsetY: 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"); if (btn1) btn1.disabled = !!busy; if (btn2) btn2.disabled = !!busy; } // ------------------------ // API (Exact match) // ------------------------ async function tpSearchExactItem(itemName, signal) { const payload = { type: "browse", criteria: DEFAULTS.criteria, // "item_exact" 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; } // ------------------------ // Styles + UI // ------------------------ 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-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-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; } `.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 (criteria=item_exact). Instant Buy only. Lowest price first."]) ]), 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. Returns cheapest lot per item (Best) or all lots (All)." ]); 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("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 body = el("div", { class: "tp-ibf-body" }, [ tabs, singleHint, batchHint, singleRow, batchRow, settingsBox, 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); // Defaults 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; // Tabs initial setMode(DEFAULTS.mode); // Actions 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-query")?.addEventListener("keydown", (e) => { if (!DEFAULTS.autoSearchOnEnter) return; if (e.key === "Enter") { e.preventDefault(); runSingle(); } }); // Draggable 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 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 (results) results.innerHTML = ""; 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]); } // ------------------------ // Mode toggle // ------------------------ 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"; } // ------------------------ // Dragging // ------------------------ 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); } // ------------------------ // Search logic // ------------------------ 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; return { minPrice, maxPrice }; } function filterAndSortInstantBuy(lots, minPrice, maxPrice) { 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); }); 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 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") ]) ]), 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; } async function runSingle() { const query = (document.getElementById("tp-ibf-query")?.value ?? "").trim(); const { minPrice, maxPrice } = 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); 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 } = 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); const resultsRows = []; let totalIBLots = 0; 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); totalIBLots += ibLots.length; if (modeReturn === "best") { if (ibLots.length) resultsRows.push({ item, bestLot: ibLots[0] }); } else { if (ibLots.length) resultsRows.push({ item, lots: ibLots }); } if (i < items.length - 1) await sleep(DEFAULTS.batchDelayMs, state.abort.signal); } if (modeSort === "name") { resultsRows.sort((a, b) => safeText(a.item).localeCompare(safeText(b.item))); } else { resultsRows.sort((a, b) => { 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)); }); } if (!resultsRows.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: " + resultsRows.length + ".") : ("Batch done. Lots found: " + totalIBLots + " across " + resultsRows.length + " item(s)."); setStatus(label, "ok"); } renderBatchResults(resultsRows, modeReturn === "best" ? "Best per item" : "All lots"); } catch (e) { const msg = e && e.message ? e.message : "Request failed"; if (String(msg).toLowerCase().includes("aborted")) setStatus("Stopped.", "warn"); else setStatus("Error: " + msg + ".", "err"); renderBatchResults([], "Error"); } finally { state.running = false; state.abort = null; setBusy(false); } } // ------------------------ // Boot // ------------------------ function boot() { injectStylesOnce(); buildLauncher(); if (DEFAULTS.autoOpenOnTradingPost && isTradingPostPage()) openModal(); } boot(); })();