// ==UserScript== // @name MZ - Ongoing Match Results // @namespace douglaskampl // @version 2.2 // @description Tracks ongoing matches results and updates standings // @author Douglas Vieira // @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 // @require https://cdnjs.cloudflare.com/ajax/libs/nprogress/0.2.0/nprogress.min.js // @license MIT // @downloadURL none // ==/UserScript== GM_addStyle(` #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; } .mz-fetch-button { background-color: #1a1a1a; color: #ff6600; border: 1px solid #ff6600; margin-left: 10px; cursor: pointer; padding: 5px 10px; border-radius: 4px; transition: all 0.2s ease; } .mz-fetch-button:hover { background-color: #333 !important; } .mz-fetch-button.disabled { opacity: 0.7 !important; cursor: not-allowed !important; background-color: #333 !important; border-color: #666 !important; color: #999 !important; } .mz-fetch-button.done { background-color: #333 !important; border-color: #666 !important; color: #999 !important; cursor: default !important; } .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; } .mz-table-row-champion { border-bottom: 2px solid green !important; } .mz-table-row-promotion { border-bottom: 2px dashed #556B2F !important; } .mz-table-row-relegation { border-bottom: 2px solid red !important; } @keyframes fadeInOut { 0% { opacity: 0; transform: translateY(-10px); } 100% { opacity: 1; transform: translateY(0); } } `); (function() { 'use strict'; const UI = { 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_"]', '[id^="trackButton_privatecup_"]', '[id^="trackButton_cup_"]' ], MATCHES_TABLE: 'table.hitlist', STANDINGS_TABLE: 'table.nice_table' }; class MatchTracker { constructor() { this.matchResults = new Map(); this.isFriendlySeries = 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.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 = await this.getMatches(); if (!matches || !matches.length) { console.log('No ongoing matches found'); return; } this.setupUI(matches); } async getMatches() { if (this.isFriendlySeries) { return await this.getFriendlySeriesMatches(); } else if (this.isPrivateCup || this.isCup) { return this.getCupMatches(); } return this.getLeagueMatches(); } 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 !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 }; }); } setupUI(matches) { const trackButton = this.findTrackButton(); if (!trackButton) return; 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; } 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) { 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.classList.add('disabled'); button.textContent = UI.BUTTON_STATES.FETCHING; this.showStatusMessage(UI.LOADING_MESSAGES.RESULTS); const results = await this.processMatches(matches); if (this.isFriendlySeries || this.isPrivateCup || this.isCup) { this.showResultsModal(results); } this.showStatusMessage(UI.LOADING_MESSAGES.UPDATING); this.updateAllTeamStats(); button.classList.remove('disabled'); button.classList.add('done'); button.textContent = UI.BUTTON_STATES.DONE; 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 []; try { const response = await fetch( `https://www.managerzone.com/ajax.php?p=friendlySeries&sub=matches&fsid=${fsidMatch[1]}&sport=soccer` ); 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, timeStr] = dateTimeMatch; 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 matches = matchesDiv.querySelectorAll('tr'); matches.forEach(match => { const link = match.querySelector('a[href*="mid="]'); if (link) { const score = link.textContent.trim(); if (!score.match(/^\d+-\d+$/)) { if (score === 'X - X') { if (now >= matchTime && now <= matchEndTime) { const homeTeam = match.querySelector('td:first-child').textContent.trim(); const awayTeam = match.querySelector('td:last-child').textContent.trim(); const params = new URLSearchParams(link.href); ongoingMatches.push({ mid: params.get('mid'), homeTeam, awayTeam }); } } else { const homeTeam = match.querySelector('td:first-child').textContent.trim(); const awayTeam = match.querySelector('td:last-child').textContent.trim(); const params = new URLSearchParams(link.href); ongoingMatches.push({ mid: params.get('mid'), homeTeam, awayTeam }); } } } }); } } }); return ongoingMatches; } catch (error) { console.error('Error fetching friendly series matches:', error); return []; } } 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}`); } async processMatches(matches) { const results = []; const total = matches.length; for (let i = 0; i < matches.length; i++) { const match = matches[i]; try { const response = await fetch( `https://www.managerzone.com/xml/match_info.php?sport_id=1&match_id=${match.mid}` ); const text = await response.text(); const matchData = this.parseMatchResponse({ responseText: text }); if (matchData) { this.matchResults.set(match.mid, matchData); this.updateMatchDisplay(match.mid, matchData); results.push({ ...match, score: `${matchData.homeGoals}-${matchData.awayGoals}` }); } } catch (error) { console.error(`Error processing match ${match.mid}:`, 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(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; }); this.sortTableByPoints(); } sortTableByPoints() { const table = document.querySelector(SELECTORS.STANDINGS_TABLE); if (!table) return; const tbody = table.querySelector('tbody'); if (!tbody) return; const columnHeaders = Array.from(table.querySelectorAll('thead tr th')); const columnCount = columnHeaders.length; 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)); }); dataRows.sort((a, b) => { const getCellValue = (row, index) => { const cell = row.querySelectorAll('td')[index]; return parseInt(cell.textContent.trim(), 10) || 0; }; const pointsA = getCellValue(a, 9); const pointsB = getCellValue(b, 9); if (pointsB !== pointsA) return pointsB - pointsA; const goalDiffA = getCellValue(a, 6) - getCellValue(a, 7); const goalDiffB = getCellValue(b, 6) - getCellValue(b, 7); if (goalDiffB !== goalDiffA) return goalDiffB - goalDiffA; return getCellValue(b, 6) - getCellValue(a, 6); }); const nonDataRows = Array.from(tbody.children).filter(row => !dataRows.includes(row)); const tempContainer = document.createElement('tbody'); 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.classList.add('mz-table-row-champion'); } else if (index === 1) { row.classList.add('mz-table-row-promotion'); } else if (index === 7) { row.classList.add('mz-table-row-relegation'); } tempContainer.appendChild(row); }); nonDataRows.forEach(row => tempContainer.appendChild(row)); tbody.parentNode.replaceChild(tempContainer, tbody); } } setTimeout(() => new MatchTracker(), 3333); })();