// ==UserScript== // @name ylOppTactsPreview (MODIFIED) // @namespace douglaskampl // @version 1.0 // @description Shows latest 10 tactics of opponents on scheduled matches page // @author kostrzak16 feat. Douglas and xente // @match https://www.managerzone.com/?p=match&sub=scheduled // @icon https://www.google.com/s2/favicons?sz=64&domain=managerzone.com // @grant GM_addStyle // @license MIT // @downloadURL none // ==/UserScript== GM_addStyle(` .magnifier-icon { cursor: pointer !important; font-size: 12px !important; margin-left: 5px !important; z-index: 100 !important; pointer-events: auto !important; } .modal-select { padding: 10px; border-radius: 6px; border: 1px solid #ccc; background: #f9f9f9; margin-right: 10px; font-size: 14px; min-width: 180px; transition: all 0.3s ease-in-out; } .modal-select:focus { outline: none; border-color: #4a90e2; box-shadow: 0 0 5px rgba(74, 144, 226, 0.5); } .modal-button { padding: 10px 20px; border-radius: 6px; border: none; background: linear-gradient(90deg, #4a90e2, #007bff); color: white; cursor: pointer; margin: 0 5px; font-size: 14px; transition: background 0.3s ease, transform 0.2s; } .modal-button:hover { background: linear-gradient(90deg, #357abd, #0056b3); transform: scale(1.05); } .modal-button.cancel { background: linear-gradient(90deg, #e74c3c, #c0392b); } .modal-button.cancel:hover { background: linear-gradient(90deg, #c0392b, #a8231c); transform: scale(1.05); } #match-type-modal { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%) scale(0.95); background: white; padding: 20px; border-radius: 8px; box-shadow: 0 4px 6px rgba(0,0,0,0.1); z-index: 1000000; text-align: center; display: flex; align-items: center; justify-content: center; gap: 10px; width: auto; animation: modal-fade-in 0.3s ease forwards; } @keyframes modal-fade-in { from { opacity: 0; transform: translate(-50%, -50%) scale(0.9); } to { opacity: 1; transform: translate(-50%, -50%) scale(1); } } .tactics-container { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px; padding: 15px; background: rgba(255, 255, 255, 0.98); border-radius: 8px; box-shadow: 0 4px 6px rgba(0,0,0,0.1); max-height: 90vh; overflow-y: auto; width: 350px; z-index: 999999; animation: modal-fade-in 0.3s ease forwards; } .tactics-container canvas { border-radius: 4px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); transition: transform 0.2s; } .tactics-container canvas:hover { transform: scale(1.05); } .close-button { position: absolute; top: 10px; right: 10px; padding: 8px 12px; border-radius: 4px; background: #e74c3c; color: white; border: none; cursor: pointer; font-size: 14px; z-index: 1000000; transition: transform 0.2s; } .close-button:hover { background: #c0392b; transform: scale(1.1); } `); (function() { "use strict"; const CONSTANTS = { MAX_TACTICS: 10, SELECTORS: { LINKS: 'a[href*="tid"].clippable', SCORE_SHOWN: 'a.score-shown', FIXTURES_LIST: '#fixtures-results-list-wrapper', STATS_XENTE: '#legendDiv', ELO_SCHEDULED: '#eloScheduledSelect', HOME_TEAM: '.home-team-column.flex-grow-1' }, MATCH_TYPES: ['u18', 'u21', 'u23', 'no_restriction'], COLORS: { GREEN: [64, 154, 64], BLACK: [0, 0, 0] } }; const observer = new MutationObserver(() => { insertIconsAndListeners(); }); function startObserving() { const fixturesList = document.querySelector(CONSTANTS.SELECTORS.FIXTURES_LIST); if (fixturesList) { observer.observe(fixturesList, { childList: true, subtree: true }); } } async function fetchLatestTactics(tidValue, matchType) { try { const response = await fetch( "https://www.managerzone.com/ajax.php?p=matches&sub=list&sport=soccer", { method: 'POST', headers: { 'Accept': 'application/json', 'Content-Type': 'application/x-www-form-urlencoded' }, body: `type=played&hidescore=false&tid1=${tidValue}&offset=&selectType=${matchType}&limit=default`, credentials: 'include' } ); if (!response.ok) throw new Error('Network response was not ok'); const data = await response.json(); processTacticsData(data); } catch (error) { return; } } function processTacticsData(data) { const parser = new DOMParser(); const htmlDocument = parser.parseFromString(data.list, 'text/html'); const scoreShownLinks = htmlDocument.querySelectorAll(CONSTANTS.SELECTORS.SCORE_SHOWN); const container = createTacticsContainer(); document.body.appendChild(container); if (scoreShownLinks.length === 0) { const message = document.createElement('div'); message.style.textAlign = 'center'; message.style.color = '#555'; message.style.fontSize = '16px'; message.style.padding = '20px'; message.textContent = "No recent tactics found for the selected level. Your opponent clearly doesn't care."; container.appendChild(message); return; } Array.from(scoreShownLinks) .slice(0, CONSTANTS.MAX_TACTICS) .forEach(link => { const isHome = checkNextDdForStrong(link); const mid = extractMidFromUrl(link.href); const canvas = isHome ? createCanvasWithReplacedColors(`https://www.managerzone.com/dynimg/pitch.php?match_id=${mid}`) : createCanvasWithModifiedColorsAndRotation(`https://www.managerzone.com/dynimg/pitch.php?match_id=${mid}`); container.appendChild(canvas); }); } function createTacticsContainer() { const existingContainer = document.getElementById('tactics-container'); if (existingContainer) existingContainer.remove(); const container = document.createElement('div'); container.id = 'tactics-container'; container.className = 'tactics-container'; const closeButton = document.createElement('button'); closeButton.className = 'close-button'; closeButton.textContent = '×'; closeButton.onclick = () => container.remove(); container.appendChild(closeButton); return container; } function showMatchTypeSelector(tidValue) { const existingModal = document.getElementById('match-type-modal'); if (existingModal) existingModal.remove(); const modal = document.createElement('div'); modal.id = 'match-type-modal'; const select = document.createElement('select'); select.className = 'modal-select'; CONSTANTS.MATCH_TYPES.forEach(type => { const option = document.createElement('option'); option.value = type; option.textContent = type.replace('_', ' ').toUpperCase(); select.appendChild(option); }); const buttonsContainer = document.createElement('div'); buttonsContainer.style.marginTop = '15px'; const okButton = document.createElement('button'); okButton.className = 'modal-button'; okButton.textContent = 'OK'; okButton.onclick = () => { modal.remove(); fetchLatestTactics(tidValue, select.value); }; const cancelButton = document.createElement('button'); cancelButton.className = 'modal-button cancel'; cancelButton.textContent = 'Cancel'; cancelButton.onclick = () => modal.remove(); buttonsContainer.append(okButton, cancelButton); modal.append(select, buttonsContainer); document.body.appendChild(modal); } function insertIconsAndListeners() { document.querySelectorAll(CONSTANTS.SELECTORS.LINKS).forEach(link => { if (link.parentNode.querySelector('.magnifier-icon')) return; const icon = document.createElement('span'); icon.textContent = '🔍'; icon.className = 'magnifier-icon'; link.parentNode.insertBefore(icon, link.nextSibling); }); } function extractMidFromUrl(url) { return new URLSearchParams(new URL(url).search).get('mid'); } function checkNextDdForStrong(element) { const dd = element.closest('dd'); return dd?.nextElementSibling?.querySelector('strong') ? true : false; } function createCanvas(width, height) { const canvas = document.createElement('canvas'); canvas.width = width; canvas.height = height; canvas.style.pointerEvents = 'auto'; return canvas; } function createCanvasWithReplacedColors(imageUrl) { const canvas = createCanvas(150, 200); const context = canvas.getContext('2d'); const image = new Image(); image.crossOrigin = 'Anonymous'; image.onload = function () { context.drawImage(image, 0, 0, canvas.width, canvas.height); const imageData = context.getImageData(0, 0, canvas.width, canvas.height); for (let i = 0; i < imageData.data.length; i += 4) { if (isYellowPixel(imageData.data.slice(i, i + 3))) { setPixelColor(imageData.data, i, CONSTANTS.COLORS.GREEN); } } context.putImageData(imageData, 0, 0); }; image.src = imageUrl; return canvas; } function createCanvasWithModifiedColorsAndRotation(imageUrl) { const canvas = createCanvas(150, 200); const context = canvas.getContext('2d'); const image = new Image(); image.crossOrigin = 'Anonymous'; image.onload = function () { context.translate(canvas.width / 2, canvas.height / 2); context.rotate(Math.PI); context.translate(-canvas.width / 2, -canvas.height / 2); context.drawImage(image, 0, 0, canvas.width, canvas.height); const imageData = context.getImageData(0, 0, canvas.width, canvas.height); for (let i = 0; i < imageData.data.length; i += 4) { const [r, g, b] = imageData.data.slice(i, i + 3); if (r === 0 && g === 0 && b === 0) { setPixelColor(imageData.data, i, CONSTANTS.COLORS.GREEN); } else if (isYellowPixel([r, g, b])) { setPixelColor(imageData.data, i, CONSTANTS.COLORS.BLACK); } } context.putImageData(imageData, 0, 0); }; image.src = imageUrl; return canvas; } function isYellowPixel([r, g, b]) { return r > 200 && g > 200 && b < 100; } function setPixelColor(data, index, [r, g, b]) { data[index] = r; data[index + 1] = g; data[index + 2] = b; } document.body.addEventListener('click', (e) => { if (e.target?.classList.contains('magnifier-icon')) { e.preventDefault(); e.stopPropagation(); const link = e.target.previousSibling; if (!link?.href) return; const tidValue = new URLSearchParams(new URL(link.href).search).get('tid'); showMatchTypeSelector(tidValue); } }); function initialize() { const statsXenteRunning = document.querySelector(CONSTANTS.SELECTORS.STATS_XENTE); const eloScheduledSelected = document.querySelector(CONSTANTS.SELECTORS.ELO_SCHEDULED)?.checked; if (statsXenteRunning && eloScheduledSelected) { waitForEloValues(); } else { insertIconsAndListeners(); } startObserving(); } function waitForEloValues() { const interval = setInterval(() => { const elements = document.querySelectorAll(CONSTANTS.SELECTORS.HOME_TEAM); if (elements.length > 0 && elements[elements.length - 1]?.innerHTML.includes('br')) { clearInterval(interval); insertIconsAndListeners(); } }, 100); setTimeout(() => { clearInterval(interval); insertIconsAndListeners(); }, 1500); } setTimeout(initialize, 500); })();