您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Detects potentially weird match results in world leagues
// ==UserScript== // @name MZ - WLMatches // @namespace douglaskampl // @version 3.1 // @description Detects potentially weird match results in world leagues // @author Douglas // @match https://www.managerzone.com/?p=match&sub=livescores_overview // @icon https://www.google.com/s2/favicons?sz=64&domain=managerzone.com // @grant GM_addStyle // @grant GM_getResourceText // @resource weirdWLMatchesStyles https://br18.org/mz/userscript/other/worldLeagueMatches.css // @run-at document-idle // @license MIT // ==/UserScript== (function () { 'use strict'; GM_addStyle(GM_getResourceText('weirdWLMatchesStyles')); const improvedSpinnerStyles = ` .mz-loader { box-sizing: border-box; font-size: 0; width: 40px; height: 40px; position: fixed; top: calc(50% - 20px); left: calc(50% - 20px); z-index: 10001; border-radius: 50%; border: 4px solid rgba(150, 150, 150, 0.3); border-top-color: #555555; animation: mz-loader-spin 0.8s linear infinite; } @keyframes mz-loader-spin { to { transform: rotate(360deg); } } `; GM_addStyle(improvedSpinnerStyles); const CONSTANTS = { LEAGUE_TYPES: ['senior', 'u18_world', 'u21_world', 'u23_world'], LEAGUE_DISPLAY_NAMES: { 'senior': 'Senior', 'u18_world': 'U18', 'u21_world': 'U21', 'u23_world': 'U23' }, LEAGUE_LIMITS: { div1: 1, div2: 13, div3: 40, div4: 121, div5: 291 }, ROUND_PAIRS: [ { first: '11', second: '12', display: '11/12' }, { first: '10', second: '13', display: '10/13' }, { first: '9', second: '14', display: '9/14' }, { first: '8', second: '15', display: '8/15' }, { first: '7', second: '16', display: '7/16' }, { first: '6', second: '17', display: '6/17' }, { first: '5', second: '18', display: '5/18' }, { first: '4', second: '19', display: '4/19' }, { first: '3', second: '20', display: '3/20' }, { first: '2', second: '21', display: '2/21' }, { first: '1', second: '22', display: '1/22' } ], ERROR_MESSAGES: { FETCH_ERROR: 'Error fetching data from league', NO_DISCREPANCIES: 'No result inconsistencies found in this league.', INVALID_INPUT: 'Please enter a number between 1 and 10.' }, DOM: { NAV_UL_SELECTOR: '#leftnav-wrapper ul.leftnav', BUTTON_CLASS: 'wl-check-button', BUTTON_TEXT: 'CHECK RESULTS', BUTTON_TITLE: 'Find inconsistent results between home and away matches' }, REGEX: { SCORE: /<a[^>]*>([^<]+)<\/a>/, LINK: /<a href="([^"]+)"/, UNPLAYED_SCORE: /X\s*-\s*X/ } }; function getDivisionName(sid) { if (sid === CONSTANTS.LEAGUE_LIMITS.div1) return "Top Series"; if (sid >= 2 && sid <= 4) return `1.${sid - 1}`; if (sid >= 5 && sid <= CONSTANTS.LEAGUE_LIMITS.div2) return `2.${sid - 4}`; if (sid >= 14 && sid <= CONSTANTS.LEAGUE_LIMITS.div3) return `3.${sid - 13}`; return `4.${sid - 40}`; } async function fetchLeagueData(leagueType, worldLeagueId) { let finalLeagueType = leagueType === 'senior' ? 'world' : leagueType; const url = `/ajax.php?p=league&type=${finalLeagueType}&sid=${worldLeagueId}&tid=1&sport=soccer&sub=schedule`; try { const response = await fetch(url); if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`); return await response.text(); } catch (error) { console.error(`${CONSTANTS.ERROR_MESSAGES.FETCH_ERROR} ${worldLeagueId}:`, error.message); return null; } } function extractMatchesFromHTML(html) { const parser = new DOMParser(); const doc = parser.parseFromString(html, 'text/html'); const roundHeaders = doc.querySelectorAll('h2.subheader'); const matches = []; roundHeaders.forEach((header, index) => { const roundNumber = index + 1; const nextElement = header.nextElementSibling; if (nextElement && nextElement.classList.contains('mainContent')) { const matchRows = nextElement.querySelectorAll('tr'); matchRows.forEach(row => { const teams = row.querySelectorAll('td'); const scoreLink = row.querySelector('a'); if (teams.length >= 3 && scoreLink) { const match = { teams: [ teams[0].textContent.trim(), teams[2].textContent.trim() ], score: scoreLink.textContent.trim(), link: scoreLink.getAttribute('href'), round: roundNumber.toString() }; matches.push(match); } }); } }); return matches; } function checkDiscrepancy(homeScore, awayScore) { const homeDiff = homeScore[0] - homeScore[1]; const awayDiff = awayScore[1] - awayScore[0]; const getWinner = diff => diff > 0 ? 0 : diff < 0 ? 1 : 2; const homeWinner = getWinner(homeDiff); const awayWinner = getWinner(awayDiff); return { totalDiff: Math.abs(homeDiff) + Math.abs(awayDiff), isDiscrepancy: homeWinner !== 2 && awayWinner !== 2 && homeWinner !== awayWinner }; } async function processLeague(leagueType, worldLeagueId, minTotalDiff, roundNumbers) { const html = await fetchLeagueData(leagueType, worldLeagueId); if (!html) return { discrepancies: [] }; const matches = extractMatchesFromHTML(html); const homeResults = new Map(); const awayResults = new Map(); const discrepancies = []; matches.forEach(match => { if (CONSTANTS.REGEX.UNPLAYED_SCORE.test(match.score)) return; const matchKey = `${match.teams[0]} vs ${match.teams[1]}`; if (match.round === roundNumbers.first) { homeResults.set(matchKey, match); } else if (match.round === roundNumbers.second) { awayResults.set(matchKey, match); } }); for (const [key, awayMatch] of awayResults) { const homeKey = `${awayMatch.teams[1]} vs ${awayMatch.teams[0]}`; const homeMatch = homeResults.get(homeKey); if (!homeMatch) continue; const homeScore = homeMatch.score.split(" - ").map(Number); const awayScore = awayMatch.score.split(" - ").map(Number); const { totalDiff, isDiscrepancy } = checkDiscrepancy(homeScore, awayScore); if (totalDiff >= minTotalDiff && isDiscrepancy) { discrepancies.push({ division: getDivisionName(worldLeagueId), divisionId: worldLeagueId, homeMatch: { ...homeMatch }, awayMatch: { ...awayMatch } }); } } return { discrepancies }; } function createModal(discrepancies, leagueType, minDiff, roundNumbers) { const modalHtml = ` <div class="mz-modal-overlay mz-modern"> <div class="mz-modal mz-modern"> <div class="mz-modal-header mz-modern"> <h2 class="mz-modal-title"> ${CONSTANTS.LEAGUE_DISPLAY_NAMES[leagueType]} Inconsistent Results [R${roundNumbers.first}/${roundNumbers.second} | Min Diff: ${minDiff}] </h2> <button class="mz-modal-close">×</button> </div> <div class="mz-modal-content mz-modern"> ${discrepancies.map(d => ` <div class="mz-discrepancy mz-modern"> <div class="mz-discrepancy-header mz-modern"> DIVISION: <a href="/?p=league&type=placeholder&sid=${d.divisionId}" class="mz-div-link">${d.division}</a> </div> <div> R${d.homeMatch.round}: <a href="${d.homeMatch.link}" class="mz-match-link" target="_blank"> ${d.homeMatch.teams[0]} vs ${d.homeMatch.teams[1]} (${d.homeMatch.score}) </a> </div> <div> R${d.awayMatch.round}: <a href="${d.awayMatch.link}" class="mz-match-link" target="_blank"> ${d.awayMatch.teams[0]} vs ${d.awayMatch.teams[1]} (${d.awayMatch.score}) </a> </div> </div> `).join('')} </div> </div> </div> `; const modalElement = document.createElement('div'); modalElement.innerHTML = modalHtml; document.body.appendChild(modalElement); const links = modalElement.querySelectorAll('.mz-div-link'); for (const a of links) { const href = a.getAttribute('href'); const replaced = href.replace('placeholder', encodeURIComponent(leagueType)); a.setAttribute('href', replaced); } const closeButton = modalElement.querySelector('.mz-modal-close'); const overlay = modalElement.querySelector('.mz-modal-overlay'); const closeModal = () => modalElement.remove(); closeButton.addEventListener('click', closeModal); overlay.addEventListener('click', (e) => { if (e.target === overlay) closeModal(); }); document.addEventListener('keydown', (e) => { if (e.key === 'Escape') closeModal(); }, { once: true }); } function createSpinner() { const spinner = document.createElement('div'); spinner.className = 'mz-loader'; spinner.style.display = 'none'; document.body.appendChild(spinner); return spinner; } function promptForParams() { const modalHtml = ` <div class="mz-modal-overlay mz-modern"> <div class="input-modal mz-modern" onclick="event.stopPropagation()"> <h3>SELECT LEAGUE TYPE</h3> <select id="leagueSelect"> ${CONSTANTS.LEAGUE_TYPES.map(l => ` <option value="${l}">${CONSTANTS.LEAGUE_DISPLAY_NAMES[l]}</option> `).join('')} </select> <h3>SELECT ROUNDS TO COMPARE</h3> <select id="roundSelect"> ${CONSTANTS.ROUND_PAIRS.map((pair, index) => ` <option value="${index}">${pair.display}</option> `).join('')} </select> <h3>MINIMUM GOAL DIFFERENCE</h3> <input type="text" id="diffInput" placeholder="1-10"> <div> <button class="confirm">SEARCH</button> <button class="cancel">CANCEL</button> </div> </div> </div> `; return new Promise((resolve, reject) => { const modalElement = document.createElement('div'); modalElement.innerHTML = modalHtml; document.body.appendChild(modalElement); const leagueSelect = modalElement.querySelector('#leagueSelect'); const roundSelect = modalElement.querySelector('#roundSelect'); const input = modalElement.querySelector('#diffInput'); const confirmBtn = modalElement.querySelector('.confirm'); const cancelBtn = modalElement.querySelector('.cancel'); const overlay = modalElement.querySelector('.mz-modal-overlay'); const cleanup = () => modalElement.remove(); [leagueSelect, roundSelect, input].forEach(el => { el.addEventListener('click', e => e.stopPropagation()); el.addEventListener('keydown', e => { if (e.key === 'Enter') e.preventDefault(); }); }); const handleConfirm = () => { const val = parseInt(input.value); if (val >= 1 && val <= 10) { const selectedPair = CONSTANTS.ROUND_PAIRS[parseInt(roundSelect.value)]; cleanup(); resolve({ leagueType: leagueSelect.value, diff: val, roundNumbers: { first: selectedPair.first, second: selectedPair.second } }); } else { alert(CONSTANTS.ERROR_MESSAGES.INVALID_INPUT); } }; input.addEventListener('keydown', e => { if (e.key === 'Enter') handleConfirm(); }); confirmBtn.addEventListener('click', handleConfirm); cancelBtn.addEventListener('click', () => { cleanup(); reject('Cancelled'); }); overlay.addEventListener('click', (e) => { if (e.target === overlay) { cleanup(); reject('Cancelled'); } }); setTimeout(() => leagueSelect.focus(), 0); }); } let selectedLeagueType; async function init() { const navList = document.querySelector(CONSTANTS.DOM.NAV_UL_SELECTOR); if (!navList) return; const buttonContainer = document.createElement('li'); const button = document.createElement('button'); button.className = CONSTANTS.DOM.BUTTON_CLASS; button.innerHTML = CONSTANTS.DOM.BUTTON_TEXT; button.title = CONSTANTS.DOM.BUTTON_TITLE; buttonContainer.appendChild(button); navList.appendChild(buttonContainer); const spinner = createSpinner(); button.addEventListener('click', async () => { try { const params = await promptForParams(); selectedLeagueType = params.leagueType; const minTotalDiff = params.diff; const roundNumbers = params.roundNumbers; const allDiscrepancies = []; spinner.style.display = 'block'; button.style.cursor = 'wait'; button.disabled = true; for (let worldLeagueId = CONSTANTS.LEAGUE_LIMITS.div1; worldLeagueId <= CONSTANTS.LEAGUE_LIMITS.div2; worldLeagueId++) { const result = await processLeague(selectedLeagueType, worldLeagueId, minTotalDiff, roundNumbers); allDiscrepancies.push(...result.discrepancies); } spinner.style.display = 'none'; if (allDiscrepancies.length) { createModal(allDiscrepancies, selectedLeagueType, minTotalDiff, roundNumbers); } else { alert(CONSTANTS.ERROR_MESSAGES.NO_DISCREPANCIES); } } catch (error) { spinner.style.display = 'none'; if (error !== 'Cancelled') { console.error('Error:', error); } } finally { button.style.cursor = 'pointer'; button.disabled = false; } }); } init(); })();