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