// ==UserScript== // @name MZ - Training Report Checker // @namespace douglaskampl // @version 3.1 // @description Checks if the training report is already out for the current day // @author Douglas // @match https://www.managerzone.com/* // @icon https://www.google.com/s2/favicons?sz=64&domain=managerzone.com // @grant GM_addStyle // @license MIT // @downloadURL none // ==/UserScript== GM_addStyle(` #training_report_modal { position: absolute; background: #111; color: #ffd600 !important; padding: 5px; border: 2px solid blue; border-radius: 3px; font-size: 14px; display: none; z-index: 9999; text-align: center; animation-duration: 0.3s; transition: opacity 0.3s ease-in-out; } @keyframes fadeIn { from { opacity: 0; transform: scale(0.95); } to { opacity: 1; transform: scale(1); } } @keyframes fadeOut { from { opacity: 1; transform: scale(1); } to { opacity: 0; transform: scale(0.95); } } .fade-in { animation: fadeIn 0.3s forwards; } .fade-out { animation: fadeOut 0.3s forwards; } /* Icon subtle professional animation */ .report-icon { display: inline-block; transition: transform 0.3s ease-in-out; } .report-icon:hover { transform: rotate(20deg) scale(1.1); } `); (function () { 'use strict'; const CONFIG = { START_HOUR: 19, START_MINUTE: 1, END_HOUR: 23, END_MINUTE: 59, CHECK_INTERVAL: 300000, TIMEZONE_OFFSET: -3, READY_COLOR: 'lightgreen', // released = light green NOT_READY_COLOR: 'white', // no TR = white SATURDAY_COLOR: 'red', // saturday = red MODAL_BG: '#111', MODAL_TEXT_COLOR: '#fff', MODAL_PADDING: '5px', MODAL_BORDER_RADIUS: '3px', MODAL_FONT_SIZE: '12px', FETCH_URL_REPORT: 'https://www.managerzone.com/ajax.php?p=trainingReport&sub=daily&sport=soccer&day=', FETCH_URL_CLUBHOUSE: 'https://www.managerzone.com/?p=clubhouse', READY_ICON_HTML: '| ', NOT_READY_ICON_HTML: '| ', NO_REPORT_ICON_HTML: '| ' }; const DAY_MAP = { 0: 1, 1: 2, 2: 3, 3: 4, 4: 5, 5: 6, 6: null }; class TrainingReportChecker { constructor() { this.linkId = 'shortcut_link_trainingreport'; this.modalId = 'training_report_modal'; this.balls = null; this.currentIconColor = CONFIG.NOT_READY_COLOR; this.hovering = false; } getBrazilianDate() { const date = new Date(); const utc = date.getTime() + (date.getTimezoneOffset() * 60000); const brazilTime = new Date(utc + (3600000 * CONFIG.TIMEZONE_OFFSET)); return new Intl.DateTimeFormat('en-US', { year: 'numeric', month: 'long', day: 'numeric' }).format(brazilTime); } getLastReportDate() { const date = new Date(); const utc = date.getTime() + (date.getTimezoneOffset() * 60000); const brazilTime = new Date(utc + (3600000 * CONFIG.TIMEZONE_OFFSET)); /* If it's Sunday, go back two days (to Friday). Otherwise, go back one day. */ const daysToSubtract = brazilTime.getDay() === 0 ? 2 : 1; brazilTime.setDate(brazilTime.getDate() - daysToSubtract); return new Intl.DateTimeFormat('en-US', { year: 'numeric', month: 'long', day: 'numeric' }).format(brazilTime); } getBrazilianTime() { const date = new Date(); const utc = date.getTime() + (date.getTimezoneOffset() * 60000); return new Date(utc + (3600000 * CONFIG.TIMEZONE_OFFSET)); } isSaturday() { return this.getBrazilianTime().getDay() === 6; } isSunday() { return this.getBrazilianTime().getDay() === 0; } isWithinCheckWindow() { const now = this.getBrazilianTime(); const hour = now.getHours(); const minute = now.getMinutes(); const startTime = CONFIG.START_HOUR * 60 + CONFIG.START_MINUTE; const checkEndTime = CONFIG.END_HOUR * 60 + CONFIG.END_MINUTE; const currentTime = hour * 60 + minute; return currentTime >= startTime && currentTime <= checkEndTime; } addTrainingReportLink() { const targetDiv = document.getElementById('pt-wrapper'); if (!targetDiv) return; const link = document.createElement('a'); link.id = this.linkId; link.href = '/?p=training_report'; // Initialize with default state: if (this.isSaturday()) { link.innerHTML = CONFIG.NO_REPORT_ICON_HTML; link.style.color = CONFIG.SATURDAY_COLOR; this.currentIconColor = CONFIG.SATURDAY_COLOR; } else { link.innerHTML = CONFIG.NOT_READY_ICON_HTML; link.style.color = CONFIG.NOT_READY_COLOR; this.currentIconColor = CONFIG.NOT_READY_COLOR; } targetDiv.appendChild(link); this.createModal(); // Event listeners for hover behavior link.addEventListener('mouseenter', () => this.showModal()); link.addEventListener('mouseleave', (e) => this.handleMouseLeave(e)); const modal = document.getElementById(this.modalId); modal.addEventListener('mouseenter', () => this.hovering = true); modal.addEventListener('mouseleave', (e) => this.handleMouseLeave(e)); } createModal() { const modal = document.createElement('div'); modal.id = this.modalId; document.body.appendChild(modal); } updateModalContent(isReady) { const modal = document.getElementById(this.modalId); if (!modal) return; const todayStr = this.getBrazilianDate(); const lastReportStr = this.getLastReportDate(); let content; if (this.isSaturday()) { content = "No training report on Saturdays!"; modal.style.color = CONFIG.SATURDAY_COLOR; } else if (this.isSunday()) { if (isReady) { content = "Training report out for " + todayStr + "!"; if (this.balls !== null) { content += "
Balls earned today: " + this.balls; } modal.style.color = CONFIG.READY_COLOR; } else { content = "Training report is not out yet for " + todayStr + "."; modal.style.color = CONFIG.NOT_READY_COLOR; } } else if (isReady) { content = "Training report is out for " + todayStr + "!"; if (this.balls !== null) { content += "
Balls earned today: " + this.balls; } modal.style.color = CONFIG.READY_COLOR; } else { content = "Training report not out yet for " + todayStr + "."; modal.style.color = CONFIG.NOT_READY_COLOR; } modal.innerHTML = content; } positionModal() { const icon = document.getElementById(this.linkId); const modal = document.getElementById(this.modalId); if (!icon || !modal) return; const rect = icon.getBoundingClientRect(); modal.style.top = (window.scrollY + rect.bottom + 5) + 'px'; modal.style.left = (window.scrollX + rect.left + 50) + 'px'; } showModal() { const modal = document.getElementById(this.modalId); if (!modal) return; this.hovering = true; // if entering icon area, consider it hovering this.positionModal(); modal.style.display = 'block'; modal.classList.remove('fade-out'); modal.classList.add('fade-in'); } handleMouseLeave(e) { // If mouse leaves icon or modal, check if it's leaving to the other element const modal = document.getElementById(this.modalId); const icon = document.getElementById(this.linkId); if (!icon || !modal) return; const relatedTarget = e.relatedTarget; if (relatedTarget !== modal && relatedTarget !== icon) { // Actually leaving both this.hovering = false; this.hideModal(); } } hideModal() { if (!this.hovering) { const modal = document.getElementById(this.modalId); if (!modal) return; modal.classList.remove('fade-in'); modal.classList.add('fade-out'); setTimeout(() => { if (!this.hovering) { modal.style.display = 'none'; } }, 300); } } isReportReady(table) { return Array.from(table.querySelectorAll('tr')).some(row => row.querySelector('img[src*="training_camp.png"]')); } markReportAsChecked() { const today = this.getBrazilianDate(); localStorage.setItem('reportCheckedDate', today); const link = document.getElementById(this.linkId); link.style.color = CONFIG.READY_COLOR; link.innerHTML = CONFIG.READY_ICON_HTML; this.currentIconColor = CONFIG.READY_COLOR; this.fetchEarnedBalls(); } async fetchEarnedBalls() { try { const response = await fetch(CONFIG.FETCH_URL_CLUBHOUSE); const text = await response.text(); const parser = new DOMParser(); const doc = parser.parseFromString(text, "text/html"); const widget = doc.querySelector('#clubhouse-widget-training'); if (widget) { const strongElems = widget.querySelectorAll('strong'); if (strongElems.length > 0) { const ballsText = strongElems[0].textContent.trim(); this.balls = ballsText; // Update modal immediately to show the balls earned this.updateModalContent(true); } } } catch (_e) { // Ignore error } } hasReportBeenCheckedToday() { const today = this.getBrazilianDate(); return localStorage.getItem('reportCheckedDate') === today; } async checkTrainingReport() { const today = new Date(); const dayIndex = DAY_MAP[today.getDay()]; if (!dayIndex) { // No day index means Saturday this.updateModalContent(false); return; } if (!this.isWithinCheckWindow()) return; if (this.hasReportBeenCheckedToday()) { const link = document.getElementById(this.linkId); link.style.color = CONFIG.READY_COLOR; link.innerHTML = CONFIG.READY_ICON_HTML; this.currentIconColor = CONFIG.READY_COLOR; this.updateModalContent(true); return; } try { const response = await fetch(CONFIG.FETCH_URL_REPORT + dayIndex + '&sort_order=desc&sort_key=modification&player_sort=all'); const text = await response.text(); const parser = new DOMParser(); const doc = parser.parseFromString(text, "text/html"); const table = doc.querySelector("body > table:nth-child(3)"); if (table && this.isReportReady(table)) { this.markReportAsChecked(); this.updateModalContent(true); } else if (this.isWithinCheckWindow()) { // Not ready yet const link = document.getElementById(this.linkId); if (!this.isSaturday()) { link.style.color = CONFIG.NOT_READY_COLOR; link.innerHTML = CONFIG.NOT_READY_ICON_HTML; this.currentIconColor = CONFIG.NOT_READY_COLOR; } this.updateModalContent(false); setTimeout(() => this.checkTrainingReport(), CONFIG.CHECK_INTERVAL); } } catch (_e) { // On error, do nothing special } } init() { const sportLink = document.querySelector("#shortcut_link_thezone"); if (!sportLink) return; // Just a fallback const sport = new URL(sportLink.href).searchParams.get("sport"); if (sport !== "soccer") return; this.addTrainingReportLink(); this.updateModalContent(false); this.checkTrainingReport(); } } const checker = new TrainingReportChecker(); checker.init(); })();