NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript==
// @name Neopets 'My Shop' Pricer
// @namespace http://tampermonkey.net/
// @version 1.0.1
// @description Checks item prices in the user shop price editing table against ItemDB market value, injecting a "Market Price" column.
// @author Gemini / Logan Bell (Original Logic)
// @match https://www.neopets.com/market.phtml?type=your*
// @connect itemdb.com.br
// @grant GM_xmlhttpRequest
// @run-at document-end
// @license MIT
// ==/UserScript==
(function() {
'use strict';
console.log("Neopets User Shop 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;
`;
statusBox.innerText = 'User Shop 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_market.phtml"
const shopTable = document.querySelector('form[action="process_market.phtml"] > table');
if (!shopTable) {
statusBox.innerText = "Scanner: Shop table not found.";
return;
}
const headerRow = shopTable.querySelector('tbody > tr:first-child');
if (!headerRow || headerRow.children.length < 4) {
statusBox.innerText = "Scanner: Header row not structured as expected.";
return;
}
// Inject the new header column (Mkt. Price)
const marketHeader = document.createElement('td');
marketHeader.setAttribute('align', 'center');
marketHeader.setAttribute('bgcolor', '#dddd77');
marketHeader.innerHTML = '<b>Market</b>';
// Find the 'Type' header (4th column, index 3) and insert after it
const typeHeader = headerRow.children[3];
headerRow.insertBefore(marketHeader, typeHeader.nextSibling);
console.log("User Shop Pricer V1.1: Header column injected.");
// 2. Scrape items from table rows
const items = [];
// Get all item rows (skip header row and footer row)
const itemRows = Array.from(shopTable.querySelectorAll('tbody > tr')).slice(1, -1);
itemRows.forEach((row, index) => {
// The visible <td> cells we care about:
// Index 0: Name, Index 3: Type, Index 4: Your Price (Input)
const cells = row.querySelectorAll('td');
if (cells.length < 5) {
console.warn(`Skipping row ${index}: Not enough <td> elements found.`);
return;
}
const nameElement = cells[0].querySelector('b');
const typeCell = cells[3];
// Scrape item name from the first <td> (index 0)
const name = nameElement ? nameElement.innerText.trim() : null;
if (!name) {
console.warn(`Skipping row ${index}: Item name not found.`);
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', '#ffffcc');
resultCell.innerHTML = '<span style="color: #666; font-size: 0.9em;">Checking...</span>';
// Inject the new cell: insert after the 'Type' cell (index 3)
row.insertBefore(resultCell, typeCell.nextSibling);
items.push({
name: name,
// shopPrice is not used for profit calculation here, set to 0 to align with original updatePrices function signature
shopPrice: 0,
element: resultCell,
container: row
});
});
console.log(`User Shop 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 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 user shop 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>`;
}
});
}
})();