// ==UserScript==
// @name itemdb - Stamp Album Helper + Copy Missing (Single & Auto)
// @version 1.2.9
// @author originally EatWooloos, updated by itemdb, auto-copy by ChatGPT
// @namespace itemdb
// @description Adds an info menu about missing stamps, copy buttons, auto-copy across albums, TP helpers, shows missing counts on the album links, and can open TP searches for missing stamps across ALL albums in batches
// @icon https://itemdb.com.br/favicon.ico
// @match *://*.neopets.com/stamps.phtml?type=album&page_id=*
// @connect itemdb.com.br
// @connect neopets.com
// @connect www.neopets.com
// @grant GM_xmlhttpRequest
// @grant GM_setClipboard
// @grant GM_openInTab
// @downloadURL https://www.scriptneo.com/scripts/download.php?id=23
// @updateURL https://www.scriptneo.com/scripts/download.php?id=23
// ==/UserScript==
/****************************************************************************************
*
* < Stamp Album Helper by u/EatWooloo_As_Mutton and updated by itemdb >
* Extended with:
* - Copy Missing Stamps (this album)
* - Auto Copy Missing (all albums found in the header menu)
* - Auto Copy Missing (Names Only, all albums found in the header menu) <-- NEW
* - Trading Post icon under each stamp image (links to TP exact search)
* - Button to open all Trading Post searches for missing stamps on the page
* - Album link counts: show how many stamps are missing in each album
* - Button to open Trading Post searches for ALL missing stamps across ALL albums
* - Batch opening for ALL-albums TP: 10 tabs, wait 20 seconds, repeat
*
****************************************************************************************/
// Robust URL param parsing
const params = new URLSearchParams(window.location.search);
const albumIDStr = params.get("page_id");
const albumID = albumIDStr ? parseInt(albumIDStr, 10) : 0;
let owner = params.get("owner") || (typeof appInsightsUserName !== "undefined" ? appInsightsUserName : "");
let hasPremium = false;
if (!document.URL.includes("progress")) {
hasPremium = !!$("#sswmenu .imgmenu").length;
}
/****************************************************************************************
* Batch settings for opening lots of TP tabs
****************************************************************************************/
const OPEN_ALL_TP_BATCH_SIZE = 10;
const OPEN_ALL_TP_BATCH_WAIT_MS = 20000;
/****************************************************************************************
* Selectors (critical: only real stamp images, not injected icon imgs)
****************************************************************************************/
const STAMP_IMG_SEL = ".content table td > img";
/****************************************************************************************
* Networking helpers
****************************************************************************************/
function gmGet(url) {
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: "GET",
url,
headers: { "Content-Type": "text/html; charset=UTF-8" },
onload: res => {
if (res.status >= 200 && res.status < 300) resolve(res.responseText);
else reject(new Error(res.status + " " + url));
},
onerror: err => reject(err)
});
});
}
function gmGetJson(url) {
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: "GET",
url,
headers: { "Content-Type": "application/json" },
onload: res => {
if (res.status >= 200 && res.status < 300) {
try {
resolve(JSON.parse(res.responseText));
} catch (e) {
reject(e);
}
} else {
reject(new Error(res.status + " " + url));
}
},
onerror: err => reject(err)
});
});
}
const sleep = ms => new Promise(r => setTimeout(r, ms));
async function countdownStatus(seconds, prefix) {
for (let s = seconds; s > 0; s--) {
$("#auto-copy-status").text(`${prefix} Waiting ${s}s...`);
await sleep(1000);
}
}
/****************************************************************************************
* Album missing count cache
****************************************************************************************/
const ALBUM_COUNT_CACHE_TTL_MS = 6 * 60 * 60 * 1000; // 6 hours
const ALBUM_COUNT_CACHE_KEY = `idb_album_missing_counts::${owner || "__noowner__"}`;
function loadAlbumCountCache() {
try {
const raw = localStorage.getItem(ALBUM_COUNT_CACHE_KEY);
if (!raw) return null;
const obj = JSON.parse(raw);
if (!obj || typeof obj !== "object") return null;
if (!obj.ts || !obj.counts) return null;
if (Date.now() - obj.ts > ALBUM_COUNT_CACHE_TTL_MS) return null;
return obj;
} catch {
return null;
}
}
function saveAlbumCountCache(counts) {
try {
localStorage.setItem(ALBUM_COUNT_CACHE_KEY, JSON.stringify({ ts: Date.now(), counts }));
} catch {
// ignore
}
}
/****************************************************************************************
* ItemDB data for the current album
****************************************************************************************/
let thisPage = {};
const format = new Intl.NumberFormat().format;
async function getStampList() {
// Skip ItemDB API for album 0 to avoid 404
if (!albumID || albumID <= 0) {
console.warn("[itemdb] album_id is 0 or invalid, skipping ItemDB fetch for this page");
addCopyButtons();
addTradingPostIconsUnderImages();
return;
}
try {
const data = await gmGetJson("https://itemdb.com.br/api/v1/tools/album-helper?album_id=" + albumID);
thisPage = data;
replaceImages();
addCopyButtons();
} catch (e) {
console.error("[itemdb] Failed to fetch album data for albumID", albumID, e);
addCopyButtons();
addTradingPostIconsUnderImages();
}
}
if (albumIDStr !== null) {
getStampList();
}
/****************************************************************************************
* Styles
****************************************************************************************/
$("body").append(`
`);
/****************************************************************************************
* Trading Post helpers
****************************************************************************************/
function tpExactUrlForName(name) {
return (
"https://www.neopets.com/island/tradingpost.phtml?type=browse&criteria=item_exact&sort=newest&search_string=" +
encodeURIComponent(name)
);
}
function openTab(url) {
if (typeof GM_openInTab === "function") {
GM_openInTab(url, { active: false, insert: true, setParent: true });
return;
}
window.open(url, "_blank", "noopener,noreferrer");
}
function ensureTradingPostIconUnderImage(imgEl) {
const $img = $(imgEl);
const name = ($img.attr("alt") || "").trim();
if (!name || name === "No Stamp") return;
const $td = $img.closest("td");
if (!$td.length) return;
if ($td.find(".idb-tp-under").length) return;
const tpIcon = "http://images.neopets.com/themes/h5/basic/images/tradingpost-icon.png";
const tpUrl = tpExactUrlForName(name);
const $a = $(` `);
$a.attr("href", tpUrl);
$a.append(` `);
$img.after($a);
}
function addTradingPostIconsUnderImages() {
$(STAMP_IMG_SEL).each(function () {
ensureTradingPostIconUnderImage(this);
});
}
/****************************************************************************************
* Replace images for current album view
****************************************************************************************/
function replaceImages() {
let infoContent = {};
let totalNeeded = 0;
$(STAMP_IMG_SEL).each(function (index, element) {
let position, itemData;
let name, rarity, img;
if (thisPage[index + 1]) {
({ position, itemData } = thisPage[index + 1]);
const obj = itemData || {};
name = obj.name;
rarity = obj.rarity;
img = obj.image;
} else {
position = index + 1;
name = "No Stamp";
rarity = "r0";
img = "http://images.neopets.com/items/stamp_blank.gif";
}
$(element).attr("position", position).attr("rarity", rarity);
if ($(element).attr("alt") === "No Stamp" && name !== "No Stamp") {
$(element)
.addClass("fake-stamp")
.attr("title", name)
.attr("src", img)
.attr("alt", name)
.attr("rarity", rarity);
if (itemData && itemData.price && itemData.price.value) {
totalNeeded += itemData.price.value;
}
}
infoContent[position] = createInfoContent(element, itemData);
$(element).on("click", function () {
$(".stamp-info").html(infoContent[position]).show();
$(".content table td").removeClass("stamp-selected");
$(element).parent().addClass("stamp-selected");
});
if (hasPremium && name !== "No Stamp") {
$(element).on("dblclick", function () {
sswopen(name);
});
}
ensureTradingPostIconUnderImage(element);
});
$(".content center:last-of-type").after(
`
You would need ${format(totalNeeded)} NP to complete this album at this time `
);
}
/****************************************************************************************
* Buttons (plus start album-link counts once)
****************************************************************************************/
let albumCountsStarted = false;
function addCopyButtons() {
if ($("#copy-missing-btn").length) return;
$(".content").prepend(`
? Copy Missing Stamps (This Album)
? Auto Copy Missing (All Albums)
? Auto Copy Missing (Names Only)
? Open TP Searches (Missing on Page)
? Open TP Searches (Missing in ALL Albums)
`);
$("#copy-missing-btn").on("click", () => {
const names = getCurrentAlbumMissingNamesFromDOM();
if (names.length) {
GM_setClipboard(names.join("\n"), { type: "text" });
alert(`Copied ${names.length} missing stamps from this album.`);
} else {
alert("No missing stamps found on this album.");
}
});
$("#open-missing-tp-btn").on("click", async () => {
const names = getCurrentAlbumMissingNamesFromDOM();
const uniq = Array.from(new Set(names));
if (!uniq.length) {
alert("No missing stamps found on this page.");
return;
}
$("#open-missing-tp-btn").prop("disabled", true);
$("#auto-copy-status").text(`Opening ${uniq.length} Trading Post searches...`);
try {
for (let i = 0; i < uniq.length; i++) {
const name = uniq[i];
openTab(tpExactUrlForName(name));
$("#auto-copy-status").text(`Opening ${i + 1}/${uniq.length}: ${name}`);
await sleep(150);
}
$("#auto-copy-status").text(`Done. Opened ${uniq.length} Trading Post searches.`);
} catch (e) {
console.error(e);
$("#auto-copy-status").text(`Error opening TP tabs: ${e.message || e}`);
alert(`Error opening TP tabs: ${e.message || e}`);
} finally {
$("#open-missing-tp-btn").prop("disabled", false);
}
});
$("#open-all-missing-tp-btn").on("click", async () => {
$("#open-all-missing-tp-btn").prop("disabled", true);
$("#auto-copy-status").text("Building missing list across all albums...");
try {
const { albumList } = discoverAlbumsOnPage();
if (!albumList.length) throw new Error("No album links found in the menu bar.");
const missingSet = new Set();
let processed = 0;
for (const entry of albumList) {
processed++;
// Album 0 has no ItemDB mapping, so we cannot reliably get names to open TP searches
if (!entry.id || entry.id <= 0) {
$("#auto-copy-status").text(`Skipping ${entry.name} (id 0): cannot resolve names for TP searches.`);
continue;
}
$("#auto-copy-status").text(`Scanning ${processed}/${albumList.length}: ${entry.name}`);
let html;
try {
html = await gmGet(absoluteAlbumUrl(entry.id));
} catch (err) {
console.warn("[open-all-tp] Failed to fetch album HTML for", entry.id, entry.name, err);
continue;
}
const doc = new DOMParser().parseFromString(html, "text/html");
let api;
try {
api = await gmGetJson("https://itemdb.com.br/api/v1/tools/album-helper?album_id=" + entry.id);
} catch (err) {
console.warn("[open-all-tp] Failed to fetch ItemDB data for album", entry.id, entry.name, err);
continue;
}
const missingForAlbum = extractMissingByComparing(doc, api);
for (const itemName of missingForAlbum) {
missingSet.add(itemName);
}
await sleep(250);
}
const allMissing = Array.from(missingSet);
if (!allMissing.length) {
$("#auto-copy-status").text("Done. No missing stamps found across albums (or ItemDB failed).");
alert("No missing stamps found across albums.");
return;
}
const maxStr = prompt(
`Found ${allMissing.length} missing stamps across albums.\n\nHow many Trading Post tabs should I open?\nEnter 0 for ALL.`,
"0"
);
if (maxStr === null) {
$("#auto-copy-status").text("Cancelled.");
return;
}
let maxTabs = parseInt(String(maxStr).trim(), 10);
if (!Number.isFinite(maxTabs) || maxTabs < 0) maxTabs = 0;
const toOpen = maxTabs > 0 ? allMissing.slice(0, maxTabs) : allMissing.slice();
const ok = confirm(
`This will open ${toOpen.length} Trading Post tabs.\n\nBatching: ${OPEN_ALL_TP_BATCH_SIZE} tabs, wait ${Math.round(OPEN_ALL_TP_BATCH_WAIT_MS / 1000)} seconds, repeat.\n\nContinue?`
);
if (!ok) {
$("#auto-copy-status").text("Cancelled.");
return;
}
$("#auto-copy-status").text(`Opening ${toOpen.length} Trading Post searches in batches...`);
let opened = 0;
let batchNum = 0;
while (opened < toOpen.length) {
batchNum++;
const batchStart = opened;
const batchEnd = Math.min(opened + OPEN_ALL_TP_BATCH_SIZE, toOpen.length);
const batchCount = batchEnd - batchStart;
$("#auto-copy-status").text(
`Batch ${batchNum}: opening ${batchCount} tabs (${batchStart + 1}-${batchEnd} of ${toOpen.length})...`
);
for (let i = batchStart; i < batchEnd; i++) {
const name = toOpen[i];
openTab(tpExactUrlForName(name));
opened++;
await sleep(150);
}
if (opened < toOpen.length) {
await countdownStatus(
Math.round(OPEN_ALL_TP_BATCH_WAIT_MS / 1000),
`Batch ${batchNum} done. ${opened}/${toOpen.length} opened.`
);
}
}
$("#auto-copy-status").text(`Done. Opened ${toOpen.length} Trading Post searches (all albums).`);
alert(`Done.\nOpened: ${toOpen.length}\nTotal missing found: ${allMissing.length}`);
} catch (e) {
console.error(e);
alert(`Open-all TP failed: ${e.message || e}`);
$("#auto-copy-status").text(`Error: ${e.message || e}`);
} finally {
$("#open-all-missing-tp-btn").prop("disabled", false);
}
});
// Existing: copies "Album Name | Item Name"
$("#auto-copy-missing-btn").on("click", async () => {
$("#auto-copy-missing-btn").prop("disabled", true);
$("#auto-copy-status").text("Starting auto scan across albums...");
try {
const { albumList } = discoverAlbumsOnPage();
if (!albumList.length) throw new Error("No album links found in the menu bar.");
const allMissing = [];
let processed = 0;
for (const entry of albumList) {
processed++;
if (!entry.id || entry.id <= 0) {
$("#auto-copy-status").text("Skipping Front Page (id 0), no ItemDB data.");
continue;
}
$("#auto-copy-status").text(`Processing ${processed}/${albumList.length}: ${entry.name}`);
let html;
try {
html = await gmGet(absoluteAlbumUrl(entry.id));
} catch (err) {
console.warn("[auto-copy] Failed to fetch album HTML for", entry.id, entry.name, err);
continue;
}
const doc = new DOMParser().parseFromString(html, "text/html");
let api;
try {
api = await gmGetJson("https://itemdb.com.br/api/v1/tools/album-helper?album_id=" + entry.id);
} catch (err) {
console.warn("[auto-copy] Failed to fetch ItemDB data for album", entry.id, entry.name, err);
continue;
}
const missingForAlbum = extractMissingByComparing(doc, api);
missingForAlbum.forEach(itemName => {
allMissing.push(`${entry.name} | ${itemName}`);
});
await sleep(350);
}
if (allMissing.length) {
GM_setClipboard(allMissing.join("\n"), { type: "text" });
$("#auto-copy-status").text(
`Done. Copied ${allMissing.length} missing items from ${albumList.length} albums.`
);
alert(`Auto-copy complete.\nAlbums scanned: ${albumList.length}\nMissing items copied: ${allMissing.length}`);
} else {
$("#auto-copy-status").text(`Done. No missing items found across ${albumList.length} albums.`);
alert("Auto-copy complete. No missing items found.");
}
} catch (e) {
console.error(e);
alert(`Auto-copy failed: ${e.message || e}`);
$("#auto-copy-status").text(`Error: ${e.message || e}`);
} finally {
$("#auto-copy-missing-btn").prop("disabled", false);
}
});
// NEW: copies ONLY item names (unique), one per line
$("#auto-copy-names-only-btn").on("click", async () => {
$("#auto-copy-names-only-btn").prop("disabled", true);
$("#auto-copy-status").text("Starting auto scan across albums (names only)...");
try {
const { albumList } = discoverAlbumsOnPage();
if (!albumList.length) throw new Error("No album links found in the menu bar.");
const nameSet = new Set();
let processed = 0;
for (const entry of albumList) {
processed++;
if (!entry.id || entry.id <= 0) {
$("#auto-copy-status").text("Skipping Front Page (id 0), no ItemDB data.");
continue;
}
$("#auto-copy-status").text(`Processing ${processed}/${albumList.length}: ${entry.name}`);
let html;
try {
html = await gmGet(absoluteAlbumUrl(entry.id));
} catch (err) {
console.warn("[auto-copy-names-only] Failed to fetch album HTML for", entry.id, entry.name, err);
continue;
}
const doc = new DOMParser().parseFromString(html, "text/html");
let api;
try {
api = await gmGetJson("https://itemdb.com.br/api/v1/tools/album-helper?album_id=" + entry.id);
} catch (err) {
console.warn("[auto-copy-names-only] Failed to fetch ItemDB data for album", entry.id, entry.name, err);
continue;
}
const missingForAlbum = extractMissingByComparing(doc, api);
missingForAlbum.forEach(itemName => {
if (itemName && itemName !== "No Stamp") nameSet.add(itemName);
});
await sleep(350);
}
const namesOnly = Array.from(nameSet);
if (namesOnly.length) {
GM_setClipboard(namesOnly.join("\n"), { type: "text" });
$("#auto-copy-status").text(
`Done. Copied ${namesOnly.length} missing stamp names (unique) from ${albumList.length} albums.`
);
alert(
`Auto-copy (names only) complete.\nAlbums scanned: ${albumList.length}\nUnique names copied: ${namesOnly.length}`
);
} else {
$("#auto-copy-status").text(`Done. No missing items found across ${albumList.length} albums.`);
alert("Auto-copy (names only) complete. No missing items found.");
}
} catch (e) {
console.error(e);
alert(`Auto-copy (names only) failed: ${e.message || e}`);
$("#auto-copy-status").text(`Error: ${e.message || e}`);
} finally {
$("#auto-copy-names-only-btn").prop("disabled", false);
}
});
if (!albumCountsStarted) {
albumCountsStarted = true;
initAlbumLinkMissingCounts().catch(err => console.warn("[album-counts] init failed", err));
}
}
function getCurrentAlbumMissingNamesFromDOM() {
const names = [];
$(`${STAMP_IMG_SEL}.fake-stamp`).each(function () {
const name = $(this).attr("alt");
if (name && name !== "No Stamp") names.push(name);
});
return names;
}
/****************************************************************************************
* Album link missing counts
****************************************************************************************/
function stripTrailingCount(name) {
return (name || "")
.replace(/\s*\(\s*\d+\s*\)\s*$/g, "")
.replace(/\s*\(\s*na\s*\)\s*$/gi, "")
.trim();
}
function setAlbumLinkCount(aEl, count) {
const $a = $(aEl);
const $b = $a.find("b").first();
const countText = typeof count === "number" ? String(count) : "NA";
const cls = typeof count === "number" && count === 0 ? "idb-album-missing-count idb-zero" : "idb-album-missing-count";
if ($b.length) {
const base = stripTrailingCount($b.text());
$b.text(base);
const existing = $a.find(".idb-album-missing-count").first();
if (existing.length) {
existing.text(countText).attr("class", cls);
} else {
$b.after(` ${countText} `);
}
} else {
const base = stripTrailingCount($a.text());
$a.text(base + " ");
const existing = $a.find(".idb-album-missing-count").first();
if (existing.length) {
existing.text(countText).attr("class", cls);
} else {
$a.append(`${countText} `);
}
}
}
function getAlbumLinkElements() {
const links = Array.from(document.querySelectorAll('a[href*="stamps.phtml?type=album"][href*="page_id="]'));
const seen = new Set();
const out = [];
for (const a of links) {
const href = a.getAttribute("href") || "";
const match = href.match(/page_id=(\d+)/);
if (!match) continue;
const id = Number(match[1]);
if (seen.has(id)) continue;
seen.add(id);
const b = a.querySelector("b");
const name = stripTrailingCount((b ? b.textContent : a.textContent) || "");
out.push({ id, name, el: a });
}
out.sort((x, y) => x.id - y.id);
return out;
}
async function missingCountFromHtmlOnly(albumUrl) {
const html = await gmGet(albumUrl);
const doc = new DOMParser().parseFromString(html, "text/html");
return doc.querySelectorAll('.content table td > img[alt="No Stamp"]').length;
}
async function missingCountFromItemDb(albumId) {
const albumUrl = absoluteAlbumUrl(albumId);
let html = null;
try {
html = await gmGet(albumUrl);
} catch (e) {
console.warn("[album-counts] html fetch failed for", albumId, e);
return null;
}
const doc = new DOMParser().parseFromString(html, "text/html");
try {
const api = await gmGetJson("https://itemdb.com.br/api/v1/tools/album-helper?album_id=" + albumId);
const missing = extractMissingByComparing(doc, api);
return missing.length;
} catch (e) {
console.warn("[album-counts] ItemDB failed for", albumId, e);
try {
return doc.querySelectorAll('.content table td > img[alt="No Stamp"]').length;
} catch {
return null;
}
}
}
async function initAlbumLinkMissingCounts() {
const albumLinks = getAlbumLinkElements();
if (!albumLinks.length) return;
const cached = loadAlbumCountCache();
const counts = cached && cached.counts ? { ...cached.counts } : {};
for (const entry of albumLinks) {
if (counts[String(entry.id)] !== undefined) {
setAlbumLinkCount(entry.el, counts[String(entry.id)]);
}
}
$("#auto-copy-status").text("Updating album missing counts...");
for (let i = 0; i < albumLinks.length; i++) {
const { id, name, el } = albumLinks[i];
const key = String(id);
if (counts[key] !== undefined) continue;
$("#auto-copy-status").text(`Counting ${i + 1}/${albumLinks.length}: ${name}`);
let c = null;
if (id === 0) {
try {
c = await missingCountFromHtmlOnly(absoluteAlbumUrl(0));
} catch {
c = null;
}
} else {
c = await missingCountFromItemDb(id);
}
if (typeof c === "number" && Number.isFinite(c)) {
counts[key] = c;
setAlbumLinkCount(el, c);
saveAlbumCountCache(counts);
} else {
counts[key] = "NA";
setAlbumLinkCount(el, "NA");
saveAlbumCountCache(counts);
}
await sleep(250);
}
$("#auto-copy-status").text("Album missing counts updated.");
}
/****************************************************************************************
* Album discovery and per album extraction for auto mode
****************************************************************************************/
function discoverAlbumsOnPage() {
const links = Array.from(document.querySelectorAll('a[href*="stamps.phtml?type=album"][href*="page_id="]'));
const seen = new Set();
const albumList = [];
for (const a of links) {
const href = a.getAttribute("href") || "";
const match = href.match(/page_id=(\d+)/);
if (!match) continue;
const id = Number(match[1]);
if (seen.has(id)) continue;
seen.add(id);
const b = a.querySelector("b");
const rawName = (b ? b.textContent : a.textContent) || "";
const name = stripTrailingCount(rawName.trim().replace(/\s+/g, " "));
albumList.push({ id, name, href });
}
if (!albumList.length) {
for (let i = 1; i <= 49; i++) {
albumList.push({
id: i,
name: `Album ${i}`,
href: `/stamps.phtml?type=album&page_id=${i}` + (owner ? `&owner=${owner}` : "")
});
}
}
albumList.sort((a, b) => a.id - b.id);
return { albumList };
}
function absoluteAlbumUrl(id) {
const base = location.origin;
const q = `type=album&page_id=${id}` + (owner ? `&owner=${owner}` : "");
return `${base}/stamps.phtml?${q}`;
}
function extractMissingByComparing(doc, apiJson) {
const imgs = Array.from(doc.querySelectorAll(".content table td > img"));
const missing = [];
imgs.forEach((img, idx) => {
const position = idx + 1;
const alt = img.getAttribute("alt") || "";
const apiEntry = apiJson[position];
const itemData = apiEntry && apiEntry.itemData ? apiEntry.itemData : null;
if (alt === "No Stamp" && itemData && itemData.name && itemData.name !== "No Stamp") {
missing.push(itemData.name);
}
});
return missing;
}
/****************************************************************************************
* Original info panel builder
****************************************************************************************/
function createInfoContent(imgElement, itemData) {
const $img = $(imgElement),
src = $img.attr("src"),
stampName = $img.attr("alt"),
position = $img.attr("position"),
rarity = $img.attr("rarity");
if (stampName === "No Stamp") {
return `
${stampName}
Position: ${position}
This stamp has not been released yet.
`;
}
const hasStamp = $img.hasClass("fake-stamp") === false;
const hasStampText = `Status: ${hasStamp ? 'Collected! ' : 'Not collected '}`;
const rarityText = r => {
const rNum = parseInt(r.replace(/r/, ""), 10);
if (rNum <= 74) return "r" + r;
else if (rNum >= 75 && rNum <= 84) return `r${r} (uncommon) `;
else if (rNum >= 85 && rNum <= 89) return `r${r} (rare) `;
else if (rNum >= 90 && rNum <= 94) return `r${r} (very rare) `;
else if ((rNum >= 95 && rNum <= 98) || rNum === 100) return `r${r} (ultra rare) `;
else if (rNum === 99) return `r${r} (super rare) `;
else if (rNum >= 101 && rNum <= 104) return `r${r} (special) `;
else if (rNum >= 105 && rNum <= 110) return `r${r} (MEGA RARE) `;
else if (rNum >= 111 && rNum <= 179) return `r${r} (RARITY ${rNum}) `;
else if (rNum === 180) return `r${r} (retired) `;
else if (rNum === 200) return `r${r} (Artifact - 200) `;
return `r${r}`;
};
const createHelper = itemName => {
const linkmap = {
ssw: { img: "http://images.neopets.com/premium/shopwizard/ssw-icon.svg" },
sw: { url: "http://www.neopets.com/shops/wizard.phtml?string=%s", img: "http://images.neopets.com/themes/h5/basic/images/shopwizard-icon.png" },
tp: { url: "http://www.neopets.com/island/tradingpost.phtml?type=browse&criteria=item_exact&sort=newest&search_string=%s", img: "http://images.neopets.com/themes/h5/basic/images/tradingpost-icon.png" },
au: { url: "http://www.neopets.com/genie.phtml?type=process_genie&criteria=exact&auctiongenie=%s", img: "http://images.neopets.com/themes/h5/basic/images/auction-icon.png" },
sdb: { url: "http://www.neopets.com/safetydeposit.phtml?obj_name=%s&category=0", img: "http://images.neopets.com/images/emptydepositbox.gif" },
jni: { url: "https://items.jellyneo.net/search/?name=%s&name_type=3", img: "http://images.neopets.com/items/toy_plushie_negg_fish.gif" },
idb: { url: "https://itemdb.com.br/item/%s", img: "https://itemdb.com.br/favicon.svg" }
};
const slugify = text => {
return text
.toString()
.normalize("NFD")
.replace(/[\u0300-\u036f]/g, "")
.toLowerCase()
.trim()
.replace(/\s+/g, "-")
.replace(/[^\w-]+/g, "")
.replace(/-{2,}/g, "-");
};
const combiner = (item, url, image) => {
url = url.replace("%s", item);
return ` `;
};
const sswhelper = item => {
if (!hasPremium) return "";
return ` `;
};
return `
${sswhelper(itemName)}
${combiner(itemName, linkmap.sw.url, linkmap.sw.img)}
${combiner(itemName, linkmap.tp.url, linkmap.tp.img)}
${combiner(itemName, linkmap.au.url, linkmap.au.img)}
${combiner(itemName, linkmap.sdb.url, linkmap.sdb.img)}
${combiner(itemName, linkmap.jni.url, linkmap.jni.img)}
${combiner(slugify(itemName), linkmap.idb.url, linkmap.idb.img)}
`;
};
const slug = itemData && itemData.slug ? itemData.slug : "";
const priceObj = itemData && itemData.price ? itemData.price : null;
const priceText = priceObj && priceObj.value ? format(priceObj.value) + " NP" : "Unknown";
const inflatedWarn = priceObj && priceObj.inflated ? "⚠️" : "";
return `
${stampName} ${rarityText(rarity)}
Position: ${position}
Price: ${slug ? `${inflatedWarn}${priceText} ` : `${inflatedWarn}${priceText} `}
${hasStampText}
${createHelper(stampName)}
`;
}
/****************************************************************************************
* Info panel shell and extra links
****************************************************************************************/
$(".content table").after(`
`);
if (hasPremium) {
$(".content table").before(
`Double-click the stamp to search it on the Super Shop Wizard!
`
);
}
if (albumID > 0) {
const idbLogo = ` `;
$(".content").append(
`${idbLogo} Album info ${idbLogo}
`
);
}
/****************************************************************************************
* SSW icon
****************************************************************************************/
$("body").on("click", ".stamp-ssw-helper", function () {
const item = $(this).attr("item");
sswopen(item);
});
function sswopen(item) {
if ($(".sswdrop").hasClass("panel_hidden")) {
$("#sswmenu .imgmenu").click();
}
if ($("#ssw-tabs-1").hasClass("ui-tabs-hide")) {
$("#button-new-search").click();
}
$("#price-limited").prop("checked", true);
$("#price-limited")[0].checked = true;
$("#ssw-criteria").val("exact");
$("#searchstr").val(item);
document.querySelector("#button-search").click();
document.querySelector("#ssw-button-search").click();
$("#button-new-search").click();
}
/****************************************************************************************
* Stamp prev and next arrows
****************************************************************************************/
$("body").on("click", ".stamp-info-arrow", function () {
const isNext = $(this).hasClass("next-arrow");
const isPrev = $(this).hasClass("prev-arrow");
const position = parseInt($("#current-stamp-pos").html(), 10);
const newPosition = (function () {
if (position === 25 && isNext) return 1;
else if (position === 1 && isPrev) return 25;
else if (isNext) return position + 1;
else if (isPrev) return position - 1;
return position;
})();
$(`img[position='${newPosition}']`).click();
});