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