Greasy Fork

MZ - Training Report

Checks if the training report is already out for the current day

目前为 2024-12-24 提交的版本。查看 最新版本

// ==UserScript==
// @name         MZ - Training Report
// @namespace    douglaskampl
// @version      3.7
// @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
// @grant        GM_getValue
// @grant        GM_setValue
// @license      MIT
// ==/UserScript==

GM_addStyle(`
    #training_report_modal {
        position: absolute;
        background: #002633;
        color: #ffd600;
        padding: 8px 12px;
        border: 1px solid #374151;
        border-radius: 3px;
        font-size: 12px;
        display: none;
        z-index: 9999;
        text-align: center;
        animation-duration: 0.3s;
        transition: opacity 0.3s ease-in-out;
        box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
        min-width: 200px;
    }

    #training_report_modal::before {
        content: '';
        position: absolute;
        top: -6px;
        left: 50%;
        transform: translateX(-50%);
        border-width: 0 6px 6px;
        border-style: solid;
        border-color: transparent transparent #002633;
    }

    #training_report_modal.ready {
        color: #86efac;
    }

    #training_report_modal.not-ready {
        color: #f9fafb;
    }

    #training_report_modal.no-report {
        color: #ef4444;
    }

    @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;
    }

    .report-icon {
        display: inline-block;
        transition: transform 0.3s ease-in-out;
        background: #002633;
        padding: 4px 8px;
        border-radius: 3px;
        margin-left: 5px;
    }

    .report-icon:hover {
        transform: rotate(2deg) scale(1.1);
        background: #003344;
    }

    .report-icon.ready {
        background: #2c9c55;
        color: white;
    }

    .report-icon.not-ready {
        background: #374151;
        color: #f9fafb;
    }

    .report-icon.no-report {
        background: #991b1b;
        color: #ef4444;
    }

    .report-icon i {
        margin-right: 4px;
    }
`);

(function() {
    'use strict';

    const CONFIG = {
        CHECK_INTERVAL: 300000,
        TIMEZONE_OFFSET: -3,
        FETCH_URL_REPORT: 'https://www.managerzone.com/ajax.php?p=trainingReport&sub=daily&sport=soccer&day=',
        READY_ICON_HTML: '<span class="report-icon ready"><i class="fa fa-unlock"></i></span>',
        NOT_READY_ICON_HTML: '<span class="report-icon not-ready"><i class="fa fa-lock"></i></span>',
        NO_REPORT_ICON_HTML: '<span class="report-icon no-report"><i class="fa fa-times"></i></span>',
        STORAGE_KEY: 'reportCheckedDate',
        TIMES: {
            DEFAULT: {
                START_HOUR: 19,
                START_MINUTE: 1,
                END_HOUR: 23,
                END_MINUTE: 59
            },
            SUNDAY: {
                START_HOUR: 20,
                START_MINUTE: 0,
                END_HOUR: 23,
                END_MINUTE: 59
            }
        }
    };

    const DAY_MAP = {
        0: 1, // Sunday
        1: 2, // Monday
        2: 3, // Tuesday
        3: 4, // Wednesday
        4: 5, // Thursday
        5: 6, // Friday
        6: null // Saturday
    };

    class TrainingReportChecker {
        constructor() {
            this.linkId = 'shortcut_link_trainingreport';
            this.modalId = 'training_report_modal';
            this.balls = null;
            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);
        }

        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 times = this.isSunday() ? CONFIG.TIMES.SUNDAY : CONFIG.TIMES.DEFAULT;
            const startTime = times.START_HOUR * 60 + times.START_MINUTE;
            const checkEndTime = times.END_HOUR * 60 + times.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';

            if (this.isSaturday()) {
                link.innerHTML = CONFIG.NO_REPORT_ICON_HTML;
            } else {
                link.innerHTML = CONFIG.NOT_READY_ICON_HTML;
            }

            targetDiv.appendChild(link);

            this.createModal();

            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();
            let content;

            if (this.isSaturday()) {
                content = "No training report on Saturdays!";
                modal.className = 'no-report';
            } else if (isReady) {
                content = `Training report is out for ${todayStr}!`;
                if (this.balls !== null && this.balls > 0) {
                    content += `<br>Training balls earned today: <br>`;
                    if (this.ballPlayers && this.ballPlayers.length > 0) {
                        content += '<ul style="list-style-type: none; padding: 0; margin: 5px 0;">';
                        this.ballPlayers.forEach(player => {
                            content += `<li style="margin: 2px 0;">${player.name} (${player.skill})</li>`;
                        });
                        content += '</ul>';
                    }
                }
                modal.className = 'ready';
            } else {
                content = `Training report is not out yet for ${todayStr}.`;
                modal.className = 'not-ready';
            }
            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;
            this.positionModal();
            modal.style.display = 'block';
            modal.classList.remove('fade-out');
            modal.classList.add('fade-in');
        }

        handleMouseLeave(e) {
            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) {
                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"]') ||
                row.querySelector('img[src*="gained_skill.png"]')
            );
        }

        markReportAsChecked() {
            const today = this.getBrazilianDate();
            GM_setValue(CONFIG.STORAGE_KEY, today);
            const link = document.getElementById(this.linkId);
            if (link) {
                link.innerHTML = CONFIG.READY_ICON_HTML;
            }
            this.fetchEarnedBalls();
        }

        async fetchEarnedBalls() {
            try {
                const dayIndex = DAY_MAP[new Date().getDay()];
                if (!dayIndex) return;

                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 playerDetails = [];
                const rows = doc.querySelectorAll('tr');
                rows.forEach(row => {
                    const ballImg = row.querySelector('img[src*="gained_skill.png"]');
                    if (ballImg) {
                        const nameLink = row.querySelector('.player_link');
                        const skillCell = row.querySelector('.skillColumn .clippable');

                        if (nameLink && skillCell) {
                            const fullName = nameLink.textContent.trim();
                            const nameParts = fullName.split(' ');
                            const shortName = nameParts.length > 1 ?
                                `${nameParts[0][0]}. ${nameParts[nameParts.length-1]}` :
                                fullName;
                            const skill = skillCell.textContent.trim();

                            playerDetails.push({
                                name: shortName,
                                skill: skill
                            });
                        }
                    }
                });

                if (playerDetails.length > 0) {
                    this.balls = playerDetails.length;
                    this.ballPlayers = playerDetails;
                    this.updateModalContent(true);
                }
            } catch (_e) {}
        }

        hasReportBeenCheckedToday() {
            const today = this.getBrazilianDate();
            return GM_getValue(CONFIG.STORAGE_KEY) === today;
        }

        async checkTrainingReport() {
            const today = new Date();
            const dayIndex = DAY_MAP[today.getDay()];

            if (!dayIndex) {
                this.updateModalContent(false);
                return;
            }

            if (!this.isWithinCheckWindow()) return;

            if (this.hasReportBeenCheckedToday()) {
                const link = document.getElementById(this.linkId);
                if (link) {
                    link.innerHTML = CONFIG.READY_ICON_HTML;
                }
                this.fetchEarnedBalls();
                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()) {
                    const link = document.getElementById(this.linkId);
                    if (!this.isSaturday() && link) {
                        link.innerHTML = CONFIG.NOT_READY_ICON_HTML;
                    }
                    this.updateModalContent(false);
                    setTimeout(() => this.checkTrainingReport(), CONFIG.CHECK_INTERVAL);
                }
            } catch (_e) {
                setTimeout(() => this.checkTrainingReport(), CONFIG.CHECK_INTERVAL);
            }
        }

        init() {
            const sportLink = document.querySelector("#shortcut_link_thezone");
            if (!sportLink) return;
            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();
})();