// ==UserScript== // @name MZ - Ongoing Match Results // @namespace http://tampermonkey.net/ // @version 1.6 // @description Track ongoing matches // @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 // @downloadURL none // ==/UserScript== (function() { 'use strict'; const style = document.createElement('style'); style.textContent = ` #nprogress { pointer-events: none; } #nprogress .bar { background: #ff6600; position: fixed; z-index: 1031; top: 0; left: 0; width: 100%; height: 3px; } #nprogress .peg { display: block; position: absolute; right: 0px; width: 100px; height: 100%; box-shadow: 0 0 10px #ff6600, 0 0 5px #ff6600; opacity: 1.0; transform: rotate(3deg) translate(0px, -4px); } .status-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.5); z-index: 1000; display: flex; justify-content: center; align-items: center; color: white; font-size: 24px; font-weight: bold; text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5); } .mz-modal { position: fixed; bottom: 20px; right: 20px; background: white; padding: 20px; border-radius: 8px; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); z-index: 1100; max-width: 300px; max-height: 400px; overflow-y: auto; transition: opacity 0.3s ease, transform 0.3s ease; opacity: 0; transform: translateY(20px); } .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 #eee; } .mz-modal-close { cursor: pointer; padding: 5px 10px; background: #ff6600; color: white; border: none; border-radius: 4px; } .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 #eee; } .mz-match-result:last-child { border-bottom: none; } `; document.head.appendChild(style); const UI = { BUTTON_STYLES: { backgroundColor: 'navy', color: 'orange', border: '2px solid lightgray', marginLeft: '10px', cursor: 'pointer' }, BUTTON_STATES: { READY: 'Get match results', FETCHING: 'Fetching matches...' }, LOADING_MESSAGES: { MATCHES: 'Fetching match data...', RESULTS: 'Processing results...', UPDATING: 'Updating standings...' } }; const SELECTORS = { TRACK_BUTTONS: [ '[id^="trackButton_u18_world_series_"]', '[id^="trackButton_u18_series_"]', '[id^="trackButton_friendlyseries_"]', '#trackButton_u18_world_series_3' ], MATCHES_TABLE: 'table.hitlist', STANDINGS_TABLE: 'table.nice_table' }; class MatchTracker { constructor() { this.matchResults = new Map(); this.isFriendlySeries = window.location.href.includes('friendlyseries'); this.init(); } 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('click', () => 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 = `

Match Results

`; 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) { NProgress.configure({ showSpinner: false }); NProgress.start(); this.showLoadingOverlay(UI.LOADING_MESSAGES.MATCHES); if (!matches.length) { NProgress.done(); this.hideLoadingOverlay(); return; } button.disabled = true; button.textContent = UI.BUTTON_STATES.FETCHING; this.showLoadingOverlay(UI.LOADING_MESSAGES.RESULTS); const results = await this.processMatches(matches); if (this.isFriendlySeries) { this.showResultsModal(results); } this.showLoadingOverlay(UI.LOADING_MESSAGES.UPDATING); this.updateAllTeamStats(); this.finalizeUpdate(button); NProgress.done(); this.hideLoadingOverlay(); } 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 headerRows = Array.from(tbody.querySelectorAll('tr.seriesHeader')); const dataRows = Array.from(tbody.querySelectorAll('tr')).filter(row => !row.classList.contains('seriesHeader')); dataRows.forEach(row => { row.classList.remove('highlight_row'); row.style.borderBottom = ''; row.className = ''; }); dataRows.sort((a, b) => { const parseCell = (row, idx) => parseInt(row.querySelectorAll('td')[idx].textContent, 10) || 0; const parseGoalDiff = (row) => parseInt(row.querySelectorAll('td')[8].querySelector('nobr')?.textContent || '0', 10); const pointsA = parseCell(a, 9); const pointsB = parseCell(b, 9); if (pointsB !== pointsA) return pointsB - pointsA; const goalDiffA = parseGoalDiff(a); const goalDiffB = parseGoalDiff(b); if (goalDiffB !== goalDiffA) return goalDiffB - goalDiffA; return parseCell(b, 6) - parseCell(a, 6); }); tbody.innerHTML = ''; headerRows.forEach(row => tbody.appendChild(row)); dataRows.forEach((row, index) => { const positionCell = row.querySelector('td:first-child span'); if (positionCell) { positionCell.textContent = (index + 1).toString(); } row.className = index % 2 === 0 ? '' : 'highlight_row'; if (index === 0) { row.style.borderBottom = '2px solid green'; } 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); })();