크롬브라우저에서 쓰셈
Violentmonkey 라는 확장 프로그램 필요함

귀찮으니 자세한 사용방법은 생략한다 

사용책임은 각자한테 있음




이하 소스코드
// ==UserScript==
// @name         LA Card Hook - write img filename into clicked button
// @namespace    daeyeol.lostark.cardhook
// @version      1.0.0
// @description  Intercept GetCard251105 response and put filename into the clicked button
// @match        https://lostark.game.onstove.com/*
// @run-at       document-start
// @grant        none
// ==/UserScript==
(function () {
  'use strict';

  // 🔎 타겟 API 엔드포인트 (부분일치)
  const TARGET_PATH = '/Promotion/Card/GetCard251105';

  // 마지막 클릭된 버튼(또는 버튼 역할 요소) 추적
  let lastClickedBtn = null;

  // 버튼 판별 보조
  function asButton(el) {
    if (!el) return null;
    // 실제 <button> 또는 role=button, 클릭 가능한 <a>, data-속성 등 가볍게 커버
    return el.closest('button, [role="button"], a[role="button"], a.button, .btn, [data-button], [data-btn]') || el.closest('a, button');
  }

  // 캡처 단계에서 pointerdown / mousedown으로 "어떤 버튼"이 눌렸는지 먼저 기록
  function clickCapture(e) {
    const btn = asButton(e.target);
    if (btn) lastClickedBtn = btn;
  }
  window.addEventListener('pointerdown', clickCapture, true);
  window.addEventListener('mousedown', clickCapture, true);

  // 파일명 추출 (확장자 제외)
  function extractBaseNameFromImg(imgPath) {
    if (!imgPath) return null;
    // 예: "/2025/event/.../card21.webp" -> "card21"
    const m = String(imgPath).match(/([^/]+).[a-z0-9]+$/i);
    return m ? m[1] : null;
  }

  // 버튼 내부에 파일명 쓰기 (원래 텍스트 보존/병기 옵션)
  function writeIntoButton(btn, filename) {
    if (!btn || !filename) return;
    // 원래 텍스트를 덮어쓰기보다는, 안전하게 넣는 쪽으로. 필요시 아래 한 줄로 교체.
    // btn.textContent = filename;
    // 깔끔한 배지로 표시
    const tag = document.createElement('span');
    tag.textContent = filename;
    tag.style.marginLeft = '6px';
    tag.style.padding = '2px 6px';
    tag.style.borderRadius = '999px';
    tag.style.fontSize = '20px';
    tag.style.lineHeight = '1';
    tag.style.border = '1px solid #fff';
    tag.style.opacity = '0.9';
    tag.style.color = '#fff'
    tag.style.background = '#111'
    // 중복 삽입 방지용 클래스
    tag.className = 'la-cardhook-filename';
    // 기존 표시 제거 후 다시 삽입
    btn.querySelectorAll('.la-cardhook-filename').forEach(n => n.remove());
    btn.appendChild(tag);
  }

  // ------- fetch 후킹 -------
  const origFetch = window.fetch;
  window.fetch = async function (input, init) {
    const url = typeof input === 'string' ? input : (input && input.url) || '';
    const res = await origFetch.call(this, input, init);
    try {
      if (url.includes(TARGET_PATH)) {
        // 응답 복제 후 JSON 파싱
        const clone = res.clone();
        clone.json().then(data => {
          // { img: "/2025/.../card21.webp" } 구조 가정
          const filename = extractBaseNameFromImg(data && data.img);
          if (!filename) return;
          // 클릭했던 버튼이 아직 DOM에 살아있을 때만 기록
          const btnRef = lastClickedBtn && document.contains(lastClickedBtn) ? lastClickedBtn : null;
          if (btnRef) writeIntoButton(btnRef, filename);
        }).catch(() => {});
      }
    } catch (e) {
      // no-op
    }
    return res;
  };

  // ------- XHR 후킹 (사이트가 XHR을 쓸 경우 대비) -------
  const origOpen = XMLHttpRequest.prototype.open;
  const origSend = XMLHttpRequest.prototype.send;

  XMLHttpRequest.prototype.open = function (method, url, ...rest) {
    this.__isTarget = typeof url === 'string' && url.includes(TARGET_PATH);
    return origOpen.call(this, method, url, ...rest);
  };

  XMLHttpRequest.prototype.send = function (...args) {
    if (this.__isTarget) {
      this.addEventListener('load', () => {
        try {
          const data = JSON.parse(this.responseText);
          const filename = extractBaseNameFromImg(data && data.img);
          if (!filename) return;
          const btnRef = lastClickedBtn && document.contains(lastClickedBtn) ? lastClickedBtn : null;
          if (btnRef) writeIntoButton(btnRef, filename);
        } catch (e) { /* ignore */ }
      });
    }
    return origSend.apply(this, args);
  };
})();