您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Shows the latest tactics used by an upcoming opponent from the scheduled matches page
- // ==UserScript==
- // @name ylOppTacticsPreview (Modified)
- // @namespace douglaskampl
- // @version 4.8
- // @description Shows the latest tactics used by an upcoming opponent from the 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
- // @grant GM_getResourceText
- // @require https://cdnjs.cloudflare.com/ajax/libs/spin.js/2.3.2/spin.min.js
- // @resource oppTacticsPreviewStyles https://br18.org/mz/userscript/other/oppTacticsPreview.css
- // @run-at document-idle
- // @license MIT
- // ==/UserScript==
- (function () {
- 'use strict';
- GM_addStyle(GM_getResourceText('oppTacticsPreviewStyles'));
- const CONSTANTS = {
- MAX_OPPONENT_TACTICS: 10,
- SELECTORS: {
- FIXTURES_LIST: '#fixtures-results-list-wrapper',
- STATS_XENTE: '#legendDiv',
- ELO_SCHEDULED: '#eloScheduledSelect',
- HOME_TEAM: '.home-team-column.flex-grow-1',
- SELECT_WRAPPER: 'dd.set-default-wrapper'
- },
- MATCH_TYPES: ['u18', 'u21', 'u23', 'no_restriction'],
- MATCH_STATS_URL: (matchId) => 'https://www.managerzone.com/matchviewer/getMatchFiles.php?type=stats&mid=' + matchId + '&sport=soccer'
- };
- let ourTeamName = null;
- let selectedMatchTypeG = '';
- let currentTidValue = '';
- let currentOpponent = '';
- let currentOpponentTid = '';
- let lastMagnifierRect = null;
- let spinnerInstance = null;
- const observer = new MutationObserver(() => {
- insertIconsAndListeners();
- });
- function startObserving() {
- const fixturesList = document.querySelector(CONSTANTS.SELECTORS.FIXTURES_LIST);
- if (fixturesList) {
- observer.observe(fixturesList, {
- childList: true,
- subtree: true
- });
- }
- }
- function showLoadingSpinner() {
- if (spinnerInstance) return;
- const spinnerContainer = document.createElement('div');
- spinnerContainer.id = 'spinjs-overlay';
- spinnerContainer.style.position = 'fixed';
- spinnerContainer.style.top = '0';
- spinnerContainer.style.left = '0';
- spinnerContainer.style.width = '100vw';
- spinnerContainer.style.height = '100vh';
- spinnerContainer.style.background = 'rgba(0, 0, 0, 0.4)';
- spinnerContainer.style.zIndex = '999999';
- document.body.appendChild(spinnerContainer);
- const opts = {
- lines: 12,
- length: 16,
- width: 6,
- radius: 20,
- scale: 1,
- corners: 1,
- color: '#FFC0CB',
- opacity: 0.25,
- rotate: 0,
- direction: 1,
- speed: 1,
- trail: 60,
- fps: 20,
- zIndex: 2e9,
- className: 'spinner',
- top: '50%',
- left: '50%',
- shadow: false,
- hwaccel: false,
- position: 'absolute'
- };
- spinnerInstance = new Spinner(opts).spin(spinnerContainer);
- }
- function hideLoadingSpinner() {
- if (spinnerInstance) {
- spinnerInstance.stop();
- spinnerInstance = null;
- }
- const spinnerContainer = document.getElementById('spinjs-overlay');
- if (spinnerContainer) spinnerContainer.remove();
- }
- async function fetchLatestTactics(tidValue, opponent, matchType, opponentTid) {
- selectedMatchTypeG = matchType;
- currentTidValue = tidValue;
- currentOpponent = opponent;
- currentOpponentTid = opponentTid;
- try {
- showLoadingSpinner();
- 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 (_) {
- } finally {
- hideLoadingSpinner();
- }
- }
- function processTacticsData(data) {
- const parser = new DOMParser();
- const htmlDocument = parser.parseFromString(data.list, 'text/html');
- const scoreShownLinks = htmlDocument.querySelectorAll('a.score-shown');
- const container = createTacticsContainer(selectedMatchTypeG, currentOpponent);
- document.body.appendChild(container);
- const listWrapper = container.querySelector('.tactics-list');
- if (scoreShownLinks.length === 0) {
- const message = document.createElement('div');
- message.style.textAlign = 'center';
- message.style.color = '#555';
- message.style.fontSize = '12px';
- message.style.padding = '10px';
- message.textContent = 'No recent tactics found for the selected match type.';
- listWrapper.appendChild(message);
- container.classList.add('fade-in');
- return;
- }
- scoreShownLinks.forEach((link, index) => {
- if (index >= CONSTANTS.MAX_OPPONENT_TACTICS) return;
- const dl = link.closest('dl');
- const theScore = link.textContent.trim();
- const homeTeamName = dl.querySelector('.home-team-column .full-name')?.textContent.trim() || 'Home';
- const awayTeamName = dl.querySelector('.away-team-column .full-name')?.textContent.trim() || 'Away';
- const homeTeamLink = dl.querySelector('.home-team-column a.clippable');
- const awayTeamLink = dl.querySelector('.away-team-column a.clippable');
- let homeTid = null, awayTid = null;
- if (homeTeamLink) {
- homeTid = new URLSearchParams(new URL(homeTeamLink.href, location.href).search).get('tid');
- }
- if (awayTeamLink) {
- awayTid = new URLSearchParams(new URL(awayTeamLink.href, location.href).search).get('tid');
- }
- let homeGoals = 0;
- let awayGoals = 0;
- if (theScore.includes('-')) {
- const parts = theScore.split('-').map(x => x.trim());
- if (parts.length === 2) {
- homeGoals = parseInt(parts[0]) || 0;
- awayGoals = parseInt(parts[1]) || 0;
- }
- }
- const mid = extractMidFromUrl(link.href);
- const tacticUrl = 'https://www.managerzone.com/dynimg/pitch.php?match_id=' + mid;
- const resultUrl = 'https://www.managerzone.com/?p=match&sub=result&mid=' + mid;
- const opponentIsHome = (homeTid === currentTidValue);
- const canvas = createCanvasWithReplacedColors(tacticUrl, opponentIsHome);
- const item = document.createElement('div');
- item.className = 'tactic-item';
- let opponentGoals = opponentIsHome ? homeGoals : awayGoals;
- let otherGoals = opponentIsHome ? awayGoals : homeGoals;
- if (opponentGoals > otherGoals) {
- item.style.backgroundColor = '#daf8da';
- } else if (opponentGoals < otherGoals) {
- item.style.backgroundColor = '#f8dada';
- } else {
- item.style.backgroundColor = '#f0f0f0';
- }
- const linkA = document.createElement('a');
- linkA.href = resultUrl;
- linkA.target = '_blank';
- linkA.className = 'tactic-link';
- linkA.style.color = '#333';
- linkA.style.textDecoration = 'none';
- linkA.appendChild(canvas);
- const scoreP = document.createElement('p');
- scoreP.textContent = homeTeamName + ' ' + theScore + ' ' + awayTeamName;
- linkA.appendChild(scoreP);
- item.appendChild(linkA);
- addPlaystyleHover(mid, canvas, currentOpponentTid);
- listWrapper.appendChild(item);
- });
- container.classList.add('fade-in');
- }
- function showMatchTypeModal(tidValue, opponent, sourceElement, opponentTid) {
- const existingModal = document.getElementById('match-type-modal');
- if (existingModal) {
- fadeOutAndRemove(existingModal);
- }
- const modal = document.createElement('div');
- modal.id = 'match-type-modal';
- modal.classList.add('fade-in');
- const label = document.createElement('label');
- label.textContent = 'Select match type:';
- modal.appendChild(label);
- const select = document.createElement('select');
- CONSTANTS.MATCH_TYPES.forEach(type => {
- const option = document.createElement('option');
- option.value = type;
- let labelText;
- if (type === 'no_restriction') {
- labelText = 'Senior';
- } else {
- labelText = type.replace('_', ' ').toUpperCase();
- }
- option.textContent = labelText;
- select.appendChild(option);
- });
- modal.appendChild(select);
- const btnGroup = document.createElement('div');
- btnGroup.className = 'btn-group';
- const okButton = document.createElement('button');
- okButton.textContent = 'OK';
- okButton.onclick = () => {
- fadeOutAndRemove(modal);
- fetchLatestTactics(tidValue, opponent, select.value, opponentTid);
- };
- const cancelButton = document.createElement('button');
- cancelButton.textContent = 'Cancel';
- cancelButton.onclick = () => fadeOutAndRemove(modal);
- btnGroup.append(okButton, cancelButton);
- modal.appendChild(btnGroup);
- document.body.appendChild(modal);
- const rect = sourceElement.getBoundingClientRect();
- lastMagnifierRect = {
- left: window.scrollX + rect.left,
- top: window.scrollY + rect.top,
- bottom: window.scrollY + rect.bottom,
- width: rect.width,
- height: rect.height
- };
- modal.style.position = 'absolute';
- modal.style.top = (lastMagnifierRect.bottom + 5) + 'px';
- modal.style.left = lastMagnifierRect.left + 'px';
- }
- function createTacticsContainer(matchType, opponent) {
- const existingContainer = document.getElementById('tactics-container');
- if (existingContainer) {
- fadeOutAndRemove(existingContainer);
- }
- const container = document.createElement('div');
- container.id = 'tactics-container';
- container.className = 'tactics-container';
- const header = document.createElement('div');
- header.className = 'tactics-header';
- const title = document.createElement('div');
- title.className = 'match-info-text';
- const modalTitleMatchType = matchType === 'no_restriction' ? 'Senior' : matchType.replace('_', ' ').toUpperCase();
- title.innerHTML = '<div class="title-main">' + (opponent ? opponent : '') + ' (' + modalTitleMatchType + ')</div><div class="title-subtitle">' + opponent + '\'s latest tactics are represented by black dots with white outlines: <span style="display:inline-block;width:6px;height:6px;background:#000;border:1px solid #fff;margin-left:2px;vertical-align:middle;"></span></div>';
- header.appendChild(title);
- const closeButton = document.createElement('button');
- closeButton.className = 'close-button';
- closeButton.textContent = '×';
- closeButton.onclick = () => fadeOutAndRemove(container);
- header.appendChild(closeButton);
- container.appendChild(header);
- const listWrapper = document.createElement('div');
- listWrapper.className = 'tactics-list';
- container.appendChild(listWrapper);
- document.body.appendChild(container);
- if (lastMagnifierRect) {
- const modalWidth = 420;
- const leftPos = lastMagnifierRect.left + (lastMagnifierRect.width / 2) - (modalWidth / 2);
- const topPos = lastMagnifierRect.bottom - 350;
- container.style.position = 'absolute';
- container.style.top = topPos + 'px';
- container.style.left = leftPos + 'px';
- container.style.transform = 'none';
- }
- return container;
- }
- function fadeOutAndRemove(el) {
- el.classList.remove('fade-in');
- el.classList.add('fade-out');
- setTimeout(() => {
- if (el.parentNode) el.parentNode.removeChild(el);
- }, 200);
- }
- function identifyUserTeamName() {
- const ddRows = document.querySelectorAll('dd.odd');
- const countMap = new Map();
- let totalMatches = 0;
- ddRows.forEach(dd => {
- const homeName = dd.querySelector('.home-team-column .full-name')?.textContent.trim();
- const awayName = dd.querySelector('.away-team-column .full-name')?.textContent.trim();
- if (homeName && awayName) {
- totalMatches++;
- countMap.set(homeName, (countMap.get(homeName) || 0) + 1);
- countMap.set(awayName, (countMap.get(awayName) || 0) + 1);
- }
- });
- for (const [name, count] of countMap.entries()) {
- if (count === totalMatches) {
- return name;
- }
- }
- return null;
- }
- function insertIconsAndListeners() {
- ourTeamName = ourTeamName || identifyUserTeamName();
- if (!ourTeamName) return;
- document.querySelectorAll('dd.odd').forEach(dd => {
- const selectWrapper = dd.querySelector(CONSTANTS.SELECTORS.SELECT_WRAPPER);
- if (selectWrapper) {
- const select = selectWrapper.querySelector('select');
- if (select && !selectWrapper.querySelector('.magnifier-icon')) {
- const homeTeamName = dd.querySelector('.home-team-column .full-name')?.textContent.trim();
- const awayTeamName = dd.querySelector('.away-team-column .full-name')?.textContent.trim();
- let opponentName = null;
- let opponentTid = null;
- const homeTeamLink = dd.querySelector('.home-team-column a.clippable');
- const awayTeamLink = dd.querySelector('.away-team-column a.clippable');
- let homeTid = null, awayTid = null;
- if (homeTeamLink) {
- homeTid = new URLSearchParams(new URL(homeTeamLink.href, location.href).search).get('tid');
- }
- if (awayTeamLink) {
- awayTid = new URLSearchParams(new URL(awayTeamLink.href, location.href).search).get('tid');
- }
- if (homeTeamName === ourTeamName && awayTeamName && awayTid) {
- opponentName = awayTeamName;
- opponentTid = awayTid;
- } else if (awayTeamName === ourTeamName && homeTeamName && homeTid) {
- opponentName = homeTeamName;
- opponentTid = homeTid;
- } else {
- return;
- }
- if (!opponentTid) return;
- const iconWrapper = document.createElement('span');
- iconWrapper.className = 'magnifier-icon';
- iconWrapper.dataset.tid = opponentTid;
- iconWrapper.dataset.opponent = opponentName;
- iconWrapper.title = 'Click to check latest tactics for this opponent';
- iconWrapper.textContent = '🔍';
- select.insertAdjacentElement('afterend', iconWrapper);
- }
- }
- });
- }
- function extractMidFromUrl(url) {
- return new URLSearchParams(new URL(url, location.href).search).get('mid');
- }
- function processImage(context, canvas, image, opponentIsHome) {
- if (opponentIsHome) {
- 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);
- const data = imageData.data;
- const darkGreen = { r: 0, g: 100, b: 0 };
- for (let i = 0; i < data.length; i += 4) {
- const r = data[i];
- const g = data[i + 1];
- const b = data[i + 2];
- const isBlack = (r < 30 && g < 30 && b < 30);
- const isYellow = (r > 200 && g > 200 && b < 100);
- if (opponentIsHome) {
- if (isYellow) {
- data[i] = 0; data[i + 1] = 0; data[i + 2] = 0;
- } else if (isBlack) {
- data[i] = darkGreen.r; data[i + 1] = darkGreen.g; data[i + 2] = darkGreen.b;
- }
- } else {
- if (isBlack) {
- data[i] = 0; data[i + 1] = 0; data[i + 2] = 0;
- } else if (isYellow) {
- data[i] = darkGreen.r; data[i + 1] = darkGreen.g; data[i + 2] = darkGreen.b;
- }
- }
- }
- const tempData = new Uint8ClampedArray(data);
- for (let y = 0; y < canvas.height; y++) {
- for (let x = 0; x < canvas.width; x++) {
- const i = (y * canvas.width + x) * 4;
- if (data[i] === 0 && data[i + 1] === 0 && data[i + 2] === 0) {
- for (let dy = -1; dy <= 1; dy++) {
- for (let dx = -1; dx <= 1; dx++) {
- if (dx === 0 && dy === 0) continue;
- const nx = x + dx;
- const ny = y + dy;
- if (nx >= 0 && nx < canvas.width && ny >= 0 && ny < canvas.height) {
- const ni = (ny * canvas.width + nx) * 4;
- if (!(data[ni] === 0 && data[ni + 1] === 0 && data[ni + 2] === 0)) {
- tempData[ni] = 255; tempData[ni + 1] = 255; tempData[ni + 2] = 255;
- }
- }
- }
- }
- }
- }
- }
- context.putImageData(new ImageData(tempData, canvas.width, canvas.height), 0, 0);
- }
- function createCanvas(width, height) {
- const canvas = document.createElement('canvas');
- canvas.width = width;
- canvas.height = height;
- canvas.style.pointerEvents = 'auto';
- return canvas;
- }
- function createCanvasWithReplacedColors(imageUrl, opponentIsHome) {
- const canvas = createCanvas(150, 200);
- const context = canvas.getContext('2d');
- const image = new Image();
- image.crossOrigin = 'Anonymous';
- image.onload = () => processImage(context, canvas, image, opponentIsHome);
- image.src = imageUrl;
- return canvas;
- }
- async function fetchPlaystyleChanges(mid, opponentTid) {
- try {
- const res = await fetch(CONSTANTS.MATCH_STATS_URL(mid));
- const txt = await res.text();
- const parser = new DOMParser();
- const xml = parser.parseFromString(txt, 'text/xml');
- const tactics = xml.querySelectorAll('Events Tactic');
- const out = [];
- tactics.forEach(n => {
- if (n.getAttribute('teamId') !== opponentTid) {
- return;
- }
- const tType = n.getAttribute('type');
- if (tType === 'playstyle' || tType === 'aggression' || tType === 'tactic') {
- const time = n.getAttribute('time');
- const setting = n.getAttribute('new_setting');
- out.push('Minute ' + time + ': ' + tType + ' -> ' + setting);
- }
- });
- return out.length ? out.join('<br>') : 'No playstyle, mentality or pressing changes detected';
- } catch (_) {
- return 'No info';
- }
- }
- function addPlaystyleHover(mid, canvas, opponentTid) {
- const tooltip = document.createElement('div');
- tooltip.style.position = 'absolute';
- tooltip.style.background = '#333';
- tooltip.style.color = '#fff';
- tooltip.style.padding = '5px';
- tooltip.style.borderRadius = '3px';
- tooltip.style.fontSize = '12px';
- tooltip.style.display = 'none';
- tooltip.style.zIndex = '9999';
- document.body.appendChild(tooltip);
- canvas.addEventListener('mouseover', async (ev) => {
- tooltip.style.display = 'block';
- tooltip.style.top = ev.pageY + 15 + 'px';
- tooltip.style.left = ev.pageX + 5 + 'px';
- tooltip.innerHTML = 'Loading...';
- const info = await fetchPlaystyleChanges(mid, opponentTid);
- tooltip.innerHTML = info;
- });
- canvas.addEventListener('mousemove', (ev) => {
- tooltip.style.top = ev.pageY + 15 + 'px';
- tooltip.style.left = ev.pageX + 5 + 'px';
- });
- canvas.addEventListener('mouseout', () => {
- tooltip.style.display = 'none';
- });
- }
- 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);
- }
- function handleClickEvents(e) {
- const clickedMagnifier = e.target.closest('.magnifier-icon');
- if (clickedMagnifier) {
- e.preventDefault();
- e.stopPropagation();
- const tidValue = clickedMagnifier.dataset.tid;
- const opponent = clickedMagnifier.dataset.opponent;
- if (!tidValue) return;
- showMatchTypeModal(ourTeamName === opponent ? ourTeamName : tidValue, opponent, clickedMagnifier, tidValue);
- return;
- }
- const tacticsContainer = document.getElementById('tactics-container');
- const matchTypeModal = document.getElementById('match-type-modal');
- if (tacticsContainer && !tacticsContainer.contains(e.target) && !clickedMagnifier) {
- fadeOutAndRemove(tacticsContainer);
- }
- if (matchTypeModal && !matchTypeModal.contains(e.target) && !clickedMagnifier) {
- fadeOutAndRemove(matchTypeModal);
- }
- }
- function run() {
- const statsXenteRunning = document.querySelector(CONSTANTS.SELECTORS.STATS_XENTE);
- const eloScheduledSelected = document.querySelector(CONSTANTS.SELECTORS.ELO_SCHEDULED)?.checked;
- if (statsXenteRunning && eloScheduledSelected) {
- waitForEloValues();
- } else {
- insertIconsAndListeners();
- }
- startObserving();
- }
- document.body.addEventListener('click', handleClickEvents);
- run();
- })();