MZ - Ongoing Matches

Fetches results of ongoing matches and updates standings

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

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         MZ - Ongoing Matches
// @namespace    douglaskampl
// @version      3.7
// @description  Fetches results of ongoing matches and updates standings
// @author       Douglas
// @match        https://www.managerzone.com/?p=league*
// @match        https://www.managerzone.com/?p=friendlyseries*
// @match        https://www.managerzone.com/?p=private_cup*
// @match        https://www.managerzone.com/?p=cup*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=managerzone.com
// @grant        GM_addStyle
// @grant        GM_getResourceText
// @require      https://cdnjs.cloudflare.com/ajax/libs/nprogress/0.2.0/nprogress.min.js
// @resource     NPROGRESS_CSS https://unpkg.com/[email protected]/nprogress.css
// @resource     ongoingMatchesStyles https://br18.org/mz/userscript/other/ongoingMatches.css
// @run-at       document-idle
// @license      MIT
// ==/UserScript==

(function () {
    'use strict';

    GM_addStyle(GM_getResourceText('NPROGRESS_CSS'));
    GM_addStyle(GM_getResourceText('ongoingMatchesStyles'));

    const UI = {
        BUTTON_STATES: {
            READY: 'Get match results',
            FETCHING: 'Processing…',
            DONE: 'Results updated'
        },
        PROGRESS_MESSAGES: {
            FETCHING_MATCHES: 'Fetching matches…',
            PROCESSING_RESULTS: 'Processing results…',
            UPDATING_STANDINGS: 'Updating standings…',
            ALL_COMPLETE: 'All updates complete'
        },
        MODAL: {
            NO_MATCHES_FOUND: 'Matches have not started yet. Please wait a few minutes.',
            STANDINGS_UPDATED: 'League standings have been updated with the results above.',
            NO_UPDATES_NEEDED: 'No ongoing matches. League standings were not updated.'
        },
        MATCH_STATUS: {
            WIN: 'green',
            DRAW: 'yellow',
            LOSS: 'red'
        }
    };

    const SELECTORS = {
        TRACK_BUTTONS: [
            '[id^="trackButton_series_"]',
            '[id^="trackButton_u18_series_"]',
            '[id^="trackButton_u21_series_"]',
            '[id^="trackButton_u23_series_"]',
            '[id^="trackButton_world_series_"]',
            '[id^="trackButton_u18_world_series_"]',
            '[id^="trackButton_u21_world_series_"]',
            '[id^="trackButton_u23_world_series_"]',
            '[id^="trackButton_friendlyseries_"]',
            '[id^="trackButton_cup_"]',
            '[id^="trackButton_privatecup_"]'
        ],
        MATCHES_TABLE: 'table.hitlist',
        STANDINGS_TABLE: 'table.nice_table',
        LAST_SIX_CELL: 'td.responsive-hide'
    };

    const ENDPOINTS = {
        MATCH_INFO: 'https://www.managerzone.com/xml/match_info.php?sport_id=1&match_id=',
        FRIENDLY_LEAGUE_SCHEDULE: 'https://www.managerzone.com/ajax.php?p=friendlySeries&sub=matches&sport=soccer&fsid='
    };

    class OngoingMatchesTracker {
        constructor() {
            this.matchResults = new Map();
            this.isFriendlyLeague = window.location.href.includes('friendlyseries');
            this.isPrivateCup = window.location.href.includes('private_cup');
            this.isCup = window.location.href.includes('p=cup');
            this.hasRun = false;
            this.observe();
        }

        async init() {
            const matches = await this.getMatches();
            if (!matches || !matches.length) return;
            this.setUpUI(matches);
        }

        async getMatches() {
            if (this.isFriendlyLeague) return await this.getFriendlyLeagueMatches();
            if (this.isPrivateCup || this.isCup) return this.getCupMatches();
            return this.getLeagueMatches();
        }

        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 !/^\d+\s*-\s*\d+$/.test(score) && !/^X\s*-\s*X$/.test(score);
                })
                .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 getFriendlyLeagueMatches() {
            const fsidMatch = window.location.href.match(/fsid=(\d+)/);
            if (!fsidMatch) return [];
            try {
                const response = await fetch(ENDPOINTS.FRIENDLY_LEAGUE_SCHEDULE + fsidMatch[1]);
                const text = await response.text();
                const doc = new DOMParser().parseFromString(text, 'text/html');
                const now = new Date();
                const ongoingMatches = [];
                const rounds = doc.querySelectorAll('h2.subheader.clearfix');
                rounds.forEach(round => {
                    const headerText = round.textContent;
                    const dateTimeMatch = headerText.match(/(\d{2}\/\d{2}\/\d{4})\s+(\d{1,2}:\d{2}(?:am|pm))/i);
                    if (dateTimeMatch) {
                        const dateStr = dateTimeMatch[1];
                        const timeStr = dateTimeMatch[2];
                        const matchTime = this.parseDateTime(dateStr, timeStr);
                        const matchEndTime = new Date(matchTime.getTime() + 2 * 60 * 60 * 1000);
                        const matchesDiv = round.nextElementSibling;
                        if (matchesDiv && matchesDiv.classList.contains('mainContent')) {
                            const matchRows = matchesDiv.querySelectorAll('tr');
                            matchRows.forEach(m => {
                                const link = m.querySelector('a[href*="mid="]');
                                if (link) {
                                    const score = link.textContent.trim();
                                    if (!/^\d+-\d+$/.test(score)) {
                                        if (score === 'X - X') {
                                            if (now >= matchTime && now <= matchEndTime) {
                                                const homeTeam = m.querySelector('td:first-child').textContent.trim();
                                                const awayTeam = m.querySelector('td:last-child').textContent.trim();
                                                const params = new URLSearchParams(link.href);
                                                ongoingMatches.push({
                                                    mid: params.get('mid'),
                                                    homeTeam,
                                                    awayTeam
                                                });
                                            }
                                        } else {
                                            const homeTeam = m.querySelector('td:first-child').textContent.trim();
                                            const awayTeam = m.querySelector('td:last-child').textContent.trim();
                                            const params = new URLSearchParams(link.href);
                                            ongoingMatches.push({
                                                mid: params.get('mid'),
                                                homeTeam,
                                                awayTeam
                                            });
                                        }
                                    }
                                }
                            });
                        }
                    }
                });
                return ongoingMatches;
            } catch (error) {
                return [];
            }
        }

        getCupMatches() {
            const groupStages = document.querySelector('#group-stages');
            if (!groupStages) return [];
            return Array.from(groupStages.querySelectorAll('table.hitlist tr'))
                .filter(row => {
                    const link = row.querySelector('a[href*="mid="]');
                    if (!link) return false;
                    const score = link.textContent.trim();
                    return !/^\d+\s*-\s*\d+$/.test(score) && !/^X\s*-\s*X$/.test(score);
                })
                .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
                    };
                });
        }

        setUpUI(matches) {
            const trackButton = this.findTrackButton();
            if (!trackButton) return;
            if (trackButton.parentNode.classList.contains('mz-fetch-added')) return;
            trackButton.parentNode.classList.add('mz-fetch-added');
            const fetchButton = this.createFetchButton();
            trackButton.parentNode.insertBefore(fetchButton, trackButton.nextSibling);
            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');
            button.className = 'mz-fetch-button';
            button.textContent = UI.BUTTON_STATES.READY;
            return button;
        }

        showResultsModal(results) {
            const modal = document.createElement('div');
            modal.className = 'mz-modal';
            const roundNum = this.getCurrentRound();
            const headerTitle = roundNum
                ? `Match Results for Round ${roundNum}`
                : 'Match Results for Current Round';

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

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

            if (results && results.length > 0) {
                results.forEach(r => {
                    const matchDiv = document.createElement('div');
                    matchDiv.className = 'mz-match-result';
                    const matchLink = document.createElement('a');
                    matchLink.href = `https://www.managerzone.com/?p=match&mid=${r.mid}`;
                    matchLink.className = 'mz-match-link';
                    matchLink.textContent = `${r.homeTeam} ${r.score} ${r.awayTeam}`;
                    matchDiv.appendChild(matchLink);
                    content.appendChild(matchDiv);
                });
            } else {
                const noMatchesDiv = document.createElement('div');
                noMatchesDiv.className = 'mz-no-matches';
                noMatchesDiv.textContent = UI.MODAL.NO_MATCHES_FOUND;
                content.appendChild(noMatchesDiv);
            }

            const footer = document.createElement('div');
            footer.className = 'mz-modal-footer';
            footer.innerHTML = `
            <div class="mz-update-info">
              ${results && results.length > 0
                    ? UI.MODAL.STANDINGS_UPDATED
                    : UI.MODAL.NO_UPDATES_NEEDED}
            </div>
          `;

            modal.appendChild(header);
            modal.appendChild(content);
            modal.appendChild(footer);
            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 clickStandingsTab() {
            if (window.location.href === 'https://www.managerzone.com/?p=league&type=senior') {
                const ul = document.querySelectorAll('.ui-tabs-nav')[0];
                if (ul) {
                    const tabs = ul.querySelectorAll('li[role="tab"]');
                    if (tabs.length >= 1) {
                        const firstTab = tabs[0];
                        if (
                            !firstTab.classList.contains('ui-tabs-active') &&
                            !firstTab.classList.contains('ui-state-active')
                        ) {
                            firstTab.querySelector('a').click();
                            await new Promise(r => setTimeout(r, 1000));
                        }
                    }
                }
            } else if (window.location.href.includes('p=league')) {
                const ul = document.querySelectorAll('.ui-tabs-nav')[0];
                if (ul) {
                    const tabs = ul.querySelectorAll('li[role="tab"]');
                    if (tabs.length >= 2) {
                        const secondTab = tabs[1];
                        if (
                            !secondTab.classList.contains('ui-tabs-active') &&
                            !secondTab.classList.contains('ui-state-active')
                        ) {
                            secondTab.querySelector('a').click();
                            await new Promise(r => setTimeout(r, 1000));
                        }
                    }
                }
            } else if (this.isFriendlyLeague) {
                const allUls = document.querySelectorAll('.ui-tabs-nav');
                if (allUls.length >= 2) {
                    const secondUl = allUls[1];
                    const tabs = secondUl.querySelectorAll('li[role="tab"]');
                    if (tabs.length >= 2) {
                        const secondTab = tabs[1];
                        if (
                            !secondTab.classList.contains('ui-tabs-active') &&
                            !secondTab.classList.contains('ui-state-active')
                        ) {
                            secondTab.querySelector('a').click();
                            await new Promise(r => setTimeout(r, 1000));
                        }
                    }
                }
            }
        }

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

            await this.clickStandingsTab();

            NProgress.configure({ showSpinner: false });
            NProgress.start();
            this.showStatusMessage(UI.PROGRESS_MESSAGES.FETCHING_MATCHES);

            if (!matches.length) {
                if (this.isFriendlyLeague || this.isPrivateCup || this.isCup) {
                    this.showResultsModal([]);
                }
                NProgress.done();
                return;
            }

            button.classList.add('disabled');
            button.textContent = UI.BUTTON_STATES.FETCHING;

            this.showStatusMessage(UI.PROGRESS_MESSAGES.PROCESSING_RESULTS);
            const results = await this.processMatches(matches);

            if (this.isFriendlyLeague || this.isPrivateCup || this.isCup) {
                this.showResultsModal(results);
            }

            this.showStatusMessage(UI.PROGRESS_MESSAGES.UPDATING_STANDINGS);
            this.updateAllTeamStats();

            button.classList.remove('disabled');
            button.classList.add('done');
            button.textContent = UI.BUTTON_STATES.DONE;

            NProgress.done();
            this.showStatusMessage(UI.PROGRESS_MESSAGES.ALL_COMPLETE);
        }

        parseDateTime(dateStr, timeStr) {
            const [day, month, year] = dateStr.split('/');
            const date = `${month}/${day}/${year}`;
            let [time, period] = timeStr.toLowerCase().split(/(?=[ap]m)/);
            let [hours, minutes] = time.split(':');
            hours = parseInt(hours);
            if (period === 'pm' && hours !== 12) hours += 12;
            else if (period === 'am' && hours === 12) hours = 0;
            return new Date(`${date} ${hours}:${minutes}`);
        }

        getCurrentRound() {
            const table = document.querySelector(SELECTORS.STANDINGS_TABLE);
            if (!table) return null;
            const firstDataRow = table.querySelector('tbody tr');
            if (!firstDataRow) return null;
            const matchesCell = firstDataRow.querySelector('td:nth-child(3)');
            if (!matchesCell) return null;
            return parseInt(matchesCell.textContent.trim(), 10) + 1;
        }

        async processMatches(matches) {
            const results = [];
            const total = matches.length;
            for (let i = 0; i < total; i++) {
                const match = matches[i];
                try {
                    const response = await fetch(`${ENDPOINTS.MATCH_INFO}${match.mid}`);
                    const text = await response.text();
                    const matchData = this.parseMatchResponse({ responseText: text });
                    if (matchData) {
                        matchData.homeTeam = match.homeTeam;
                        matchData.awayTeam = match.awayTeam;
                        this.matchResults.set(match.mid, matchData);
                        this.updateMatchDisplay(match.mid, matchData);
                        results.push({
                            ...match,
                            score: `${matchData.homeGoals}-${matchData.awayGoals}`
                        });
                    }
                } catch (error) { }
                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(l => l.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 }
                };
            }
        }

        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(r => {
                const cells = r.querySelectorAll('td');
                return cells.length >= 10 && cells[2] && !isNaN(parseInt(cells[2].textContent));
            });
            dataRows.sort((a, b) => {
                const getVal = (rw, i) => parseInt(rw.querySelectorAll('td')[i].textContent.trim(), 10) || 0;
                const pointsA = getVal(a, 9);
                const pointsB = getVal(b, 9);
                if (pointsB !== pointsA) return pointsB - pointsA;
                const goalDiffA = getVal(a, 6) - getVal(a, 7);
                const goalDiffB = getVal(b, 6) - getVal(b, 7);
                if (goalDiffB !== goalDiffA) return goalDiffB - goalDiffA;
                return getVal(b, 6) - getVal(a, 6);
            });
            const nonDataRows = Array.from(tbody.children).filter(r => !dataRows.includes(r));
            const tempContainer = document.createDocumentFragment();
            dataRows.forEach((row, index) => {
                const originalRow = row.cloneNode(true);
                const positionCell = originalRow.querySelector('td:first-child span');
                if (positionCell) {
                    const prevHelpButton = row.querySelector('td:first-child span .help_button');
                    positionCell.innerHTML = `${index + 1}`;
                    if (prevHelpButton) positionCell.appendChild(prevHelpButton);
                }
                originalRow.className = index % 2 === 0 ? '' : 'highlight_row';
                if (index === 0) originalRow.classList.add('mz-table-row-champion');
                else if (index === 1) originalRow.classList.add('mz-table-row-promotion');
                else if (index === 7) originalRow.classList.add('mz-table-row-relegation');
                tempContainer.appendChild(originalRow);
            });
            nonDataRows.forEach(r => {
                tempContainer.appendChild(r.cloneNode(true));
            });
            tbody.innerHTML = '';
            tbody.appendChild(tempContainer);
            table.classList.add('standings-updated');
            setTimeout(() => table.classList.remove('standings-updated'), 2000);
        }

        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(r => r.querySelector(`a[href*="tid=${tid}"]`));
            highlightedRows.forEach(r => rowsSet.add(r));
            return Array.from(rowsSet);
        }

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

        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 = c => parseInt(c.textContent, 10) || 0;
                const updateCell = (c, val) => {
                    c.classList.add('updated-cell');
                    c.textContent = val;
                    setTimeout(() => c.classList.remove('updated-cell'), 1500);
                };
                updateCell(cells[2], parseCell(cells[2]) + 1);
                if (result.points === 3) updateCell(cells[3], parseCell(cells[3]) + 1);
                else if (result.points === 1) updateCell(cells[4], parseCell(cells[4]) + 1);
                else updateCell(cells[5], parseCell(cells[5]) + 1);
                updateCell(cells[6], parseCell(cells[6]) + result.goalsFor);
                updateCell(cells[7], parseCell(cells[7]) + result.goalsAgainst);
                const goalDiff = parseCell(cells[6]) - parseCell(cells[7]);
                const goalDiffElem = cells[8].querySelector('nobr');
                if (goalDiffElem) updateCell(goalDiffElem, goalDiff);
                updateCell(cells[9], parseCell(cells[9]) + result.points);
                this.updateLastSixDisplay(row, result, tid);
            });
        }

        updateLastSixDisplay(row, result, tid) {
            const lastSixCell = row.querySelector(SELECTORS.LAST_SIX_CELL);
            if (!lastSixCell) return;
            const nobrElement = lastSixCell.querySelector('nobr');
            if (!nobrElement) return;
            const links = nobrElement.querySelectorAll('a');
            const allTeamMatches = Array.from(this.matchResults.entries()).filter(([_, data]) => data.homeTid === tid || data.awayTid === tid);
            if (!allTeamMatches.length) return;
            const [matchId, data] = allTeamMatches[allTeamMatches.length - 1];
            const matchStatus = this.getMatchStatus(result);
            if (links.length >= 6) links[0].remove();
            const newStatusLink = this.createStatusLink(matchStatus, matchId, data, tid);
            nobrElement.appendChild(newStatusLink);
        }

        getMatchStatus(result) {
            if (result.points === 3) return UI.MATCH_STATUS.WIN;
            if (result.points === 1) return UI.MATCH_STATUS.DRAW;
            return UI.MATCH_STATUS.LOSS;
        }

        createStatusLink(status, matchId, matchData, tid) {
            const link = document.createElement('a');
            link.href = `/?p=match&sub=result&mid=${matchId}`;
            const isHome = matchData.homeTid === tid;
            const teamScore = isHome ? matchData.homeGoals : matchData.awayGoals;
            const oppScore = isHome ? matchData.awayGoals : matchData.homeGoals;
            link.title = `${matchData.homeTeam} ${matchData.homeGoals} - ${matchData.awayGoals} ${matchData.awayTeam}`;
            const img = document.createElement('img');
            img.style.border = '0';
            img.src = `img/status_${status}.gif`;
            img.alt = '';
            img.width = '13';
            img.height = '12';
            link.appendChild(img);
            return link;
        }

        showStatusMessage(message) {
            const existingMessage = document.querySelector('.status-message');
            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);
        }

        observe() {
            const observer = new MutationObserver((mutations) => {
                for (const mutation of mutations) {
                    if (mutation.addedNodes.length) {
                        const trackButton = this.findTrackButton();
                        if (trackButton && !document.querySelector('.mz-fetch-button')) {
                            this.init();
                            break;
                        }
                    }
                }
            });

            observer.observe(document.body, {
                childList: true,
                subtree: true
            });
        }
    }

    const tracker = new OngoingMatchesTracker();
    tracker.init();
})();