// ==UserScript== // @name Neopets - Trading Post Bulk Delete Helper // @version 1.1 // @description Adds a panel to select, delete selected, or delete all of your visible Trading Post lots. // @author Fixed Version // @match *://www.neopets.com/island/tradingpost.phtml* // @match *://neopets.com/island/tradingpost.phtml* // @grant none // @icon https://images.neopets.com/tradingpost/assets/images/trade_icon.png // @downloadURL https://www.scriptneo.com/scripts/download.php?id=28 // @updateURL https://www.scriptneo.com/scripts/download.php?id=28 // ==/UserScript== (function () { "use strict"; const TPBH = { panelId: "tpbh-panel", statusId: "tpbh-status", listId: "tpbh-list", endpointLots: "/np-templates/ajax/island/tradingpost/get-lots.php?sort=newest", endpointDelete: "/np-templates/ajax/island/tradingpost/delete-lot.php", delayBetweenDeletesMs: 900, requireTypedConfirm: true }; let lots = []; let selectedLotIds = new Set(); let isDeleting = false; ready(function () { addCSS(); addPanel(); loadLots(); }); function ready(fn) { if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", fn); } else { fn(); } } function qs(selector, root = document) { return root.querySelector(selector); } function qsa(selector, root = document) { return Array.from(root.querySelectorAll(selector)); } function sleep(ms) { return new Promise(function (resolve) { window.setTimeout(resolve, ms); }); } function setStatus(message, type = "") { const status = qs("#" + TPBH.statusId); if (!status) { return; } status.textContent = message; status.className = "tpbh-status"; if (type) { status.classList.add(type); } } function addPanel() { if (qs("#" + TPBH.panelId)) { return; } const panel = document.createElement("div"); panel.id = TPBH.panelId; panel.innerHTML = `
Trading Post Bulk Delete
Select trades, then delete/cancel them in a controlled batch.
Deleting/cancelling lots costs NP. Your example response shows 15,000 NP for one deleted lot.
Loading your trades...
`; const target = qs(".tp-main-content") || qs("#tp_app__2025") || qs("#content") || qs("#main") || document.body; target.insertBefore(panel, target.firstChild); qs("#tpbh-refresh").addEventListener("click", function () { if (!isDeleting) { loadLots(); } }); qs("#tpbh-select-all").addEventListener("click", function () { lots.forEach(function (lot) { selectedLotIds.add(String(getLotId(lot))); }); renderLots(); updateSelectionStatus(); }); qs("#tpbh-clear").addEventListener("click", function () { selectedLotIds.clear(); renderLots(); updateSelectionStatus(); }); qs("#tpbh-delete-selected").addEventListener("click", function () { deleteSelectedLots(); }); qs("#tpbh-delete-all").addEventListener("click", function () { deleteAllLoadedLots(); }); } async function loadLots() { try { selectedLotIds.clear(); setStatus("Loading your Trading Post lots...", "tpbh-working"); const response = await fetch(TPBH.endpointLots, { method: "GET", credentials: "include", headers: { "x-requested-with": "XMLHttpRequest" }, cache: "no-store" }); if (!response.ok) { throw new Error("Could not load trades. HTTP " + response.status); } const data = await response.json(); console.log("[TPBH] get-lots response:", data); const rawLots = normalizeLotsResponse(data); lots = rawLots.filter(function (lot) { return getLotId(lot); }); renderLots(); if (lots.length === 0) { setStatus("No active trades found.", "tpbh-success"); } else { setStatus("Loaded " + lots.length + " trade(s).", "tpbh-success"); } } catch (error) { console.error("[TPBH] loadLots error:", error); setStatus("ERROR: " + error.message, "tpbh-error"); } } function normalizeLotsResponse(data) { if (!data) { return []; } if (Array.isArray(data)) { return data; } if (Array.isArray(data.lots)) { return data.lots; } if (Array.isArray(data.trades)) { return data.trades; } if (Array.isArray(data.results)) { return data.results; } if (data.data && Array.isArray(data.data.lots)) { return data.data.lots; } if (data.data && Array.isArray(data.data.trades)) { return data.data.trades; } return []; } function renderLots() { const list = qs("#" + TPBH.listId); if (!list) { return; } if (!lots.length) { list.innerHTML = `
No active trades found.
`; return; } list.innerHTML = lots.map(function (lot) { const lotId = String(getLotId(lot)); const selected = selectedLotIds.has(lotId); const title = getLotTitle(lot); const wish = getLotWishlist(lot); const itemCount = getLotItemCount(lot); const offerCount = getLotOfferCount(lot); const instantBuy = getLotInstantBuy(lot); return ` `; }).join(""); qsa(".tpbh-check", list).forEach(function (checkbox) { checkbox.addEventListener("change", function () { const lotId = String(checkbox.value); if (checkbox.checked) { selectedLotIds.add(lotId); } else { selectedLotIds.delete(lotId); } renderLots(); updateSelectionStatus(); }); }); } function updateSelectionStatus() { if (!lots.length) { setStatus("No active trades found.", "tpbh-success"); return; } setStatus( selectedLotIds.size + " selected out of " + lots.length + " loaded trade(s).", "tpbh-working" ); } async function deleteSelectedLots() { const selected = Array.from(selectedLotIds); if (!selected.length) { alert("Please select at least one trade first."); return; } await confirmAndDelete(selected, "selected"); } async function deleteAllLoadedLots() { const all = lots.map(function (lot) { return String(getLotId(lot)); }).filter(Boolean); if (!all.length) { alert("No trades are loaded."); return; } await confirmAndDelete(all, "all loaded"); } async function confirmAndDelete(lotIds, label) { if (isDeleting) { return; } const estimatedFee = lotIds.length * 15000; const message = "You are about to delete/cancel " + lotIds.length + " " + label + " trade(s).\n\n" + "Estimated fee using your latest example: " + formatNumber(estimatedFee) + " NP\n\n" + "Items should be returned to your inventory, but this action cannot be undone from the script.\n\n" + ( TPBH.requireTypedConfirm ? "Type DELETE to continue." : "Press OK to continue." ); if (TPBH.requireTypedConfirm) { const typed = window.prompt(message, ""); if (typed !== "DELETE") { setStatus("Cancelled. Nothing was deleted.", "tpbh-success"); return; } } else if (!window.confirm(message)) { setStatus("Cancelled. Nothing was deleted.", "tpbh-success"); return; } await deleteLotsSequentially(lotIds); } async function deleteLotsSequentially(lotIds) { isDeleting = true; toggleButtons(true); let successCount = 0; let failCount = 0; let totalCost = 0; let totalItemsReturned = 0; let totalOffersDeleted = 0; const failed = []; try { for (let i = 0; i < lotIds.length; i++) { const lotId = String(lotIds[i]); setStatus( "Deleting trade " + (i + 1) + " / " + lotIds.length + " | Lot " + lotId + "...", "tpbh-working" ); try { const result = await deleteSingleLot(lotId); console.log("[TPBH] delete result for lot " + lotId + ":", result); if (result && result.success === true) { successCount++; totalCost += Number(result.deletion_cost || 0); totalItemsReturned += Number(result.items_returned || 0); totalOffersDeleted += Number(result.offers_deleted || 0); selectedLotIds.delete(lotId); lots = lots.filter(function (lot) { return String(getLotId(lot)) !== lotId; }); renderLots(); setStatus( "Deleted " + successCount + " / " + lotIds.length + " | Cost: " + formatNumber(totalCost) + " NP" + " | Items returned: " + formatNumber(totalItemsReturned), "tpbh-working" ); } else { failCount++; failed.push({ lotId: lotId, error: result && (result.error || result.message) ? result.error || result.message : "Unknown error" }); } } catch (error) { failCount++; failed.push({ lotId: lotId, error: error.message }); } await sleep(TPBH.delayBetweenDeletesMs); } if (failCount > 0) { console.warn("[TPBH] Failed deletes:", failed); setStatus( "Done. Deleted " + successCount + ". Failed " + failCount + ". Cost: " + formatNumber(totalCost) + " NP. Check console.", "tpbh-error" ); } else { setStatus( "Done. Deleted " + successCount + " trade(s). Cost: " + formatNumber(totalCost) + " NP. Items returned: " + formatNumber(totalItemsReturned) + ". Offers deleted: " + formatNumber(totalOffersDeleted) + ".", "tpbh-success" ); } } finally { isDeleting = false; toggleButtons(false); window.setTimeout(function () { loadLots(); }, 900); } } async function deleteSingleLot(lotId) { const numericLotId = Number(lotId); if (!Number.isInteger(numericLotId) || numericLotId <= 0) { throw new Error("Invalid lot ID: " + lotId); } const response = await fetch(TPBH.endpointDelete, { method: "POST", credentials: "include", headers: { "Content-Type": "application/json", "x-requested-with": "XMLHttpRequest" }, body: JSON.stringify({ lot_id: numericLotId }) }); const text = await response.text(); if (!response.ok) { throw new Error("HTTP " + response.status + ": " + text.slice(0, 200)); } try { return JSON.parse(text); } catch (error) { console.error("[TPBH] Non-JSON delete response:", text); return { success: false, message: "Non-JSON response from delete-lot.php" }; } } function toggleButtons(disabled) { qsa("#" + TPBH.panelId + " button").forEach(function (button) { button.disabled = disabled; }); } function getLotId(lot) { return ( lot.lot_id || lot.lotId || lot.id || lot.trade_id || lot.tradeId || "" ); } function getLotTitle(lot) { if (lot.title) { return lot.title; } if (lot.name) { return lot.name; } if (lot.item_name) { return lot.item_name; } if (Array.isArray(lot.lot_items) && lot.lot_items.length) { return lot.lot_items.map(function (item) { return item.name || item.item_name || item.itemName || "Item"; }).slice(0, 3).join(", "); } if (Array.isArray(lot.items) && lot.items.length) { return lot.items.map(function (item) { return item.name || item.item_name || item.itemName || "Item"; }).slice(0, 3).join(", "); } return "Trading Post Lot"; } function getLotWishlist(lot) { return ( lot.wishlist || lot.wish_list || lot.description || lot.lot_wishlist || lot.lotWishlist || "" ); } function getLotItemCount(lot) { if (typeof lot.item_count !== "undefined") { return lot.item_count; } if (typeof lot.itemCount !== "undefined") { return lot.itemCount; } if (typeof lot.count !== "undefined") { return lot.count; } if (Array.isArray(lot.lot_items)) { return lot.lot_items.length; } if (Array.isArray(lot.items)) { return lot.items.length; } return "?"; } function getLotOfferCount(lot) { if (typeof lot.offer_count !== "undefined") { return lot.offer_count; } if (typeof lot.offers_count !== "undefined") { return lot.offers_count; } if (typeof lot.offerCount !== "undefined") { return lot.offerCount; } if (typeof lot.offers !== "undefined") { if (Array.isArray(lot.offers)) { return lot.offers.length; } return lot.offers; } return "0"; } function getLotInstantBuy(lot) { const value = lot.instant_buy_amount || lot.instantBuyAmount || lot.instant_buy || lot.instantBuy || ""; if (!value) { return ""; } return formatNumber(value); } function formatNumber(value) { const number = Number(String(value).replace(/,/g, "")); if (!Number.isFinite(number)) { return String(value); } return new Intl.NumberFormat("en-US").format(number); } function escapeHtml(value) { return String(value) .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """) .replace(/'/g, "'"); } function addCSS() { if (qs("#tpbh-style")) { return; } const style = document.createElement("style"); style.id = "tpbh-style"; style.textContent = ` #tpbh-panel { background: #fff; border: 3px solid #6d4b9b; border-radius: 12px; margin: 16px auto; padding: 14px; max-width: 980px; color: #111; box-shadow: 0 3px 14px rgba(0, 0, 0, 0.18); font-family: Arial, sans-serif; box-sizing: border-box; } .tpbh-header { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 10px; } .tpbh-title { font-size: 20px; font-weight: 800; color: #4f2f78; } .tpbh-subtitle { font-size: 13px; color: #444; margin-top: 2px; } .tpbh-warning { background: #fff3cd; border: 1px solid #e7c76b; color: #4d3900; padding: 10px; border-radius: 8px; margin: 10px 0; font-size: 13px; } .tpbh-actions { display: flex; flex-wrap: wrap; gap: 8px; margin: 12px 0; } .tpbh-btn { border: 0; border-radius: 8px; padding: 9px 12px; font-weight: 800; cursor: pointer; font-size: 13px; } .tpbh-btn:disabled { opacity: 0.55; cursor: default; } .tpbh-btn-neutral { background: #e9e4f2; color: #2c1d3d; } .tpbh-btn-danger { background: #b71c1c; color: #fff; } .tpbh-btn-dark { background: #2b1d3a; color: #fff; } .tpbh-status { margin: 10px 0; padding: 9px 10px; border-radius: 8px; background: #eef1f5; color: #222; font-size: 13px; font-weight: 700; } .tpbh-status.tpbh-working { background: #e6f0ff; color: #0f3d75; } .tpbh-status.tpbh-success { background: #dff3e3; color: #145c25; } .tpbh-status.tpbh-error { background: #f8d7da; color: #842029; } .tpbh-list { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 10px; margin-top: 10px; } .tpbh-lot { display: flex; gap: 10px; align-items: flex-start; border: 2px solid #ddd; border-radius: 10px; padding: 10px; background: #fafafa; cursor: pointer; transition: border-color 0.15s ease, background 0.15s ease; } .tpbh-lot:hover { border-color: #8a6db1; background: #f6f0ff; } .tpbh-lot-selected { border-color: #5e2ca5; background: #efe4ff; } .tpbh-check { margin-top: 4px; transform: scale(1.25); } .tpbh-lot-body { min-width: 0; flex: 1; } .tpbh-lot-top { display: flex; justify-content: space-between; gap: 8px; font-size: 13px; color: #222; margin-bottom: 4px; } .tpbh-lot-title { font-weight: 800; color: #222; font-size: 14px; margin-bottom: 4px; overflow-wrap: anywhere; } .tpbh-lot-wish { font-size: 12px; color: #444; margin-bottom: 4px; overflow-wrap: anywhere; } .tpbh-lot-meta { display: flex; flex-wrap: wrap; gap: 8px; font-size: 12px; color: #555; } .tpbh-empty { padding: 14px; border: 1px dashed #bbb; border-radius: 8px; color: #555; background: #fafafa; } @media (max-width: 600px) { #tpbh-panel { margin: 10px; padding: 10px; } .tpbh-header { flex-direction: column; align-items: stretch; } .tpbh-actions { flex-direction: column; } .tpbh-btn { width: 100%; } } `; document.head.appendChild(style); } })();