// ==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(`Trading Post`); $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-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 `
Previous ${stampName} Next
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 `
Previous ${stampName}
${rarityText(rarity)}
Next
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(); });