您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Fetches results of ongoing matches and updates standings
- // ==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/nprogress@0.2.0/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();
- })();