MZ - Ongoing Match Results

Track ongoing matches and update standings for ManagerZone

当前为 2024-12-10 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         MZ - Ongoing Match Results
// @namespace    http://tampermonkey.net/
// @version      1.6
// @description  Track ongoing matches and update standings for ManagerZone
// @author       You
// @match        https://www.managerzone.com/?p=league&type=*
// @match        https://www.managerzone.com/?p=friendlyseries&sub=standings&fsid=*
// @grant        GM_xmlhttpRequest
// @connect      www.managerzone.com
// @require      https://cdnjs.cloudflare.com/ajax/libs/nprogress/0.2.0/nprogress.min.js
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    const style = document.createElement('style');
    style.textContent = `
        #nprogress {
            pointer-events: none;
        }
        #nprogress .bar {
            background: linear-gradient(to right, #ff6600, #ff9966);
            position: fixed;
            z-index: 1031;
            top: 0;
            left: 0;
            width: 100%;
            height: 2px;
            box-shadow: 0 1px 3px rgba(255, 102, 0, 0.2);
        }
        .status-message {
            position: fixed;
            top: 10px;
            right: 10px;
            background: rgba(0, 0, 0, 0.8);
            color: #ff6600;
            padding: 8px 15px;
            border-radius: 4px;
            font-size: 14px;
            z-index: 1000;
            animation: fadeInOut 0.3s ease;
        }
        @keyframes fadeInOut {
            0% { opacity: 0; transform: translateY(-10px); }
            100% { opacity: 1; transform: translateY(0); }
        }
        .mz-modal {
            position: fixed;
            bottom: 20px;
            right: 20px;
            background: rgba(0, 0, 0, 0.9);
            padding: 20px;
            border-radius: 8px;
            box-shadow: 0 4px 15px rgba(255, 102, 0, 0.2);
            z-index: 1100;
            max-width: 300px;
            max-height: 400px;
            overflow-y: auto;
            transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
            opacity: 0;
            transform: translateY(20px);
            color: #ff6600;
            border: 1px solid #ff6600;
        }
        .mz-modal.show {
            opacity: 1;
            transform: translateY(0);
        }
        .mz-modal-header {
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin-bottom: 15px;
            padding-bottom: 10px;
            border-bottom: 1px solid #ff6600;
        }
        .mz-modal-close {
            cursor: pointer;
            padding: 5px 10px;
            background: #ff6600;
            color: black;
            border: none;
            border-radius: 4px;
            transition: all 0.2s ease;
        }
        .mz-modal-close:hover {
            background: #ff9966;
        }
        .mz-modal-content {
            margin-bottom: 15px;
            font-size: 14px;
        }
        .mz-match-result {
            display: flex;
            justify-content: space-between;
            align-items: center;
            padding: 10px;
            border-bottom: 1px solid rgba(255, 102, 0, 0.3);
        }
        .mz-match-result:last-child {
            border-bottom: none;
        }
    `;
    document.head.appendChild(style);

    const UI = {
        BUTTON_STYLES: {
            backgroundColor: '#1a1a1a',
            color: '#ff6600',
            border: '1px solid #ff6600',
            marginLeft: '10px',
            cursor: 'pointer',
            padding: '5px 10px',
            borderRadius: '4px',
            transition: 'all 0.2s ease'
        },
        BUTTON_STATES: {
            READY: 'Get match results',
            FETCHING: 'Processing...',
            DONE: 'Results updated'
        },
        LOADING_MESSAGES: {
            MATCHES: 'Fetching matches...',
            RESULTS: 'Processing results...',
            UPDATING: 'Updating standings...'
        }
    };

    const SELECTORS = {
        TRACK_BUTTONS: [
            '[id^="trackButton_u18_world_series_"]',
            '[id^="trackButton_u18_series_"]',
            '[id^="trackButton_friendlyseries_"]',
        ],
        MATCHES_TABLE: 'table.hitlist',
        STANDINGS_TABLE: 'table.nice_table'
    };

    class MatchTracker {
        constructor() {
            this.matchResults = new Map();
            this.isFriendlySeries = window.location.href.includes('friendlyseries');
            this.hasRun = false;
            this.init();
        }

        showStatusMessage(message) {
            const existingMessage = document.querySelector('.status-message');
            if (existingMessage) {
                existingMessage.remove();
            }

            const messageElement = document.createElement('div');
            messageElement.className = 'status-message';
            messageElement.textContent = message;
            document.body.appendChild(messageElement);

            setTimeout(() => {
                messageElement.style.opacity = '0';
                setTimeout(() => messageElement.remove(), 300);
            }, 2000);
        }

        async init() {
            const matches = this.isFriendlySeries ?
                await this.getFriendlySeriesMatches() :
                this.getLeagueMatches();

            if (!matches || !matches.length) {
                console.log('No ongoing matches found');
                return;
            }

            this.setupUI(matches);
        }

        setupUI(matches) {
            const trackButton = this.findTrackButton();
            if (!trackButton) {
                console.error('Track button not found');
                return;
            }

            const fetchButton = this.createFetchButton();
            trackButton.parentNode.insertBefore(fetchButton, trackButton.nextSibling);

            fetchButton.addEventListener('mouseenter', () => {
                if (!this.hasRun) {
                    fetchButton.style.backgroundColor = '#333';
                }
            });

            fetchButton.addEventListener('mouseleave', () => {
                if (!this.hasRun) {
                    fetchButton.style.backgroundColor = '#1a1a1a';
                }
            });

            fetchButton.addEventListener('click', () => {
                if (!this.hasRun) {
                    this.handleFetchClick(fetchButton, matches);
                }
            });
        }

        findTrackButton() {
            return SELECTORS.TRACK_BUTTONS.reduce((found, selector) =>
                found || document.querySelector(selector), null);
        }

        createFetchButton() {
            const button = document.createElement('button');
            Object.assign(button.style, UI.BUTTON_STYLES);
            button.textContent = UI.BUTTON_STATES.READY;
            return button;
        }

        showLoadingOverlay(message) {
            let overlay = document.querySelector('.status-overlay');
            if (!overlay) {
                overlay = document.createElement('div');
                overlay.className = 'status-overlay';
                document.body.appendChild(overlay);
            }
            overlay.textContent = message;
        }

        hideLoadingOverlay() {
            const overlay = document.querySelector('.status-overlay');
            if (overlay) {
                overlay.remove();
            }
        }

        showResultsModal(results) {
            const modal = document.createElement('div');
            modal.className = 'mz-modal';

            const header = document.createElement('div');
            header.className = 'mz-modal-header';
            header.innerHTML = `
                <h3>Match Results</h3>
                <button class="mz-modal-close">Close</button>
            `;

            const content = document.createElement('div');
            content.className = 'mz-modal-content';

            results.forEach(result => {
                const matchDiv = document.createElement('div');
                matchDiv.className = 'mz-match-result';
                matchDiv.textContent = `${result.homeTeam} ${result.score} ${result.awayTeam}`;
                content.appendChild(matchDiv);
            });

            modal.appendChild(header);
            modal.appendChild(content);
            document.body.appendChild(modal);

            setTimeout(() => modal.classList.add('show'), 10);

            modal.querySelector('.mz-modal-close').addEventListener('click', () => {
                modal.classList.remove('show');
                setTimeout(() => modal.remove(), 300);
            });
        }

        async handleFetchClick(button, matches) {
            if (this.hasRun) return;

            this.hasRun = true;
            NProgress.configure({ showSpinner: false });
            NProgress.start();
            this.showStatusMessage(UI.LOADING_MESSAGES.MATCHES);

            if (!matches.length) {
                NProgress.done();
                return;
            }

            button.disabled = true;
            button.textContent = UI.BUTTON_STATES.FETCHING;
            button.style.opacity = '0.7';
            button.style.cursor = 'not-allowed';

            this.showStatusMessage(UI.LOADING_MESSAGES.RESULTS);
            const results = await this.processMatches(matches);

            if (this.isFriendlySeries) {
                this.showResultsModal(results);
            }

            this.showStatusMessage(UI.LOADING_MESSAGES.UPDATING);
            this.updateAllTeamStats();

            button.textContent = UI.BUTTON_STATES.DONE;
            button.style.backgroundColor = '#333';
            button.style.borderColor = '#666';
            button.style.color = '#999';

            NProgress.done();
            this.showStatusMessage('All updates complete');
        }

        getLeagueMatches() {
            const matchesTable = document.querySelector(SELECTORS.MATCHES_TABLE);
            if (!matchesTable) return [];

            return Array.from(matchesTable.querySelectorAll('tr'))
                .filter(row => {
                    const link = row.querySelector('a[href*="mid="]');
                    if (!link) return false;
                    const score = link.textContent.trim();
                    return !score.match(/^\d+\s*-\s*\d+$/) && !score.match(/^X\s*-\s*X$/);
                })
                .map(row => {
                    const link = row.querySelector('a[href*="mid="]');
                    const homeTeam = row.querySelector('td:first-child').textContent.trim();
                    const awayTeam = row.querySelector('td:last-child').textContent.trim();
                    const params = new URLSearchParams(link.href);
                    return {
                        mid: params.get('mid'),
                        homeTeam,
                        awayTeam
                    };
                });
        }

        async getFriendlySeriesMatches() {
            const fsidMatch = window.location.href.match(/fsid=(\d+)/);
            if (!fsidMatch) return [];

            return new Promise((resolve, reject) => {
                GM_xmlhttpRequest({
                    method: "GET",
                    url: `https://www.managerzone.com/ajax.php?p=friendlySeries&sub=matches&fsid=${fsidMatch[1]}&sport=soccer`,
                    onload: response => {
                        const parser = new DOMParser();
                        const doc = parser.parseFromString(response.responseText, "text/html");
                        const matchRows = Array.from(doc.querySelectorAll('table.hitlist tr'));

                        const inProgressMatches = matchRows
                            .filter(row => {
                                const link = row.querySelector('a[href*="mid="]');
                                if (!link) return false;
                                const score = link.textContent.trim();
                                return !score.match(/^\d+\s*-\s*\d+$/) && !score.match(/^X\s*-\s*X$/);
                            })
                            .map(row => {
                                const link = row.querySelector('a[href*="mid="]');
                                const homeTeam = row.querySelector('td:first-child').textContent.trim();
                                const awayTeam = row.querySelector('td:last-child').textContent.trim();
                                const params = new URLSearchParams(link.href);
                                return {
                                    mid: params.get('mid'),
                                    homeTeam,
                                    awayTeam
                                };
                            });

                        resolve(inProgressMatches);
                    },
                    onerror: reject
                });
            });
        }

        async processMatches(matches) {
            const results = [];
            const total = matches.length;

            for (let i = 0; i < matches.length; i++) {
                const match = matches[i];
                const result = await new Promise((resolve) => {
                    GM_xmlhttpRequest({
                        method: "GET",
                        url: `https://www.managerzone.com/xml/match_info.php?sport_id=1&match_id=${match.mid}`,
                        onload: response => {
                            try {
                                const matchData = this.parseMatchResponse(response);
                                if (matchData) {
                                    this.matchResults.set(match.mid, matchData);
                                    this.updateMatchDisplay(match.mid, matchData);
                                    resolve({
                                        ...match,
                                        score: `${matchData.homeGoals}-${matchData.awayGoals}`
                                    });
                                }
                            } catch (error) {
                                console.error(`Error processing match ${match.mid}:`, error);
                                resolve(null);
                            }
                        },
                        onerror: () => resolve(null),
                        ontimeout: () => resolve(null)
                    });
                });

                if (result) {
                    results.push(result);
                }
                NProgress.set((i + 1) / total);
            }

            return results;
        }

        parseMatchResponse(response) {
            const parser = new DOMParser();
            const xmlDoc = parser.parseFromString(response.responseText, "application/xml");
            const matchNode = xmlDoc.querySelector('Match');
            if (!matchNode) return null;

            const homeTeam = matchNode.querySelector('Team[field="home"]');
            const awayTeam = matchNode.querySelector('Team[field="away"]');
            if (!homeTeam || !awayTeam) return null;

            return {
                homeTid: homeTeam.getAttribute('id'),
                awayTid: awayTeam.getAttribute('id'),
                homeGoals: parseInt(homeTeam.getAttribute('goals'), 10) || 0,
                awayGoals: parseInt(awayTeam.getAttribute('goals'), 10) || 0
            };
        }

        updateMatchDisplay(mid, matchData) {
            const link = Array.from(document.links)
                .find(link => link.href.includes(`mid=${mid}`));
            if (link) {
                link.textContent = `${matchData.homeGoals}-${matchData.awayGoals}`;
            }
        }

        calculateMatchResult(matchData) {
            if (matchData.homeGoals > matchData.awayGoals) {
                return {
                    home: { points: 3, goalsFor: matchData.homeGoals, goalsAgainst: matchData.awayGoals },
                    away: { points: 0, goalsFor: matchData.awayGoals, goalsAgainst: matchData.homeGoals }
                };
            } else if (matchData.homeGoals < matchData.awayGoals) {
                return {
                    home: { points: 0, goalsFor: matchData.homeGoals, goalsAgainst: matchData.awayGoals },
                    away: { points: 3, goalsFor: matchData.awayGoals, goalsAgainst: matchData.homeGoals }
                };
            } else {
                return {
                    home: { points: 1, goalsFor: matchData.homeGoals, goalsAgainst: matchData.awayGoals },
                    away: { points: 1, goalsFor: matchData.awayGoals, goalsAgainst: matchData.homeGoals }
                };
            }
        }

        findTeamRows(tid) {
            const teamLinks = Array.from(document.querySelectorAll(`a[href*="tid=${tid}"]`));
            const rowsSet = new Set();

            teamLinks.forEach(link => {
                const row = link.closest('tr');
                if (row) rowsSet.add(row);
            });

            const highlightedRows = Array.from(document.querySelectorAll('.highlight_row'))
                .filter(row => row.querySelector(`a[href*="tid=${tid}"]`));
            highlightedRows.forEach(row => rowsSet.add(row));

            return Array.from(rowsSet);
        }

        updateAllTeamStats() {
            this.matchResults.forEach((matchData) => {
                const result = this.calculateMatchResult(matchData);
                this.updateTeamRow(matchData.homeTid, result.home);
                this.updateTeamRow(matchData.awayTid, result.away);
            });
        }

        updateTeamRow(tid, result) {
            const teamRows = this.findTeamRows(tid);
            if (!teamRows.length) return;

            teamRows.forEach(row => {
                const cells = row.querySelectorAll('td');
                if (cells.length < 10) return;

                const parseCell = cell => parseInt(cell.textContent, 10) || 0;

                cells[2].textContent = parseCell(cells[2]) + 1;

                if (result.points === 3) {
                    cells[3].textContent = parseCell(cells[3]) + 1;
                } else if (result.points === 1) {
                    cells[4].textContent = parseCell(cells[4]) + 1;
                } else {
                    cells[5].textContent = parseCell(cells[5]) + 1;
                }

                cells[6].textContent = parseCell(cells[6]) + result.goalsFor;
                cells[7].textContent = parseCell(cells[7]) + result.goalsAgainst;

                const goalDiff = parseCell(cells[6]) - parseCell(cells[7]);
                const goalDiffElem = cells[8].querySelector('nobr');
                if (goalDiffElem) {
                    goalDiffElem.textContent = goalDiff;
                }

                cells[9].textContent = parseCell(cells[9]) + result.points;
            });
        }

        sortTableByPoints() {
            const table = document.querySelector(SELECTORS.STANDINGS_TABLE);
            if (!table) return;

            const tbody = table.querySelector('tbody');
            if (!tbody) return;

            const dataRows = Array.from(tbody.querySelectorAll('tr')).filter(row => {
                const cells = row.querySelectorAll('td');
                return cells.length >= 10 && cells[2] && !isNaN(parseInt(cells[2].textContent));
            });

            const originalState = dataRows.map(row => ({
                row,
                classes: row.getAttribute('class'),
                style: row.getAttribute('style')
            }));

            dataRows.sort((a, b) => {
                const getCellValue = (row, index) => {
                    const cell = row.querySelectorAll('td')[index];
                    return parseInt(cell.textContent.trim(), 10) || 0;
                };

                // Points comparison (column 9)
                const pointsA = getCellValue(a, 9);
                const pointsB = getCellValue(b, 9);
                if (pointsB !== pointsA) return pointsB - pointsA;

                // Goal difference comparison (GP - GC)
                const goalDiffA = getCellValue(a, 6) - getCellValue(a, 7);
                const goalDiffB = getCellValue(b, 6) - getCellValue(b, 7);
                if (goalDiffB !== goalDiffA) return goalDiffB - goalDiffA;

                // Goals scored comparison (GP)
                return getCellValue(b, 6) - getCellValue(a, 6);
            });

            const nonDataRows = Array.from(tbody.children).filter(row => !dataRows.includes(row));
            tbody.innerHTML = '';

            dataRows.forEach((row, index) => {
                const positionCell = row.querySelector('td:first-child span');
                if (positionCell) {
                    positionCell.textContent = (index + 1).toString();
                }

                row.className = '';
                row.removeAttribute('style');

                row.classList.add(index % 2 === 0 ? '' : 'highlight_row');

                if (index === 0) {
                    row.style.borderBottom = '2px solid green';
                } else if (index === 1) {
                    row.style.borderBottom = '2px dashed #556B2F';
                } else if (index === 7) {
                    row.style.borderBottom = '2px solid red';
                }

                tbody.appendChild(row);
            });

            nonDataRows.forEach(row => tbody.appendChild(row));

            table.style.display = 'none';
            table.offsetHeight;
            table.style.display = '';
        }

        finalizeUpdate(button) {
            this.sortTableByPoints();
            button.disabled = false;
            button.textContent = UI.BUTTON_STATES.READY;
        }
    }

    setTimeout(() => new MatchTracker(), 3333);
})();