// ==UserScript==
// @name Neopets - Gallery Pricer
// @version 1.0.0
// @author Scriptneo
// @namespace Scriptneo
// @description Shows the market price for your sdb and shop items (price multiplied by quantity) and displays a total summary. Uses itemdb.com.br values.
// @website https://itemdb.com.br
// @match *://*.neopets.com/gallery/*
// @match *://*.neopets.com/gallery/quickremove.phtml*
// @icon https://itemdb.com.br/favicon.ico
// @connect itemdb.com.br
// @grant GM_xmlhttpRequest
// @noframes
// @downloadURL https://www.scriptneo.com/scripts/download.php?id=39
// @updateURL https://www.scriptneo.com/scripts/download.php?id=39
// ==/UserScript==
const isQuickRemoveForm = window.location.pathname.endsWith('/quickremove.phtml');
const quickRemoveFormTable = '#quickremove_form table:first';
const galleryFormTable = '#gallery_form table:first';
// For quick remove: iterate over rows (skip header/footer) and extract the item name from cell index 2.
const fetchIdsFromQuickRemove = () => {
return $(`${quickRemoveFormTable} tr:not(:first):not(:last)`).map(function() {
return $(this).find('td').eq(2).text().trim();
}).get();
};
// For gallery view: iterate over TDs that contain an item image and extract the item name from the element.
const fetchIdsFromGalleryView = () => {
return $(`${galleryFormTable} tr td`).has('img.itemimg').map(function() {
return $(this).find('b.textcolor').text().trim();
}).get();
};
async function fetchPriceData() {
const IDs = isQuickRemoveForm ? fetchIdsFromQuickRemove() : fetchIdsFromGalleryView();
GM_xmlhttpRequest({
method: 'POST',
url: 'https://itemdb.com.br/api/v1/items/many',
headers: { 'Content-Type': 'application/json' },
data: JSON.stringify({ name: IDs }),
onload: function (res) {
if (res.status === 200) {
const itemData = JSON.parse(res.responseText);
priceGallery(itemData);
} else {
console.error('[itemdb] Failed to fetch price data', res);
}
}
});
}
async function priceGallery(itemData) {
const intl = new Intl.NumberFormat();
// Totals for summary
let totalValue = 0;
let totalCount = 0; // Number of distinct items
let totalQuantity = 0; // Sum of quantities
// Array to hold details for the list display.
let listEntries = [];
// Helper to determine the plain text price display.
function getPlainPrice(item, qty, displayPrice) {
let plainPrice = "";
if (!item || (item && item.status !== 'no trade' && !item.price?.value && !item.isNC)) {
plainPrice = "???";
} else if (item && item.status === 'no trade') {
plainPrice = "No Trade";
} else if (item && item.isNC && !item.owls) {
plainPrice = "NC";
} else if (item && item.isNC && item.owls) {
plainPrice = item.owls.value;
} else if (item && item.price?.value) {
plainPrice = `≈ ${intl.format(displayPrice)} NP`;
}
if (item && item.isMissingInfo) {
plainPrice += " (Info needed)";
}
return plainPrice;
}
if (isQuickRemoveForm) {
// Add header for Price column
$(`${quickRemoveFormTable} tr:first`).prepend(
`
Price
`
);
// Process each row in quick remove view
$(`${quickRemoveFormTable} tr:not(:first):not(:last)`).each(function () {
totalCount++;
const $tds = $(this).find('td');
// Extract quantity from cell 0 (e.g. "Qty:1")
let qty = 1;
const qtyText = $tds.eq(0).text();
const m = qtyText.match(/Qty:\s*(\d+)/i);
if (m) {
qty = parseInt(m[1], 10);
}
totalQuantity += qty;
// Extract item name from cell 2
const itemId = $tds.eq(2).text().trim();
const item = itemData[itemId];
let priceStr = '';
let computedPrice = 0;
try {
if (!item || (item && item.status !== 'no trade' && !item.price?.value && !item.isNC)) {
priceStr = `???`;
} else if (item && item.status === 'no trade') {
priceStr = `No Trade`;
} else if (item && item.isNC && !item.owls) {
priceStr = `NC`;
} else if (item && item.isNC && item.owls) {
priceStr = `${item.owls.value} Owls`;
} else if (item && item.price?.value) {
computedPrice = item.price.value * qty;
totalValue += computedPrice;
priceStr = `≈ ${intl.format(computedPrice)} NP`;
priceStr += ` r${item.rarity}`;
}
if (item && item.isMissingInfo) {
priceStr += ` We need info about this item Learn how to Help
`;
}
} catch (e) {
priceStr = `Not Found We need info about this item Learn how to Help
`;
}
$(this).prepend(`
${priceStr}
`);
// Save this entry for the list.
const plainPrice = getPlainPrice(item, qty, computedPrice);
listEntries.push({ name: itemId, qty, price: plainPrice, totalPrice: computedPrice });
});
} else {
// Process gallery view: For each cell with an item image.
$(`${galleryFormTable} tr td`).has('img.itemimg').each(function () {
totalCount++;
const $td = $(this);
// Extract item name from the element within the same cell.
const itemId = $td.find('b.textcolor').text().trim();
let qty = 1;
// The quantity is in the row immediately following the image row.
const $imgRow = $td.closest('tr');
const $qtyRow = $imgRow.next('tr');
if ($qtyRow.length) {
const colIndex = $td.index();
const $qtyCell = $qtyRow.find('td').eq(colIndex);
const qtyText = $qtyCell.text();
const m = qtyText.match(/Qty:\s*(\d+)/i);
if (m) {
qty = parseInt(m[1], 10);
}
}
totalQuantity += qty;
const item = itemData[itemId];
let priceStr = '';
let computedPrice = 0;
let unitPrice = 0;
try {
if (!item || (item && item.status !== 'no trade' && !item.price?.value && !item.isNC)) {
priceStr = `???`;
} else if (item && item.status === 'no trade') {
priceStr = `No Trade`;
} else if (item && item.isNC && !item.owls) {
priceStr = `NC`;
} else if (item && item.isNC && item.owls) {
priceStr = `${item.owls.value} Owls`;
} else if (item && item.price?.value) {
// Use unit price for display under the item name.
unitPrice = item.price.value;
computedPrice = unitPrice * qty; // computed total for summary and list
totalValue += computedPrice;
priceStr = `≈ ${intl.format(unitPrice)} NP`;
priceStr += ` r${item.rarity}`;
}
if (item && item.isMissingInfo) {
priceStr += ` We need info about this item Learn how to Help
`;
}
} catch (e) {
priceStr = `Not Found We need info about this item Learn how to Help
`;
}
// Append the price info to the quantity cell.
if ($qtyRow.length) {
const colIndex = $td.index();
const $qtyCell = $qtyRow.find('td').eq(colIndex);
$qtyCell.append(`
${priceStr}
`);
} else {
$td.append(`
${priceStr}
`);
}
// Save this entry for the list.
// Now using computedPrice for the list drop down, so it shows quantity * unit price.
const plainPrice = getPlainPrice(item, qty, computedPrice);
listEntries.push({ name: itemId, qty, price: plainPrice, totalPrice: computedPrice });
});
}
// Create the summary string with a "List" button and a container for the list.
const summaryStr = `
Total Items: ${totalCount} |
Total Quantity: ${totalQuantity} |
Total Value: ${intl.format(totalValue)} NP
`;
// Insert the summary after the navigation block.
const $target = $('#header');
if ($target.length) {
$target.after(summaryStr);
}
// Attach click event to the "List" button to toggle the list display.
document.getElementById('listToggleBtn').addEventListener('click', function () {
const listDiv = document.getElementById('itemListDiv');
if (listDiv.style.display === 'none') {
// Sort entries from highest to lowest total price.
listEntries.sort((a, b) => b.totalPrice - a.totalPrice);
// Build the list content.
let listHtml = 'Item Name - Item Qty - Item Price ';
listEntries.forEach(function(entry) {
listHtml += `${entry.name}x${entry.qty} - ${entry.price} `;
});
listDiv.innerHTML = listHtml;
listDiv.style.display = 'block';
} else {
listDiv.style.display = 'none';
}
});
}
fetchPriceData();