NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript==
// @name Neopets 'Quick Stock' Pricer
// @namespace http://tampermonkey.net/
// @version 1.0.1
// @description Checks item prices in the Quick Stock table against ItemDB market value, injecting a "Market Price" column for easy reference.
// @author Logan Bell
// @match https://www.neopets.com/quickstock.phtml
// @connect itemdb.com.br
// @grant GM_xmlhttpRequest
// @run-at document-end
// @license MIT
// ==/UserScript==
(function() {
'use strict';
console.log("Neopets Quick Stock Pricer V1.1: Script started.");
// --- Configuration ---
const API_URL = "https://itemdb.com.br/api/v1/items/many";
const ITEMDB_BASE_URL = "https://itemdb.com.br/item/";
// --- GUI: Status Box ---
const statusBox = document.createElement('div');
statusBox.id = 'gemini-status-box';
statusBox.style.cssText = `
position: fixed; bottom: 10px; right: 10px; padding: 6px;
background: #f7f7f7; border: 1px solid #ccc; z-index: 9999;
font-size: 11px; font-weight: bold; border-radius: 4px;
color: #333;
`;
statusBox.innerText = 'Quick Stock Scanner: Ready';
document.body.appendChild(statusBox);
// --- Helper Functions (Identical to Original) ---
function formatNumber(num) {
return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
}
// Creates a URL slug from the item name
function createSlug(name) {
return name.trim().toLowerCase().replace(/\s+/g, '-').replace(/[^a-z0-9-]/g, '');
}
// --- Main Logic ---
// 1. Find the target table and inject the new header column
// The target table is within the form that has action="process_quickstock.phtml"
const quickstockTable = document.querySelector('form[action="process_quickstock.phtml"] > table');
if (!quickstockTable) {
statusBox.innerText = "Scanner: Quick Stock table not found.";
return;
}
// Find the first header row
const headerRow = quickstockTable.querySelector('tbody > tr:first-child');
if (!headerRow || headerRow.children.length < 8) {
statusBox.innerText = "Scanner: Header row not structured as expected.";
return;
}
// Inject the new header column (Market Price)
const marketHeader = document.createElement('th');
marketHeader.setAttribute('align', 'center');
marketHeader.setAttribute('width', '10%'); // Give it a fixed width
marketHeader.setAttribute('bgcolor', '#dddd77');
marketHeader.innerHTML = '<b>Market Price</b>';
// The 'Object Name' is the first header (index 0). We want to insert 'Market Price' right after it,
// before the 'Stock' column (index 1).
const stockHeader = headerRow.children[1];
headerRow.insertBefore(marketHeader, stockHeader);
// Also update the second header row (if it exists) for visual consistency
const allHeaderRows = quickstockTable.querySelectorAll('tr[bgcolor="#EEEEBB"]');
if (allHeaderRows.length > 1) {
const secondHeaderRow = allHeaderRows[1];
const secondMarketHeader = marketHeader.cloneNode(true);
secondHeaderRow.insertBefore(secondMarketHeader, secondHeaderRow.children[1]);
}
console.log("Quick Stock Pricer V1.1: Header column injected.");
// 2. Scrape items from table rows
const items = [];
// Get all item rows. We target rows with background colors used for items, skipping headers and special gift box rows.
const itemRows = Array.from(quickstockTable.querySelectorAll('tbody > tr')).filter(row => {
const bgColor = row.getAttribute('bgcolor');
// Typical alternating item row colors
return (bgColor === '#FFFFFF' || bgColor === '#ffffcc');
});
itemRows.forEach((row, index) => {
// The item name is always in the first <td> cell.
const cells = row.querySelectorAll('td');
if (cells.length < 7) {
// Skips the blank separator row or malformed rows
return;
}
const nameCell = cells[0];
const name = nameCell ? nameCell.innerText.trim() : null;
if (!name || name.length < 2) {
console.warn(`Skipping row ${index}: Item name not found or too short.`);
return;
}
// Create the market price cell to inject
const resultCell = document.createElement('td');
resultCell.className = 'gemini-price-check';
resultCell.setAttribute('align', 'center');
resultCell.setAttribute('bgcolor', row.getAttribute('bgcolor')); // Maintain row color stripe
resultCell.innerHTML = '<span style="color: #666; font-size: 0.9em;">Checking...</span>';
// Injection point: insert after the 'Object Name' cell (index 0) and before the 'Stock' cell (index 1)
const stockCell = cells[1];
row.insertBefore(resultCell, stockCell);
items.push({
name: name,
// shopPrice is not applicable/used here
shopPrice: 0,
element: resultCell,
container: row
});
});
console.log(`Quick Stock Pricer V1.1: Found ${items.length} items to check.`);
statusBox.innerText = `Scanner: Found ${items.length} items...`;
if (items.length === 0) {
statusBox.innerText = "Scanner: No items found in the main inventory table.";
return;
}
// 3. Fetch Data (Identical to Original)
const itemNames = items.map(i => i.name);
GM_xmlhttpRequest({
method: "POST",
url: API_URL,
headers: { "Content-Type": "application/json" },
data: JSON.stringify({ name: itemNames }),
onload: function(response) {
if (response.status !== 200) {
console.error("API Error:", response.statusText);
statusBox.innerText = "Error: API Failed";
return;
}
try {
const data = JSON.parse(response.responseText);
updatePrices(data);
statusBox.innerText = "Scanner: Complete!";
setTimeout(() => statusBox.remove(), 5000);
} catch (e) {
console.error("JSON Parse Error:", e);
statusBox.innerText = "Error: Bad Data";
}
},
onerror: function(err) {
console.error("Request Failed:", err);
statusBox.innerText = "Error: Check Permissions";
}
});
// 4. Update UI (Modified for quick stock table cell display)
function updatePrices(apiData) {
items.forEach(item => {
const itemData = apiData[item.name];
const itemSlug = createSlug(item.name);
const itemDBLink = ITEMDB_BASE_URL + itemSlug;
// Link wrapper for the market price
const linkStart = `<a href="${itemDBLink}" target="_blank" style="text-decoration: none; color: #000;">`;
const linkEnd = `</a>`;
if (itemData && itemData.price && itemData.price.value) {
const marketPrice = itemData.price.value;
item.element.innerHTML = `
${linkStart}
<div style="font-size: 1.0em; font-weight: bold; line-height: 1.1; color: #333333;">
${formatNumber(marketPrice)}
</div>
${linkEnd}`;
} else {
item.element.innerHTML = `<span style="color: #aaa; font-size: 0.9em;">No ItemDB Data</span>`;
}
});
}
})();