// ==UserScript== // @name MZ - WL Weird Matches // @namespace douglaskampl // @version 2.2 // @description Checks world leagues rounds for unusual pairs of results // @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 weirdMatchesStyles https://u18mz.vercel.app/mz/userscript/other/weirdMatches.css // @run-at document-idle // @license MIT // @downloadURL none // ==/UserScript== (function () { 'use strict'; GM_addStyle(GM_getResourceText('weirdMatchesStyles')); 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 for league', NO_DISCREPANCIES: 'No weird matches found!', INVALID_INPUT: 'Please enter a number between 1 and 10', UNPLAYED_MATCHES: 'Warning: Some matches show X - X scores and may not have been played yet' }, DOM: { HEADER_ID: '#header-next-game', BUTTON_CLASS: 'wl-check-button', BUTTON_TEXT: 'WL', BUTTON_TITLE: 'Check World League Rounds' }, REGEX: { GAMES: /[^<]*]+>[^<]*<\/td>[^<]*]+>[^<]*]+>[^<]+<\/a>[^<]*<\/td>[^<]*]+>[^<]*<\/td>[^<]*<\/tr>/g, SCORE: /]*>([^<]+)<\/a>/, LINK: /= 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) { const url = `/ajax.php?p=league&type=${leagueType}&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 extractMatches(round, roundNumbers) { const roundParts = round.split(""); const currentRound = roundParts[0].trim(); const matchesHTML = roundParts[1].split("")[0]; return { currentRound, games: matchesHTML.match(CONSTANTS.REGEX.GAMES) || [] }; } function parseGameData(game) { const teams = game.match(/]*>[^<]*<\/td>/g).map(td => td.replace(/<[^>]+>/g, "").trim()); const score = CONSTANTS.REGEX.SCORE.exec(game)[1].trim(); const link = CONSTANTS.REGEX.LINK.exec(game)[1]; return { teams, score, link }; } 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: [], hasUnplayedMatches: false }; const rounds = html.split(/

/).slice(1); const homeResults = new Map(); const awayResults = new Map(); const discrepancies = []; let hasUnplayedMatches = false; for (const round of rounds) { const { currentRound, games } = extractMatches(round, roundNumbers); if (!games.length) continue; const isHomeRound = currentRound.includes(`Round ${roundNumbers.first}`) || currentRound.includes(`Rodada ${roundNumbers.first}`); const isAwayRound = currentRound.includes(`Round ${roundNumbers.second}`) || currentRound.includes(`Rodada ${roundNumbers.second}`); for (const game of games) { const { teams, score, link } = parseGameData(game); if (CONSTANTS.REGEX.UNPLAYED_SCORE.test(score)) { hasUnplayedMatches = true; continue; } const matchKey = `${teams[0]} vs ${teams[1]}`; const matchData = { team1: teams[0], team2: teams[1], score, link }; if (isHomeRound) homeResults.set(matchKey, { ...matchData, round: roundNumbers.first }); if (isAwayRound) awayResults.set(matchKey, { ...matchData, round: roundNumbers.second }); } } for (const [key, awayMatch] of awayResults) { const homeKey = `${awayMatch.team2} vs ${awayMatch.team1}`; 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, hasUnplayedMatches }; } function createModal(discrepancies, leagueType, minDiff, roundNumbers, hasUnplayedMatches) { const modalHtml = ` `; 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 = `

WL Type

Round Pairs

Enter Min. Goal Diff.

`; 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 header = document.querySelector(CONSTANTS.DOM.HEADER_ID); if (!header) return; const button = document.createElement('button'); button.className = CONSTANTS.DOM.BUTTON_CLASS; button.innerHTML = CONSTANTS.DOM.BUTTON_TEXT; button.title = CONSTANTS.DOM.BUTTON_TITLE; header.appendChild(button); 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 = []; let hasUnplayedMatches = false; 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); hasUnplayedMatches = hasUnplayedMatches || result.hasUnplayedMatches; } spinner.style.display = 'none'; if (allDiscrepancies.length) { createModal(allDiscrepancies, selectedLeagueType, minTotalDiff, roundNumbers, hasUnplayedMatches); } 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(); })();