// ==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
// ==/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);
})();