// ==UserScript==
// @name MZ - Ongoing Match Results
// @namespace http://tampermonkey.net/
// @version 1.7
// @description Fetch ongoing match results and update 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*
// @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_"]',
'[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) {
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.isPrivateCup || this.isCup) {
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;
});
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';
row.style.borderBottom = '';
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';
}
tempContainer.appendChild(row);
});
nonDataRows.forEach(row => tempContainer.appendChild(row));
tbody.parentNode.replaceChild(tempContainer, tbody);
}
}
setTimeout(() => new MatchTracker(), 3333);
})();