Greasy Fork

ylOppTactsPreview (MODIFIED)

Shows latest 10 tactics of opponents on scheduled matches page

目前为 2024-12-11 提交的版本。查看 最新版本

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