Ir para conteúdo
  • 2 Quem está por aqui   0 membros estão online

    • Nenhum usuário registrado visualizando esta página.

[NEOPETS] TVW Prize Shop Pricer (Script)


Mumph
 Compartilhar

Posts Recomendados

 

[MUMPH] TVW Prize Shop Pricer

Exibe os preços de mercado do itemdb, a eficiência NP/pt e um painel com as 5 melhores ofertas na loja de recompensas de The Void Within.

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

■ INFORMAÇÕES BÁSICAS

Nome do utilitário: [MUMPH] TVW Prize Shop Pricer
Última modificação: 12/06/2026.

Criado por: Mumph

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

 

■ DESCRIÇÃO

Citar

Este script foi desenvolvido para otimizar sua experiência na loja de recompensas do evento "The Void Within" (TVW) no Neopets. Ele injeta informações diretamente na interface da página, exibindo o valor de mercado dos itens de acordo com o banco de dados do itemdb, a eficiência de lucro calculada em Neopoints por ponto da loja (NP/pt) e adiciona um painel fixo com o Top 5 de melhores ofertas disponíveis.

 


Nota Importante: Há alguns meses, o ItemDB implementou a exigência de uma sessão de cookie válida (24 horas se estiver desconectado, 14 dias se estiver conectado). Em teoria, basta acessar o site ItemDB.com.br para que ele comece a funcionar.

 

 

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

■ COMO INSTALAR / UTILIZAR

  • Instale a extensão Tampermonkey na loja oficial de extensões do seu navegador (ou no Firefox para Android).
  • Se você utiliza um navegador baseado em Chromium (Chrome, Edge, Opera, etc.), ative o Modo de Desenvolvedor na aba de extensões do seu navegador e, nos detalhes do Tampermonkey, certifique-se de ativar a opção Permitir scripts de usuário.
  • Clique no ícone do Tampermonkey, selecione "Adicionar novo script" (ou o ícone de +), apague todo o código padrão e cole o código correspondente. Salve o arquivo. Acesse a página de recompensas do evento no Neopets para ver o script em funcionamento de forma automática!

 

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

■ USERSCRIPT

 

 

 

 


// ==UserScript==
// @name         [Mumph] TVW Prize Shop Pricer
// @namespace  

É necessário se cadastrar para acessar o conteúdo.

// @version      2.1.1
// @description  Displays itemdb market prices, NP/pt efficiency, and a Top 5 deals panel on The Void Within reward shop.
// @author       Mumph
// @match        *://*.neopets.com/tvw/rewards/*
// @connect      itemdb.com.br
// @grant        GM_xmlhttpRequest
// @grant        GM_addStyle
// ==/UserScript==

(function () {
  'use strict';

  const CONFIG = {
    PREFIX: '[TVW Pricer]',
    API_URL: 'https://itemdb.com.br/api/v1/items/many',
    ITEM_URL: 'https://itemdb.com.br/item/',
    INIT_DELAY: 800
  };

  const intl = new Intl.NumberFormat();
  const log = (...args) => console.log(CONFIG.PREFIX, ...args);
  const warn = (...args) => console.warn(CONFIG.PREFIX, ...args);
  const error = (...args) => console.error(CONFIG.PREFIX, ...args);

  GM_addStyle(`
    .tvw-badge { display: flex; flex-direction: column; align-items: center; gap: 3px; margin-top: 6px; padding: 5px 6px 6px; background: rgba(0,0,0,.45); border-radius: 7px; font-size: 11px; line-height: 1.25; color: #f0e8ff; font-family: 'Segoe UI', Arial, sans-serif; }
    .tvw-badge a { color: inherit !important; text-decoration: underline dotted !important; }
    .tvw-badge-rarity { font-size: 10px; font-weight: 700; opacity: .85; }
    .tvw-badge-price { font-size: 12px; font-weight: 700; color: #fff; }
    .tvw-badge-sale { font-size: 10px; font-weight: 800; letter-spacing: .03em; }
    .tvw-badge-nppt { font-size: 11px; font-weight: 800; padding: 1px 6px; border-radius: 4px; letter-spacing: .02em; border: 1.5px solid currentColor; margin-top: 1px; }
    .tvw-badge-missing { font-size: 10px; color: #aaa; font-style: italic; }
    #tvw-top5 { position: fixed; bottom: 18px; right: 18px; z-index: 99998; width: 260px; background: linear-gradient(145deg, #1a0a2e, #2d1060); border: 1.5px solid #7c4dff; border-radius: 12px; box-shadow: 0 6px 28px rgba(0,0,0,.7); font-family: 'Segoe UI', Arial, sans-serif; color: #f0e8ff; overflow: hidden; transition: transform .25s ease, opacity .25s ease; }
    #tvw-top5.tvw-collapsed .tvw-top5-body { display: none; }
    #tvw-top5-header { display: flex; align-items: center; justify-content: space-between; padding: 9px 12px; background: rgba(124,77,255,.25); cursor: pointer; user-select: none; border-bottom: 1px solid rgba(124,77,255,.4); }
    #tvw-top5-header span { font-size: 12px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; }
    #tvw-top5-toggle { font-size: 14px; opacity: .7; }
    .tvw-top5-body { padding: 8px 10px 10px; }
    .tvw-top5-loading { text-align: center; font-size: 11px; opacity: .6; padding: 8px 0; }
    .tvw-top5-row { display: flex; align-items: center; gap: 8px; padding: 5px 4px; border-radius: 6px; transition: background .15s; cursor: default; }
    .tvw-top5-row:hover { background: rgba(255,255,255,.07); }
    .tvw-top5-rank { font-size: 13px; font-weight: 800; color: #ffd740; min-width: 18px; text-align: center; }
    .tvw-top5-rank.silver { color: #cfd8dc; }
    .tvw-top5-rank.bronze { color: #ff8a65; }
    .tvw-top5-rank.plain { color: #9e9e9e; }
    .tvw-top5-img { width: 30px; height: 30px; border-radius: 4px; object-fit: contain; background: rgba(0,0,0,.3); flex-shrink: 0; }
    .tvw-top5-info { flex: 1; min-width: 0; }
    .tvw-top5-name { font-size: 11px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; color: #e8d5ff; }
    .tvw-top5-meta { font-size: 10px; color: #b39ddb; margin-top: 1px; }
    .tvw-top5-nppt { font-size: 11px; font-weight: 800; text-align: right; flex-shrink: 0; padding: 2px 5px; border-radius: 4px; border: 1.5px solid currentColor; }
    .tvw-top5-divider { border: none; border-top: 1px solid rgba(124,77,255,.25); margin: 4px 0; }
    #tvw-top5-footer { font-size: 9px; color: #7c6b99; text-align: center; padding: 0 8px 7px; }
  `);

  function npptColor(v) {
    if (v >= 10000) return '#00c853';
    if (v >= 5000) return '#76d275';
    if (v >= 2000) return '#c6e48b';
    if (v >= 500) return '#f9a825';
    return '#ef5350';
  }

  function rarityColor(r) {
    if (r <= 74) return '#9e9e9e';
    if (r <= 100) return '#66bb6a';
    if (r <= 104) return '#f06292';
    if (r <= 110) return '#ffa726';
    if (r <= 179) return '#ef5350';
    if (r === 180) return '#bdbdbd';
    if (r <= 250) return '#ef5350';
    return '#ce93d8';
  }

  function getRankClass(i) { return ['', 'silver', 'bronze', 'plain', 'plain'] ?? 'plain'; }
  function getRankLabel(i) { return ['🥇', '🥈', '🥉', '4', '5'] ?? String(i + 1); }

  function createTop5Panel() {
    const panel = document.createElement('div');
    panel.id = 'tvw-top5';
    panel.innerHTML = `
      <div id="tvw-top5-header">
        <span>🏆 Top 5 NP/pt Deals</span>
        <span id="tvw-top5-toggle">▲</span>
      </div>
      <div class="tvw-top5-body">
        <div class="tvw-top5-loading">Loading prices…</div>
      </div>
    `;
    document.body.appendChild(panel);
    panel.querySelector('#tvw-top5-header').addEventListener('click', () => {
      const collapsed = panel.classList.toggle('tvw-collapsed');
      panel.querySelector('#tvw-top5-toggle').textContent = collapsed ? '▼' : '▲';
    });
    return panel;
  }

  function renderTop5(panel, entries) {
    const body = panel?.querySelector('.tvw-top5-body');
    if (!body) return;
    if (!entries.length) {
      body.innerHTML = '<div class="tvw-top5-loading">No priced items found.</div>';
      return;
    }
    const top = entries.slice(0, 5);
    body.innerHTML = '';
    top.forEach((e, i) => {
      if (i > 0) {
        const hr = document.createElement('hr');
        hr.className = 'tvw-top5-divider';
        body.appendChild(hr);
      }
      const row = document.createElement('div');
      row.className = 'tvw-top5-row';
      row.title = `${e.name}\n${intl.format(e.npVal)} NP ÷ ${intl.format(e.plotCost)} pts`;
      const npptEl = document.createElement('span');
      npptEl.className = 'tvw-top5-nppt';
      npptEl.style.color = npptColor(e.nppt);
      npptEl.textContent = intl.format(e.nppt);
      row.innerHTML = `
        <span class="tvw-top5-rank ${getRankClass(i)}">${getRankLabel(i)}</span>
        <img class="tvw-top5-img" src="${e.imgSrc}" alt="${e.name}">
        <div class="tvw-top5-info">
          <div class="tvw-top5-name">
            <a href="${CONFIG.ITEM_URL}${e.slug}?utm_content=tvwPricer" target="_blank" rel="noopener noreferrer">${e.name}</a>
          </div>
          <div class="tvw-top5-meta">${intl.format(e.npVal)} NP · ${intl.format(e.plotCost)} pts</div>
        </div>
      `;
      row.appendChild(npptEl);
      body.appendChild(row);
    });
  }

  function injectBadge(cardEl, item, plotCost) {
    if (!cardEl) return;
    cardEl.querySelector('.tvw-badge')?.remove();
    const badge = document.createElement('div');
    badge.className = 'tvw-badge';
    try {
      if (!item) throw new Error('Item not in database');
      if (item.rarity) {
        const rl = document.createElement('span');
        rl.className = 'tvw-badge-rarity';
        rl.style.color = rarityColor(item.rarity);
        rl.textContent = `r${item.rarity}${item.ff_points ? ` · ${item.ff_points} ff` : ''}`;
        badge.appendChild(rl);
      }
      if (item.saleStatus && item.saleStatus.status !== 'regular') {
        const ss = document.createElement('span');
        ss.className = 'tvw-badge-sale';
        ss.style.color = item.saleStatus.status === 'ets' ? '#69f0ae' : '#ff5252';
        ss.textContent = `[${item.saleStatus.status.toUpperCase()}]`;
        badge.appendChild(ss);
      }
      const priceEl = document.createElement('a');
      priceEl.className = 'tvw-badge-price';
      priceEl.href = `${CONFIG.ITEM_URL}${item.slug}?utm_content=tvwPricer`;
      priceEl.target = '_blank';
      priceEl.rel = 'noopener noreferrer';
      if (item.status === 'no trade') {
        priceEl.textContent = 'No Trade';
        priceEl.style.color = '#9e9e9e';
      } else if (item.isNC) {
        priceEl.textContent = item.ncValue ? `${item.ncValue.range} caps` : 'NC Item';
        priceEl.style.color = '#ce93d8';
      } else if (!item.price?.value) {
        priceEl.textContent = '??? NP';
        priceEl.style.color = '#9e9e9e';
      } else {
        const npVal = item.price.value;
        priceEl.textContent = `${item.price.inflated ? '⚠ ' : ''}${intl.format(npVal)} NP`;
        if (plotCost > 0) {
          const nppt = Math.round(npVal / plotCost);
          const npptEl = document.createElement('span');
          npptEl.className = 'tvw-badge-nppt';
          npptEl.style.color = npptColor(nppt);
          npptEl.title = `${intl.format(npVal)} NP ÷ ${intl.format(plotCost)} pts`;
          npptEl.textContent = `${intl.format(nppt)} NP/pt`;
          badge.appendChild(priceEl);
          badge.appendChild(npptEl);
          cardEl.appendChild(badge);
          return;
        }
      }
      badge.appendChild(priceEl);
      if (item.isMissingInfo) {
        const mi = document.createElement('span');
        mi.className = 'tvw-badge-missing';
        mi.innerHTML = `<a href="https://itemdb.com.br/contribute?utm_content=tvwPricer" target="_blank" rel="noopener noreferrer" style="color:#9e9e9e;">⚠ needs info</a>`;
        badge.appendChild(mi);
      }
    } catch (e) {
      const fb = document.createElement('span');
      fb.className = 'tvw-badge-missing';
      fb.textContent = 'Not found';
      badge.appendChild(fb);
    }
    cardEl.appendChild(badge);
  }

  function init() {
    try {
      const top5Panel = createTop5Panel();
      log('Scanning prize shop…');
      const cards = Array.from(document.querySelectorAll('.plothub-shop-item[data-purchase]'));
      if (!cards.length) {
        warn('No prize cards found — ensure you are on the TVW rewards page.');
        return;
      }
      log(`Found ${cards.length} purchasable prize cards.`);
      const idToCards = {};
      const idList = [];
      cards.forEach(card => {
        const id = card.dataset.purchase;
        const plotCost = parseInt(card.dataset.price, 10) || 0;
        const imgSrc = card.querySelector('img.plothub-item-img')?.src ?? '';
        const itemName = card.querySelector('.plothub-item-name')?.textContent.trim() ?? 'Unknown';
        if (!idToCards[id]) {
          idToCards[id] = [];
          idList.push(id);
        }
        idToCards[id].push({ el: card, plotCost, imgSrc, itemName });
      });
      log(`Fetching prices for ${idList.length} unique items…`);
      GM_xmlhttpRequest({
        method: 'POST',
        url: CONFIG.API_URL,
        headers: { 'Content-Type': 'application/json' },
        data: JSON.stringify({ item_id: idList }),
        timeout: 15000,
        onload(res) {
          if (res.status !== 200) {
            error('API error:', res.status, res.statusText);
            return;
          }
          let itemData;
          try { itemData = JSON.parse(res.responseText); }
          catch (e) { error('Parse error:', e); return; }
          let priced = 0, missing = 0, nc = 0;
          const pricedEntries = [];
          idList.forEach(id => {
            const item = itemData[id] ?? null;
            const entries = idToCards[id] || [];
            entries.forEach(({ el, plotCost }) => { injectBadge(el, item, plotCost); });
            if (!item) {
              missing++;
            } else if (item.isNC) {
              nc++;
            } else if (item.price?.value) {
              priced++;
              const { plotCost: pc, imgSrc: isrc } = entries[0];
              const nppt = Math.round(item.price.value / pc);
              pricedEntries.push({
                name: item.name ?? entries[0].itemName,
                slug: item.slug,
                imgSrc: isrc,
                npVal: item.price.value,
                plotCost: pc,
                nppt: nppt
              });
            }
          });
          pricedEntries.sort((a, b) => b.nppt - a.nppt);
          renderTop5(top5Panel, pricedEntries);
          log(`Summary: ${priced} priced · ${nc} NC · ${missing} missing`);
        },
        onerror(err) { error('Network error:', err); },
        ontimeout() { error('Request timed out.'); }
      });
    } catch (err) {
      error('Initialization failed:', err);
    }
  }

  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', () => setTimeout(init, CONFIG.INIT_DELAY));
  } else {
    setTimeout(init, CONFIG.INIT_DELAY);
  }

})();
 

 

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Cordialmente, Mumph
 
Editado por Mumph
Legibilidade
Link para o comentário
Compartilhar em outros sites

Crie uma conta ou entre para comentar

Você precisar ser um membro para fazer um comentário

Criar uma conta

Crie uma nova conta em nossa comunidade. É fácil!

Crie uma nova conta

Entrar

Já tem uma conta? Faça o login.

Entrar Agora
 Compartilhar

×
×
  • Criar Novo...

Informação Importante

Nós fazemos uso de cookies no seu dispositivo para ajudar a tornar este site melhor. Você pode ajustar suas configurações de cookies , caso contrário, vamos supor que você está bem para continuar.