Greasy Fork is available in English.

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

Aby zainstalować ten skrypt, wymagana jest instalacje jednego z następujących rozszerzeń: Tampermonkey, Greasemonkey lub Violentmonkey.

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

Aby zainstalować ten skrypt, wymagana jest instalacje jednego z następujących rozszerzeń: Tampermonkey, Violentmonkey.

Aby zainstalować ten skrypt, wymagana będzie instalacja rozszerzenia Tampermonkey lub Userscripts.

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

Aby zainstalować ten skrypt, musisz zainstalować rozszerzenie menedżera skryptów użytkownika.

(Mam już menedżera skryptów użytkownika, pozwól mi to zainstalować!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

Będziesz musiał zainstalować rozszerzenie menedżera stylów użytkownika, aby zainstalować ten styl.

Będziesz musiał zainstalować rozszerzenie menedżera stylów użytkownika, aby zainstalować ten styl.

Musisz zainstalować rozszerzenie menedżera stylów użytkownika, aby zainstalować ten styl.

(Mam już menedżera stylów użytkownika, pozwól mi to zainstalować!)

// ==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);