Greasy Fork

Greasy Fork is available in English.

MZ - WL Weird Matches

Checks world leagues rounds for unusual pairs of results

当前为 2025-01-18 提交的版本,查看 最新版本

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         MZ - WL Weird Matches
// @namespace    douglaskampl
// @version      2.4
// @description  Checks world leagues rounds for unusual pairs of results
// @author       Douglas
// @match        https://www.managerzone.com/?p=match&sub=livescores_overview
// @icon         https://www.google.com/s2/favicons?sz=64&domain=managerzone.com
// @grant        GM_addStyle
// @grant        GM_getResourceText
// @resource     weirdMatchesStyles https://u18mz.vercel.app/mz/userscript/other/weirdMatches.css
// @run-at       document-idle
// @license      MIT
// ==/UserScript==

(function () {
    'use strict';

    GM_addStyle(GM_getResourceText('weirdMatchesStyles'));

    const CONSTANTS = {
        LEAGUE_TYPES: ['senior', 'u18_world', 'u21_world', 'u23_world'],
        LEAGUE_DISPLAY_NAMES: {
            'senior': 'Senior',
            'u18_world': 'U18',
            'u21_world': 'U21',
            'u23_world': 'U23'
        },
        LEAGUE_LIMITS: {
            div1: 1,
            div2: 13,
            div3: 40,
            div4: 121,
            div5: 291
        },
        ROUND_PAIRS: [
            { first: '11', second: '12', display: '11/12' },
            { first: '10', second: '13', display: '10/13' },
            { first: '9', second: '14', display: '9/14' },
            { first: '8', second: '15', display: '8/15' },
            { first: '7', second: '16', display: '7/16' },
            { first: '6', second: '17', display: '6/17' },
            { first: '5', second: '18', display: '5/18' },
            { first: '4', second: '19', display: '4/19' },
            { first: '3', second: '20', display: '3/20' },
            { first: '2', second: '21', display: '2/21' },
            { first: '1', second: '22', display: '1/22' }
        ],
        ERROR_MESSAGES: {
            FETCH_ERROR: 'Error fetching data for league',
            NO_DISCREPANCIES: 'No weird matches found!',
            INVALID_INPUT: 'Please enter a number between 1 and 10'
        },
        DOM: {
            HEADER_ID: '#header-next-game',
            BUTTON_CLASS: 'wl-check-button',
            BUTTON_TEXT: 'WL',
            BUTTON_TITLE: 'Check World League Rounds'
        },
        REGEX: {
            SCORE: /<a[^>]*>([^<]+)<\/a>/,
            LINK: /<a href="([^"]+)"/,
            UNPLAYED_SCORE: /X\s*-\s*X/
        }
    };

    function getDivisionName(sid) {
        if (sid === CONSTANTS.LEAGUE_LIMITS.div1) return "Top Series";
        if (sid >= 2 && sid <= 4) return `1.${sid - 1}`;
        if (sid >= 5 && sid <= CONSTANTS.LEAGUE_LIMITS.div2) return `2.${sid - 4}`;
        if (sid >= 14 && sid <= CONSTANTS.LEAGUE_LIMITS.div3) return `3.${sid - 13}`;
        return `4.${sid - 40}`;
    }

    async function fetchLeagueData(leagueType, worldLeagueId) {
        let finalLeagueType = leagueType === 'senior' ? 'world' : leagueType;
        const url = `/ajax.php?p=league&type=${finalLeagueType}&sid=${worldLeagueId}&tid=1&sport=soccer&sub=schedule`;
        try {
            const response = await fetch(url);
            if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
            return await response.text();
        } catch (error) {
            console.error(`${CONSTANTS.ERROR_MESSAGES.FETCH_ERROR} ${worldLeagueId}:`, error.message);
            return null;
        }
    }

    function extractMatchesFromHTML(html) {
        const parser = new DOMParser();
        const doc = parser.parseFromString(html, 'text/html');
        const roundHeaders = doc.querySelectorAll('h2.subheader');
        const matches = [];

        roundHeaders.forEach((header, index) => {
            const roundNumber = index + 1; // Header index + 1 gives us the round number
            const nextElement = header.nextElementSibling;

            if (nextElement && nextElement.classList.contains('mainContent')) {
                const matchRows = nextElement.querySelectorAll('tr');

                matchRows.forEach(row => {
                    const teams = row.querySelectorAll('td');
                    const scoreLink = row.querySelector('a');

                    if (teams.length >= 3 && scoreLink) {
                        const match = {
                            teams: [
                                teams[0].textContent.trim(),
                                teams[2].textContent.trim()
                            ],
                            score: scoreLink.textContent.trim(),
                            link: scoreLink.getAttribute('href'),
                            round: roundNumber.toString()
                        };
                        matches.push(match);
                    }
                });
            }
        });

        return matches;
    }

    function checkDiscrepancy(homeScore, awayScore) {
        const homeDiff = homeScore[0] - homeScore[1];
        const awayDiff = awayScore[1] - awayScore[0];

        const getWinner = diff => diff > 0 ? 0 : diff < 0 ? 1 : 2;
        const homeWinner = getWinner(homeDiff);
        const awayWinner = getWinner(awayDiff);

        return {
            totalDiff: Math.abs(homeDiff) + Math.abs(awayDiff),
            isDiscrepancy: homeWinner !== 2 && awayWinner !== 2 && homeWinner !== awayWinner
        };
    }

    async function processLeague(leagueType, worldLeagueId, minTotalDiff, roundNumbers) {
        const html = await fetchLeagueData(leagueType, worldLeagueId);
        if (!html) return { discrepancies: [] };

        const matches = extractMatchesFromHTML(html);
        const homeResults = new Map();
        const awayResults = new Map();
        const discrepancies = [];

        matches.forEach(match => {
            if (CONSTANTS.REGEX.UNPLAYED_SCORE.test(match.score)) return;

            const matchKey = `${match.teams[0]} vs ${match.teams[1]}`;

            if (match.round === roundNumbers.first) {
                homeResults.set(matchKey, match);
            } else if (match.round === roundNumbers.second) {
                awayResults.set(matchKey, match);
            }
        });

        for (const [key, awayMatch] of awayResults) {
            const homeKey = `${awayMatch.teams[1]} vs ${awayMatch.teams[0]}`;
            const homeMatch = homeResults.get(homeKey);

            if (!homeMatch) continue;

            const homeScore = homeMatch.score.split(" - ").map(Number);
            const awayScore = awayMatch.score.split(" - ").map(Number);

            const { totalDiff, isDiscrepancy } = checkDiscrepancy(homeScore, awayScore);

            if (totalDiff >= minTotalDiff && isDiscrepancy) {
                discrepancies.push({
                    division: getDivisionName(worldLeagueId),
                    divisionId: worldLeagueId,
                    homeMatch: { ...homeMatch },
                    awayMatch: { ...awayMatch }
                });
            }
        }

        return { discrepancies };
    }

    function createModal(discrepancies, leagueType, minDiff, roundNumbers) {
        const modalHtml = `
            <div class="mz-modal-overlay mz-modern">
                <div class="mz-modal mz-modern">
                    <div class="mz-modal-header mz-modern">
                        <h2 class="mz-modal-title">
                            ${CONSTANTS.LEAGUE_DISPLAY_NAMES[leagueType]} WL Weird Matches (Rounds ${roundNumbers.first}/${roundNumbers.second}, Min. Diff: ${minDiff})
                        </h2>
                        <button class="mz-modal-close">&times;</button>
                    </div>
                    <div class="mz-modal-content mz-modern">
                        ${discrepancies.map(d => `
                            <div class="mz-discrepancy mz-modern">
                                <div class="mz-discrepancy-header mz-modern">
                                    Division: <a href="/?p=league&type=placeholder&sid=${d.divisionId}" class="mz-div-link">${d.division}</a>
                                </div>
                                <div>
                                    R${d.homeMatch.round}: <a href="${d.homeMatch.link}" class="mz-match-link" target="_blank">
                                        ${d.homeMatch.teams[0]} vs ${d.homeMatch.teams[1]} (${d.homeMatch.score})
                                    </a>
                                </div>
                                <div>
                                    R${d.awayMatch.round}: <a href="${d.awayMatch.link}" class="mz-match-link" target="_blank">
                                        ${d.awayMatch.teams[0]} vs ${d.awayMatch.teams[1]} (${d.awayMatch.score})
                                    </a>
                                </div>
                            </div>
                        `).join('')}
                    </div>
                </div>
            </div>
        `;

        const modalElement = document.createElement('div');
        modalElement.innerHTML = modalHtml;
        document.body.appendChild(modalElement);

        const links = modalElement.querySelectorAll('.mz-div-link');
        for (const a of links) {
            const href = a.getAttribute('href');
            const replaced = href.replace('placeholder', encodeURIComponent(leagueType));
            a.setAttribute('href', replaced);
        }

        const closeButton = modalElement.querySelector('.mz-modal-close');
        const overlay = modalElement.querySelector('.mz-modal-overlay');
        const closeModal = () => modalElement.remove();

        closeButton.addEventListener('click', closeModal);
        overlay.addEventListener('click', (e) => {
            if (e.target === overlay) closeModal();
        });
        document.addEventListener('keydown', (e) => {
            if (e.key === 'Escape') closeModal();
        }, { once: true });
    }

    function createSpinner() {
        const spinner = document.createElement('div');
        spinner.className = 'mz-loader';
        spinner.style.display = 'none';
        document.body.appendChild(spinner);
        return spinner;
    }

    function promptForParams() {
        const modalHtml = `
            <div class="mz-modal-overlay mz-modern">
                <div class="input-modal mz-modern" onclick="event.stopPropagation()">
                    <h3 class="mz-modern">WL Type</h3>
                    <select id="leagueSelect">
                        ${CONSTANTS.LEAGUE_TYPES.map(l => `
                            <option value="${l}">${CONSTANTS.LEAGUE_DISPLAY_NAMES[l]}</option>
                        `).join('')}
                    </select>
                    <h3 class="mz-modern">Round Pairs</h3>
                    <select id="roundSelect">
                        ${CONSTANTS.ROUND_PAIRS.map((pair, index) => `
                            <option value="${index}">${pair.display}</option>
                        `).join('')}
                    </select>
                    <h3 class="mz-modern">Enter Min. Goal Diff.</h3>
                    <input type="text" id="diffInput">
                    <div class="mz-modern">
                        <button class="confirm">Check</button>
                        <button class="cancel">Cancel</button>
                    </div>
                </div>
            </div>
        `;

        return new Promise((resolve, reject) => {
            const modalElement = document.createElement('div');
            modalElement.innerHTML = modalHtml;
            document.body.appendChild(modalElement);

            const leagueSelect = modalElement.querySelector('#leagueSelect');
            const roundSelect = modalElement.querySelector('#roundSelect');
            const input = modalElement.querySelector('#diffInput');
            const confirmBtn = modalElement.querySelector('.confirm');
            const cancelBtn = modalElement.querySelector('.cancel');
            const overlay = modalElement.querySelector('.mz-modal-overlay');

            const cleanup = () => modalElement.remove();

            [leagueSelect, roundSelect, input].forEach(el => {
                el.addEventListener('click', e => e.stopPropagation());
                el.addEventListener('keydown', e => { if (e.key === 'Enter') e.preventDefault(); });
            });

            const handleConfirm = () => {
                const val = parseInt(input.value);
                if (val >= 1 && val <= 10) {
                    const selectedPair = CONSTANTS.ROUND_PAIRS[parseInt(roundSelect.value)];
                    cleanup();
                    resolve({
                        leagueType: leagueSelect.value,
                        diff: val,
                        roundNumbers: {
                            first: selectedPair.first,
                            second: selectedPair.second
                        }
                    });
                } else {
                    alert(CONSTANTS.ERROR_MESSAGES.INVALID_INPUT);
                }
            };

            input.addEventListener('keydown', e => {
                if (e.key === 'Enter') handleConfirm();
            });

            confirmBtn.addEventListener('click', handleConfirm);
            cancelBtn.addEventListener('click', () => {
                cleanup();
                reject('Cancelled');
            });

            overlay.addEventListener('click', (e) => {
                if (e.target === overlay) {
                    cleanup();
                    reject('Cancelled');
                }
            });

            setTimeout(() => leagueSelect.focus(), 0);
        });
    }

    let selectedLeagueType;

    async function init() {
        const header = document.querySelector(CONSTANTS.DOM.HEADER_ID);
        if (!header) return;

        const button = document.createElement('button');
        button.className = CONSTANTS.DOM.BUTTON_CLASS;
        button.innerHTML = CONSTANTS.DOM.BUTTON_TEXT;
        button.title = CONSTANTS.DOM.BUTTON_TITLE;
        header.appendChild(button);

        const spinner = createSpinner();

        button.addEventListener('click', async () => {
            try {
                const params = await promptForParams();
                selectedLeagueType = params.leagueType;
                const minTotalDiff = params.diff;
                const roundNumbers = params.roundNumbers;
                const allDiscrepancies = [];

                spinner.style.display = 'block';
                button.style.cursor = 'wait';
                button.disabled = true;

                for (let worldLeagueId = CONSTANTS.LEAGUE_LIMITS.div1; worldLeagueId <= CONSTANTS.LEAGUE_LIMITS.div2; worldLeagueId++) {
                    const result = await processLeague(selectedLeagueType, worldLeagueId, minTotalDiff, roundNumbers);
                    allDiscrepancies.push(...result.discrepancies);
                }

                spinner.style.display = 'none';
                if (allDiscrepancies.length) {
                    createModal(allDiscrepancies, selectedLeagueType, minTotalDiff, roundNumbers);
                } else {
                    alert(CONSTANTS.ERROR_MESSAGES.NO_DISCREPANCIES);
                }
            } catch (error) {
                spinner.style.display = 'none';
                if (error !== 'Cancelled') {
                    console.error('Error:', error);
                }
            } finally {
                button.style.cursor = 'pointer';
                button.disabled = false;
            }
        });
    }

    init();
})();