Challengers Tower - Inventory Export

Adds an Export button to the Inventory page that copies a plain-text list of all items with tier, quantity, and equipped status

Tendrás que instalar una extensión para tu navegador como Tampermonkey, Greasemonkey o Violentmonkey si quieres utilizar este script.

You will need to install an extension such as Tampermonkey to install this script.

Tendrás que instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Userscripts para instalar este script.

Tendrás que instalar una extensión como Tampermonkey antes de poder instalar este script.

Necesitarás instalar una extensión para administrar scripts de usuario si quieres instalar este script.

(Ya tengo un administrador de scripts de usuario, déjame instalarlo)

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

(Ya tengo un administrador de estilos de usuario, déjame instalarlo)

// ==UserScript==
// @name         Challengers Tower - Inventory Export
// @description  Adds an Export button to the Inventory page that copies a plain-text list of all items with tier, quantity, and equipped status
// @namespace    https://challengerstower.com
// @version      1.7.0
// @author       Zach
// @license      MIT
// @match        *://challengerstower.com/*
// @run-at       document-end
// @grant        none
// ==/UserScript==

function ctCopy(str) {
  var ta = document.createElement("textarea");
  ta.value = str;
  ta.style.position = "fixed";
  ta.style.left = "-9999px";
  document.body.appendChild(ta);
  ta.select();
  var ok = false;
  try { ok = document.execCommand("copy"); } catch(e) {}
  document.body.removeChild(ta);
  return ok;
}

function ctGetAlpineData(el) {
  // Try different Alpine versions' internal property names
  if(el._x_dataStack && el._x_dataStack[0]) return el._x_dataStack[0];
  if(el.__x && el.__x.$data) return el.__x.$data;
  if(el._x_data) return el._x_data;
  try {
    if(typeof Alpine !== "undefined" && Alpine.$data) return Alpine.$data(el);
  } catch(e) {}
  return null;
}

function ctFindGroups() {
  var els = document.querySelectorAll("[x-data]");
  for(var i=0; i<els.length; i++) {
    var data = ctGetAlpineData(els[i]);
    if(data && data.groups && data.groups.length > 0) {
      return data.groups;
    }
  }
  return null;
}

function ctScrapeDOM() {
  // Fallback: scrape from rendered DOM, inventory section only
  var results = [];
  var seen = {};

  // Find the inventory section by looking for the "Inventory" heading
  var headings = document.querySelectorAll("h3");
  var inventorySection = null;
  for(var i=0; i<headings.length; i++) {
    if(headings[i].textContent.trim() === "Inventory") {
      inventorySection = headings[i].closest(".bg-white, .bg-gray-800");
      break;
    }
  }

  var container = inventorySection || document;
  var nameEls = container.querySelectorAll("span.font-medium");

  nameEls.forEach(function(el) {
    var name = el.textContent.trim();
    if(!name || seen[name]) return;

    var card = el.closest(".bg-gray-100, .rounded-lg");
    if(!card) return;
    var cardText = card.textContent || "";

    var tierMatch = cardText.match(/\bT([0-9]+)\b/);
    var tier = tierMatch ? "T" + tierMatch[1] : "";
    var isUlt = /\bULT\b/.test(cardText) ? " [ULT]" : "";
    var equipped = /Equipped/i.test(cardText) ? " [Equipped]" : "";
    var upgradeMatch = cardText.match(/T[0-9]+:\s*[0-9]+%/);
    var progress = upgradeMatch ? " (" + upgradeMatch[0] + ")" : "";

    seen[name] = true;
    results.push(name + (tier ? " [" + tier + "]" : "") + isUlt + progress + equipped);
  });

  return results.length > 0 ? results : null;
}

function ctScrape() {
  var groups = ctFindGroups();
  if(groups) {
    return groups.map(function(g) {
      var tier = "T" + g.highest_tier;
      var isUlt = g.item.ultimate ? " [ULT]" : "";
      var count = g.total_count > 1 ? " x" + g.total_count : "";
      var progress = g.maxed_out ? " [MAX]" : " (T" + g.next_tier + ": " + Math.round(g.progress) + "%)";
      var equipped = g.equipped_tiers.length > 0 ? " [Equipped]" : "";
      return g.item.name + " [" + tier + "]" + isUlt + count + progress + equipped;
    });
  }
  // Fall back to DOM scraping
  return ctScrapeDOM();
}

function ctAddButton() {
  if(document.getElementById("ct-inv-export")) return;
  if(!/\/items/.test(window.location.href)) return;

  var btn = document.createElement("button");
  btn.id = "ct-inv-export";
  btn.textContent = "Export Inventory";
  btn.style.cssText = "position:fixed;bottom:80px;right:16px;z-index:99999;padding:10px 16px;background:#4f8ef7;color:#fff;border:none;border-radius:8px;font-weight:bold;font-size:14px;cursor:pointer;box-shadow:0 2px 8px rgba(0,0,0,0.35);";

  btn.onclick = function() {
    var lines = ctScrape();
    if(!lines) {
      btn.textContent = "Not ready yet";
      btn.style.background = "#e74c3c";
      setTimeout(function() {
        btn.textContent = "Export Inventory";
        btn.style.background = "#4f8ef7";
      }, 2000);
      return;
    }
    var out = ["=== CT Inventory (" + lines.length + " items) ==="].concat(lines).join("\n");
    console.log(out);
    var ok = ctCopy(out);
    btn.textContent = ok ? "Copied! (" + lines.length + ")" : "See console";
    btn.style.background = ok ? "#2ecc71" : "#e74c3c";
    setTimeout(function() {
      btn.textContent = "Export Inventory";
      btn.style.background = "#4f8ef7";
    }, 3000);
  };

  document.body.appendChild(btn);
}

var ctAttempts = 0;
var ctPoll = setInterval(function() {
  ctAttempts++;
  if(!/\/items/.test(window.location.href)) {
    clearInterval(ctPoll);
    return;
  }
  if(document.body && (ctFindGroups() || ctAttempts >= 40)) {
    ctAddButton();
    clearInterval(ctPoll);
  }
}, 500);