MZ - Youth Exchange/16

Persists new player data when exchanging youth players

// ==UserScript==
// @name         MZ - Youth Exchange/16
// @namespace    douglaskampl
// @version      3.8
// @description  Persists new player data when exchanging youth players
// @author       Douglas
// @match        https://www.managerzone.com/?p=youth_academy*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=managerzone.com
// @grant        GM_xmlhttpRequest
// @connect      *
// @require      https://cdn.jsdelivr.net/npm/sweetalert2@11
// @run-at       document-idle
// @license      MIT
// ==/UserScript==

(function () {
    'use strict';

    const CONFIG = {
        USERS: {
            douglaskampl: 'Brazil', /* You can put your username and country here. */
        },
        YOUTH_EXCHANGE_PLAYER_AGE: 16,
        ENDPOINTS: {
            WORKER: 'https://youth-exchange-worker.douglasdotv.workers.dev/',
            SCOUT: 'https://www.managerzone.com/ajax.php?p=players&sub=scout_report&pid=null&sport=soccer',
            MANAGER: 'http://www.managerzone.com/xml/manager_data.php',
            COUNTRIES: 'https://u18mz.vercel.app/mz/countriesData.json'
        },
        SKILLS: [
            'speed', 'stamina', 'playIntelligence', 'passing', 'shooting',
            'heading', 'keeping', 'ballControl', 'tackling', 'aerialPassing', 'setPlays',
            'experience', 'form'
        ]
    };

    const state = {
        username: 'Unknown',
        nationality: 'Unknown',
        storedPlayerData: null,
        dataReady: false,
        lastPlayerID: null,
        currentSeason: document.querySelector('#header-stats-wrapper h5.linked')?.textContent.match(/(\d+)/)?.[1],
        countries: null
    };

    function showToast(color, message) {
        const icons = { blue: 'info', green: 'success', orange: 'warning', red: 'error' };
        Swal.fire({
            toast: true,
            position: 'bottom-right',
            iconColor: color,
            icon: icons[color] || 'error',
            title: message,
            showConfirmButton: false,
            timer: 3000,
            background: color,
            color: 'white'
        });
    }

    async function request(url, options = {}) {
        return new Promise((resolve, reject) => {
            GM_xmlhttpRequest({
                method: options.method || 'GET',
                url,
                headers: {
                    'Content-Type': 'application/json',
                    'Origin': 'https://www.managerzone.com',
                    ...options.headers
                },
                data: options.data,
                onload: resolve,
                onerror: reject
            });
        });
    }

    function handleApiError(error, message) {
        console.error(message, error);
        showToast('red', message);
        return null;
    }

    async function fetchCountries() {
        if (state.countries) return state.countries;

        try {
            const response = await request(CONFIG.ENDPOINTS.COUNTRIES);
            const countries = JSON.parse(response.responseText);
            state.countries = new Map(
                countries.map(country => [country.code, country.name])
            );
            return state.countries;
        } catch (error) {
            handleApiError(error, 'Failed to fetch countries data');
            return new Map();
        }
    }

    async function getCountryName(countryCode) {
        if (!countryCode) return null;

        const countries = await fetchCountries();
        return countries.get(countryCode) || countryCode;
    }

    function getPlayerData(container) {
        if (!container) return null;
        const stats = {};
        CONFIG.SKILLS.forEach((skill, index) => {
            const row = container.querySelectorAll('.player_skills tr')[index];
            const val = row?.querySelector('.skillval span')?.textContent.trim();
            stats[skill] = val ? parseInt(val, 10) : 0;
        });
        const id = container.querySelector('.player_id_span')?.textContent;
        const name = container.querySelector('.player_name')?.textContent;
        const totalBalls = container.querySelectorAll('tbody > tr')[6]?.querySelector('.bold')?.textContent;
        if (!id || !name) return null;
        return { id, name, totalBalls, stats };
    }

    function extractSkills(doc) {
        const dataList = doc.querySelectorAll('dl > dd');
        const getSkills = (c) =>
            Array.from(c.querySelectorAll('li > span:last-child')).map(span => span.textContent.trim());
        const [hpSkills = [], lpSkills = []] = [dataList[0], dataList[1]].map(el => el ? getSkills(el) : []);
        return {
            hp: dataList[0]?.querySelectorAll('.lit')?.length || 0,
            lp: dataList[1]?.querySelectorAll('.lit')?.length || 0,
            trainingSpeed: dataList[2]?.querySelectorAll('.lit')?.length || 0,
            firstHpSkill: hpSkills[0] || '',
            secondHpSkill: hpSkills[1] || '',
            firstLpSkill: lpSkills[0] || '',
            secondLpSkill: lpSkills[1] || ''
        };
    }

    async function extractPlayerData() {
        const container = document.getElementById('thePlayers_x');
        const currentID = container?.querySelector('.player_id_span')?.textContent;
        if (!container || (currentID === state.lastPlayerID && state.storedPlayerData)) return;
        state.lastPlayerID = currentID;
        state.dataReady = false;
        showToast('orange', 'Extracting player data');
        try {
            const response = await request(CONFIG.ENDPOINTS.SCOUT);
            if (!response || !response.responseText) return;
            const cleanText = response.responseText.replace(/Trzxyvopaxis/g, '');
            const doc = new DOMParser().parseFromString(cleanText, 'text/html');
            const basicData = getPlayerData(container);
            if (!basicData) return;
            const skills = extractSkills(doc);
            state.storedPlayerData = {
                ...basicData,
                ...skills,
                age: CONFIG.YOUTH_EXCHANGE_PLAYER_AGE,
                country: state.nationality,
                owner: state.username,
                season: parseInt(state.currentSeason, 10)
            };
            state.dataReady = true;
            showToast('green', 'Player data is ready for submission.');
        } catch (error) {
            handleApiError(error, 'Failed to retrieve player data');
        }
    }

    async function sendData() {
        if (!state.dataReady) {
            showToast('red', 'Data is not ready yet. Please wait.');
            return false;
        }
        try {
            showToast('blue', 'Sending data...');
            const payload = { ...state.storedPlayerData };
            const response = await request(CONFIG.ENDPOINTS.WORKER, {
                method: 'POST',
                data: JSON.stringify(payload)
            });
            if (response.status >= 200 && response.status < 300) {
                showToast('green', 'Data sent successfully.');
                return true;
            }
            if (response.status === 409) {
                showToast('orange', 'This player is already submitted. Data was not sent.');
                return false;
            }
            throw new Error(`HTTP ${response.status}: ${response.responseText}`);
        } catch (error) {
            handleApiError(error, 'Failed to send data');
            return false;
        }
    }

    function attachButtonListeners() {
        ['#exchange_button', '#discard_youth_button'].forEach(selector => {
            const button = document.querySelector(selector);
            if (button && !button.dataset.listenerAdded) {
                button.addEventListener('click', async (e) => {
                    if (!state.dataReady) {
                        e.preventDefault();
                        showToast('red', 'Data is not ready yet. Please wait before proceeding.');
                        return;
                    }
                    await sendData();
                });
                button.dataset.listenerAdded = true;
            }
        });
    }

    function observeContainer() {
        const container = document.getElementById('thePlayers_x');

        const handleContainerChanges = (mutations) => {
            const container = document.getElementById('thePlayers_x');
            if (container) {
                const currentID = container.querySelector('.player_id_span')?.textContent;
                if (currentID && currentID !== state.lastPlayerID) {
                    extractPlayerData();
                }
                attachButtonListeners();
            }
        };

        if (container) {
            const observer = new MutationObserver(handleContainerChanges);
            observer.observe(container, { childList: true, subtree: true });
            handleContainerChanges();
        } else {
            const bodyObserver = new MutationObserver((mutations) => {
                const newContainer = document.getElementById('thePlayers_x');
                if (newContainer) {
                    bodyObserver.disconnect();
                    const observer = new MutationObserver(handleContainerChanges);
                    observer.observe(newContainer, { childList: true, subtree: true });
                    handleContainerChanges();
                }
            });
            bodyObserver.observe(document.body, { childList: true, subtree: true });
        }
    }

    async function init() {
        state.username = document.getElementById('header-username')?.textContent || 'Unknown';
        state.nationality = CONFIG.USERS[state.username] || 'Unknown';

        if (!state.nationality || state.nationality === 'Unknown') {
            try {
                const response = await request(`${CONFIG.ENDPOINTS.MANAGER}?sport_id=1&username=${state.username}`);
                const doc = new DOMParser().parseFromString(response.responseText, 'text/xml');
                const countryCode = doc.querySelector('UserData')?.getAttribute('countryShortname');

                if (countryCode) {
                    state.nationality = await getCountryName(countryCode) || 'Unknown';
                }
            } catch (error) {
                handleApiError(error, 'Failed to fetch nationality');
            }
        }

        observeContainer();
    }

    init();
})();