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

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램을 설치해야 합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

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