ManagerZone Auto-Claim

Auto-claims MZ event tickets when the Claim button enables AND the multiplier has dropped below base, from any MZ tab via a hidden iframe.

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

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

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

Advertisement:

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

Advertisement:

// ==UserScript==
// @name         ManagerZone Auto-Claim
// @namespace    mz-auto-claim
// @version      1.1.0
// @description  Auto-claims MZ event tickets when the Claim button enables AND the multiplier has dropped below base, from any MZ tab via a hidden iframe.
// @match        https://www.managerzone.com/*
// @run-at       document-idle
// @grant        none
// @license MIT
// ==/UserScript==
(function () {
  'use strict';

  const ENABLED = true;
  const CHECK_INTERVAL = 180_000;       // cycle cadence (~3min)
  const IFRAME_POLL_INTERVAL = 500;     // how often to check #claim in the iframe
  const IFRAME_POLL_DURATION = 5_000;   // how long to wait for the button to enable
  const CONFIRM_TIMEOUT = 5_000;        // how long to wait for re-disable (claim confirmed)
  const EVENT_URL = 'https://www.managerzone.com/?p=event';

  const log = (msg) => console.log(`[MZ Auto-Claim] ${msg}`);
  const warn = (msg) => console.warn(`[MZ Auto-Claim] ${msg}`);

  if (window.self !== window.top) return;
  if (!ENABLED) { log('disabled via config'); return; }

  const isLoggedOutUrl = (href) => href.includes('p=logout');
  const findClaimButton = (doc) => doc.getElementById('claim');
  const isClaimReady = (el) => !!el && !el.classList.contains('buttondiv_disabled');
  const isClaimDisabled = (el) => !!el && el.classList.contains('buttondiv_disabled');

  // The multiplier ratchets DOWN from base (x8) toward a x4 floor as matches are won/drawn;
  // claiming resets it to base. Reading the Highcharts donut: series-0 = the big centre value
  // (current), series-1 = the wheel segments (the possible values; base = the max).
  // Returns { current, base } or null if it can't be read.
  const readMultiplier = (doc) => {
    const c = doc.getElementById('current-multiplier-container');
    if (!c) return null;
    const num = (el) => (el ? parseInt(el.textContent.replace(/\D/g, ''), 10) : NaN);
    const current = num(c.querySelector('.highcharts-data-labels.highcharts-series-0 text tspan'));
    const segs = [...c.querySelectorAll('.highcharts-data-labels.highcharts-series-1 text tspan')]
      .map((t) => parseInt(t.textContent.replace(/\D/g, ''), 10))
      .filter((n) => Number.isFinite(n));
    const base = segs.length ? Math.max(...segs) : NaN;
    if (!Number.isFinite(current) || !Number.isFinite(base)) return null;
    return { current, base };
  };

  const pollUntil = (fn, interval, duration) => new Promise((resolve) => {
    const deadline = Date.now() + duration;
    const tick = () => {
      let result = null;
      try { result = fn(); } catch (e) { /* iframe not ready yet */ }
      if (result) { resolve(result); return; }
      if (Date.now() >= deadline) { resolve(null); return; }
      setTimeout(tick, interval);
    };
    tick();
  });

  let cycleRunning = false;

  const runClaimCycle = () => {
    if (cycleRunning) { log('cycle already running — skipping'); return Promise.resolve(); }
    cycleRunning = true;
    return new Promise((resolve) => {
      const iframe = document.createElement('iframe');
      iframe.style.display = 'none';
      iframe.setAttribute('aria-hidden', 'true');
      iframe.src = EVENT_URL;

      const teardown = () => { iframe.remove(); cycleRunning = false; resolve(); };

      iframe.addEventListener('load', async () => {
        try {
          let doc, href;
          try {
            href = iframe.contentWindow.location.href;
            doc = iframe.contentDocument;
          } catch (e) {
            warn('cross-frame access blocked — aborting cycle');
            teardown();
            return;
          }

          if (isLoggedOutUrl(href)) {
            warn('logged out — skipping (session lapsed)');
            teardown();
            return;
          }

          const readyBtn = await pollUntil(
            () => { const el = findClaimButton(doc); return isClaimReady(el) ? el : null; },
            IFRAME_POLL_INTERVAL,
            IFRAME_POLL_DURATION,
          );

          if (!readyBtn) {
            const present = findClaimButton(doc);
            if (!present) warn('no claim button — wrong page or unexpected layout?');
            else log('claim locked (still cooling down)');
            teardown();
            return;
          }

          // Only claim if the multiplier has been played down below base — claiming at base
          // would just reset a multiplier that has nothing accumulated to reset.
          const mult = readMultiplier(doc);
          if (mult && mult.current >= mult.base) {
            log(`multiplier at base (x${mult.current}) — nothing to reset, leaving unclaimed`);
            teardown();
            return;
          }
          log(mult
            ? `multiplier x${mult.current} (base x${mult.base}) — claiming`
            : 'multiplier unreadable — claiming anyway');

          readyBtn.click();

          const confirmed = await pollUntil(
            () => isClaimDisabled(findClaimButton(doc)) ? true : null,
            IFRAME_POLL_INTERVAL,
            CONFIRM_TIMEOUT,
          );

          log(confirmed
            ? `claimed (confirmed) at ${new Date().toISOString()}`
            : `clicked but not confirmed at ${new Date().toISOString()}`);
          teardown();
        } catch (e) {
          warn(`unexpected error in cycle: ${e}`);
          teardown();
        }
      });

      if (!document.body) { teardown(); return; }
      document.body.appendChild(iframe);
    });
  };

  log('started');

  runClaimCycle();

  setInterval(() => {
    if (document.hidden) {
      log('hidden -> reload (keep-alive + fresh cycle on reload)');
      location.reload();
    } else {
      runClaimCycle();
    }
  }, CHECK_INTERVAL);
})();