Greasy Fork

Greasy Fork is available in English.

MZ - NT Player Search

Searches for players who match specific requirements

当前为 2025-02-17 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         MZ - NT Player Search
// @namespace    douglaskampl
// @version      2.0
// @description  Searches for players who match specific requirements
// @author       Douglas Vieira
// @match        https://www.managerzone.com/?p=national_teams&type=senior
// @match        https://www.managerzone.com/?p=national_teams&type=u21
// @icon         https://yt3.googleusercontent.com/ytc/AIdro_mDHaJkwjCgyINFM7cdUV2dWPPnL9Q58vUsrhOmRqkatg=s160-c-k-c0x00ffffff-no-rj
// @grant        GM_xmlhttpRequest
// @grant        GM_addStyle
// @connect      mzlive.eu
// @require      https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js
// @run-at       document-idle
// @license      MIT
// ==/UserScript==

(function () {
    'use strict';

    GM_addStyle("@import url('https://fonts.googleapis.com/css2?family=Space+Mono:wght@400;700&display=swap');");
    GM_addStyle(".mz-search-container{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%) scale(.95);background:linear-gradient(135deg,#0a0a0a 0%,#1a1a2e 100%);color:#f0f0f0;padding:2rem;border-radius:12px;box-shadow:0 8px 32px rgba(83,11,237,.3),0 4px 8px rgba(0,0,0,.2);z-index:9999;visibility:hidden;width:800px;max-width:99%;opacity:0;transition:all .3s cubic-bezier(0.4,0,0.2,1);border:1px solid rgba(138,43,226,.1)}.mz-search-container.visible{visibility:visible;opacity:1;transform:translate(-50%,-50%) scale(1)}.mz-search-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:2rem;padding-bottom:1rem;border-bottom:1px solid rgba(138,43,226,.2)}.mz-search-header h2{font-family:'Space Mono',monospace;margin:0;color:violet;font-size:1.5rem;text-shadow:0 0 10px rgba(138,43,226,.5)}.mz-search-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:1rem;margin-bottom:1.5rem}.mz-search-field{display:flex;flex-direction:column;gap:.5rem}.mz-search-field label{color:#ff9966;font-size:.875rem;text-transform:uppercase;letter-spacing:1px}.mz-search-field select{padding:.75rem;border:1px solid rgba(138,43,226,.3);border-radius:8px;background:#1a1a2e;color:#f0f0f0;font-size:1rem;transition:all .2s;appearance:none;background-image:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23ff9966' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E\");background-repeat:no-repeat;background-position:right .75rem center;background-size:1rem}.mz-search-field select:focus{outline:none;border-color:#ff9966;box-shadow:0 0 0 2px rgba(138,43,226,.2)}.mz-search-button{width:auto;max-width:300px;padding:0.5rem 1rem;background:#009b3a;color:#ffdf00;border:none;border-radius:8px;font-weight:500;font-size:0.9rem;cursor:pointer;transition:all .2s;text-transform:uppercase;letter-spacing:2px;box-shadow:0 4px 6px rgba(0,0,0,.1);display:block;margin:1rem auto}.mz-search-button:not(:disabled):hover{transform:translateY(-2px);box-shadow:0 6px 8px rgba(0,0,0,.2)}.mz-search-button:disabled{opacity:0.5;cursor:not-allowed;background:#666}.mz-progress{margin-top:1.5rem;padding:1rem;background:rgba(26,26,46,.5);border-radius:8px;visibility:hidden;opacity:0;transition:all .3s}.mz-progress.visible{visibility:visible;opacity:1}.mz-progress-info{display:flex;justify-content:space-between;align-items:center;margin-bottom:1rem;color:#ff9966;font-size:.875rem}.mz-progress-bar{width:100%;height:6px;background:#1a1a2e;border-radius:3px;overflow:hidden}.mz-progress-fill{height:100%;width:0;background:linear-gradient(135deg,#4834d4 0%,#6366f1 100%);transition:width .3s ease-out}.mz-search-log{margin-top:1rem;padding:1rem;background:rgba(26,26,46,.3);border-radius:8px;font-family:monospace;font-size:.875rem;max-height:150px;overflow-y:auto;scrollbar-width:thin;scrollbar-color:#6366f1 #1a1a2e}.mz-search-log::-webkit-scrollbar{width:8px;height:8px}.mz-search-log::-webkit-scrollbar-track{background:#1a1a2e;border-radius:4px}.mz-search-log::-webkit-scrollbar-thumb{background:#6366f1;border-radius:4px}.mz-search-log::-webkit-scrollbar-thumb:hover{background:#4834d4}.mz-search-log-entry{margin-bottom:.5rem;padding:.5rem;background:rgba(26,26,46,.5);border-radius:4px;color:#00ffff;animation:slideIn 0.3s ease-out forwards;opacity:0;transform:translateX(-20px)}@keyframes slideIn{from{opacity:0;transform:translateX(-20px)}to{opacity:1;transform:translateX(0)}}.mz-guestbook-link{position:fixed;top:1rem;right:1rem;color:#ff9966;transition:all .2s}.mz-guestbook-link:hover{color:#6366f1;transform:scale(1.1)}.mz-country-select{width:200px}.mz-country-select select{width:100%}.mz-loading{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);background:rgba(10,10,20,.95);padding:1rem;border-radius:8px;z-index:10000;box-shadow:0 8px 32px rgba(83,11,237,.2);visibility:hidden;opacity:0;transition:all .3s}.mz-loading.visible{visibility:visible;opacity:1}.mz-spinner{position:relative;width:20px;height:20px}.mz-spinner::before,.mz-spinner::after{content:'';position:absolute;border-radius:50%;animation:pulse 1.8s ease-in-out infinite;transform-origin:center}.mz-spinner::before{width:100%;height:100%;background:rgba(99,102,241,.5);animation-delay:-0.9s;transform:scale(0.3)}.mz-spinner::after{width:75%;height:75%;background:rgba(99,102,241,.8);top:12.5%;transform:scale(0.3)}@keyframes pulse{0%,100%{transform:scale(0.3);opacity:1}50%{transform:scale(0.6);opacity:.25}}.ui-state-default:last-child{background:linear-gradient(135deg,#4834d4 0%,#6366f1 100%);border-color:#4834d4}.ui-state-default:last-child:hover{background:linear-gradient(135deg,#5844e4 0%,#7376f1 100%);border-color:#5844e4}.ui-state-default:last-child a{color:#fff;text-shadow:0 1px 2px rgba(0,0,0,.2);font-family:'Space Mono',monospace !important;letter-spacing:1px}.mz-results-button{width:auto;max-width:300px;padding:0.5rem 1rem;background:#009b3a;color:#ffdf00;border:none;border-radius:8px;font-weight:500;font-size:0.9rem;cursor:pointer;transition:all .2s;text-transform:uppercase;letter-spacing:2px;box-shadow:0 4px 6px rgba(0,0,0,.1);display:none;margin:1rem auto}.mz-results-button:hover{transform:translateY(-2px);box-shadow:0 6px 8px rgba(0,0,0,.2)}.mz-results-modal{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);background:linear-gradient(135deg,#0a0a0a 0%,#1a1a2e 100%);color:#f0f0f0;padding:0;border-radius:12px;z-index:10001;width:90%;height:90vh;overflow:hidden;box-shadow:0 8px 32px rgba(83,11,237,.3);animation:modalSlideIn 0.3s ease-out forwards}@keyframes modalSlideIn{from{opacity:0;transform:translate(-50%,-48%)}to{opacity:1;transform:translate(-50%,-50%)}}.mz-results-header{position:sticky;top:0;display:flex;justify-content:space-between;align-items:center;padding:1.5rem;background:inherit;border-bottom:1px solid rgba(138,43,226,.2);z-index:1}.mz-results-title{font-family:'Space Mono',monospace;margin:0;font-size:1.5rem;color:#fff;text-shadow:0 0 10px rgba(138,43,226,.5)}.mz-results-close{background:none;border:none;color:#ff9966;font-size:1.5rem;cursor:pointer;transition:all 0.2s;padding:0.5rem}.mz-results-close:hover{color:#6366f1;transform:scale(1.1)}.mz-results-content{padding:1.5rem;height:calc(90vh - 5rem);overflow-y:auto;scrollbar-width:thin;scrollbar-color:#6366f1 #1a1a2e}.mz-results-content::-webkit-scrollbar{width:8px}.mz-results-content::-webkit-scrollbar-track{background:#1a1a2e}.mz-results-content::-webkit-scrollbar-thumb{background:#6366f1;border-radius:4px}.mz-results-content::-webkit-scrollbar-thumb:hover{background:#4834d4}.mz-player-card{background:rgba(26,26,46,.5);border-radius:8px;margin-bottom:1.5rem;padding:1.5rem;transition:all 0.2s;border:1px solid rgba(138,43,226,.1)}.mz-player-card:hover{transform:translateY(-2px);box-shadow:0 4px 12px rgba(83,11,237,.2)}.mz-player-header{display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:1rem}.mz-player-info{flex:1}.mz-player-name{font-size:1.25rem;font-weight:bold;color:#fff;margin:0 0 0.5rem 0}.mz-player-details{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:1rem;color:#ff9966;font-size:0.875rem}.mz-player-skills{display:grid;grid-template-columns:repeat(auto-fill,minmax(250px,1fr));gap:1rem;margin-top:1rem}.mz-skill-row{display:flex;align-items:center;padding:0.5rem;background:rgba(26,26,46,.3);border-radius:4px}.mz-skill-name{flex:1;font-size:0.875rem;color:#f0f0f0}.mz-skill-value{display:flex;align-items:center;gap:0.5rem}.mz-skill-level{width:100px;height:8px;background:#1a1a2e;border-radius:4px;overflow:hidden}.mz-skill-fill{height:100%;background:linear-gradient(135deg,#4834d4 0%,#6366f1 100%);transition:width 0.3s}.mz-skill-number{font-size:0.875rem;color:#ff9966;min-width:2rem;text-align:right}.mz-results-pagination{display:flex;justify-content:center;align-items:center;gap:1rem;margin:1rem 0;padding:1rem;border-bottom:1px solid rgba(138,43,226,.2)}.mz-pagination-button{background:#1a1a2e;color:#f0f0f0;border:1px solid rgba(138,43,226,.3);border-radius:4px;padding:0.5rem 1rem;cursor:pointer;transition:all 0.2s}.mz-pagination-button:not(:disabled):hover{background:#2a2a4e;transform:translateY(-1px)}.mz-pagination-button:disabled{opacity:0.5;cursor:not-allowed}.mz-pagination-info{color:#ff9966;font-size:0.875rem}.mz-results-total{padding:1rem;text-align:right;color:#888;font-size:0.875rem}.mz-export-button{background:#1a1a2e;color:#f0f0f0;border:1px solid rgba(138,43,226,.3);border-radius:4px;padding:0.5rem 1rem;cursor:pointer;transition:all 0.2s;margin-left:1rem}.mz-export-button:hover{background:#2a2a4e;transform:translateY(-1px)}.mz-export-button:active{transform:translateY(1px)}.mz-header-controls{display:flex;align-items:center;gap:1rem}");

    const MASSIVE_COUNTRIES = ['BR', 'CN', 'AR', 'SE', 'PL', 'TR'];
    const PLAYERS_PER_PAGE = 20;

    class Logger {
        constructor(container, batchSize = 10) {
            this.container = container;
            this.batchSize = batchSize;
            this.queue = [];
            this.timeout = null;
        }

        getTimestamp() {
            const now = new Date();
            return `[${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')}:${now.getSeconds().toString().padStart(2, '0')}]`;
        }

        log(message, type = 'info') {
            this.queue.push({
                message: `${this.getTimestamp()} ${message}`,
                type
            });

            if (this.queue.length >= this.batchSize) {
                this.flush();
            } else if (!this.timeout) {
                this.timeout = setTimeout(() => this.flush(), 100);
            }
        }

        flush() {
            if (!this.queue.length) return;

            const fragment = document.createDocumentFragment();

            this.queue.forEach(({ message, type }) => {
                const entry = document.createElement('div');
                entry.className = `mz-search-log-entry ${type}`;
                entry.textContent = message;
                fragment.appendChild(entry);
            });

            this.container.appendChild(fragment);
            this.container.scrollTop = this.container.scrollHeight;

            this.queue = [];
            if (this.timeout) {
                clearTimeout(this.timeout);
                this.timeout = null;
            }
        }
    }

    class RequestQueue {
        constructor(maxConcurrent = 5, delay = 100) {
            this.queue = [];
            this.maxConcurrent = maxConcurrent;
            this.delay = delay;
            this.running = 0;
            this.processed = 0;
        }

        add(request) {
            return new Promise((resolve, reject) => {
                const wrappedRequest = async () => {
                    try {
                        await new Promise(res => setTimeout(res, this.delay));
                        const result = await request();
                        this.processed++;
                        resolve(result);
                    } catch (error) {
                        reject(error);
                    } finally {
                        this.running--;
                        this.processNext();
                    }
                };

                this.queue.push(wrappedRequest);
                this.processNext();
            });
        }

        processNext() {
            while (this.running < this.maxConcurrent && this.queue.length > 0) {
                this.running++;
                const request = this.queue.shift();
                request();
            }
        }

        reset() {
            this.queue = [];
            this.running = 0;
            this.processed = 0;
        }
    }

    class ChunkProcessor {
        constructor(chunkSize = 25) {
            this.chunkSize = chunkSize;
        }

        async process(items, processFn, onChunkComplete) {
            const chunks = this.createChunks(items);
            let processed = 0;

            for (const chunk of chunks) {
                await Promise.all(chunk.map(processFn));
                processed += chunk.length;
                if (onChunkComplete) {
                    onChunkComplete(processed, items.length);
                }
                await new Promise(res => setTimeout(res, 50));
            }
        }

        createChunks(items) {
            const chunks = [];
            for (let i = 0; i < items.length; i += this.chunkSize) {
                chunks.push(items.slice(i, i + this.chunkSize));
            }
            return chunks;
        }
    }

    class ProgressManager {
        constructor(element, throttleMs = 100) {
            this.element = element;
            this.throttleMs = throttleMs;
            this.lastUpdate = 0;
            this.pendingUpdate = null;
        }

        update(percent) {
            const now = Date.now();
            if (now - this.lastUpdate >= this.throttleMs) {
                this.updateProgress(percent);
                this.lastUpdate = now;
            } else if (!this.pendingUpdate) {
                this.pendingUpdate = setTimeout(() => {
                    this.updateProgress(percent);
                    this.lastUpdate = Date.now();
                    this.pendingUpdate = null;
                }, this.throttleMs);
            }
        }

        updateProgress(percent) {
            if (this.element) {
                this.element.style.width = `${percent}%`;
            }
        }
    }

    class NTPlayerParser {
        constructor(minRequirements) {
            this.minRequirements = minRequirements;
            this.skillMapping = {
                "Speed": "speed",
                "Stamina": "stamina",
                "Play Intelligence": "playIntelligence",
                "Passing": "passing",
                "Shooting": "shooting",
                "Heading": "heading",
                "Keeping": "keeping",
                "Ball Control": "ballControl",
                "Tackling": "tackling",
                "Aerial Passing": "aerialPassing",
                "Set Plays": "setPlays",
                "Experience": "experience"
            };
        }

        parseSkills(html) {
            const parser = new DOMParser();
            const doc = parser.parseFromString(html, 'text/html');
            const rows = doc.querySelectorAll('.player_skills tr');

            if (!rows.length) return null;

            const skills = {};
            let totalBalls = 0;

            const totalBallsElement = doc.querySelector('td[title="All skills combined except Form and Experience"] span.bold');
            if (totalBallsElement) {
                totalBalls = parseInt(totalBallsElement.textContent, 10) || 0;
            }

            rows.forEach(row => {
                const skillElem = row.querySelector('td span.clippable');
                if (!skillElem) return;

                const rawSkillName = skillElem.textContent.trim();
                if (rawSkillName === "Form") return;
                if (!(rawSkillName in this.skillMapping)) return;

                const skillKey = this.skillMapping[rawSkillName];
                const valueCell = row.querySelector('.skillval');
                if (!valueCell) return;

                const rawValue = valueCell.textContent.replace(/[()]/g, "").trim();
                const value = parseInt(rawValue, 10);
                if (!isNaN(value)) {
                    skills[skillKey] = value;
                }
            });

            if (Object.keys(skills).length === 0) return null;
            if (!this.validateSkills(skills)) return null;

            return { skills, totalBalls };
        }

        validateSkills(skills) {
            return Object.entries(this.minRequirements)
                .filter(([key]) => key in skills)
                .every(([key, minValue]) => skills[key] >= minValue);
        }

        async fetchAndParsePlayer(playerId, ntid, cid) {
            const url = `https://www.managerzone.com/ajax.php?p=nationalTeams&sub=search&ntid=${ntid}&cid=${cid}&type=national_team&pid=${playerId}&sport=soccer`;

            try {
                const response = await fetch(url);
                const html = await response.text();
                return this.parseSkills(html);
            } catch (error) {
                return null;
            }
        }
    }

    class PlayerData {
        constructor(id, name, teamName, age, value, salary, totalBalls, skills) {
            this.id = id;
            this.name = name;
            this.teamName = teamName;
            this.age = age;
            this.value = value;
            this.salary = salary;
            this.totalBalls = totalBalls;
            this.skills = skills;
        }

        toExcelRow() {
            return {
                'ID': this.id,
                'Name': this.name,
                'Team': this.teamName,
                'Age': this.age,
                'Value': this.value,
                'Salary': this.salary,
                'Total Balls': this.totalBalls,
                'Speed': this.skills.speed || 0,
                'Stamina': this.skills.stamina || 0,
                'Play Intelligence': this.skills.playIntelligence || 0,
                'Short Passing': this.skills.passing || 0,
                'Shooting': this.skills.shooting || 0,
                'Heading': this.skills.heading || 0,
                'Keeping': this.skills.keeping || 0,
                'Ball Control': this.skills.ballControl || 0,
                'Tackling': this.skills.tackling || 0,
                'Aerial Passing': this.skills.aerialPassing || 0,
                'Set Plays': this.skills.setPlays || 0,
                'Experience': this.skills.experience || 0
            };
        }
    }

    class NationalTeamChecker {
        constructor() {
            this.username = null;
        }

        async getCurrentUsername() {
            if (this.username) return this.username;
            const usernameElem = document.querySelector('#header-username');
            this.username = usernameElem ? usernameElem.textContent.trim() : null;
            return this.username;
        }

        async validate() {
            const username = await this.getCurrentUsername();
            if (!username) return false;

            try {
                const teamTabLink = document.querySelector('#nt-tabs ul.ui-tabs-nav li a[href*="sub=team"]');
                if (!teamTabLink) return false;

                const linkUrl = new URL(teamTabLink.getAttribute('href'), location.origin);
                const ntid = linkUrl.searchParams.get('ntid');
                const cid = linkUrl.searchParams.get('cid');

                if (!ntid || !cid) return false;

                const ajaxUrl = `https://www.managerzone.com/ajax.php?p=nationalTeams&sub=team&ntid=${ntid}&cid=${cid}&type=national_team&sport=soccer`;
                const response = await fetch(ajaxUrl);
                const text = await response.text();
                const parser = new DOMParser();
                const doc = parser.parseFromString(text, 'text/html');
                const managerTables = doc.querySelectorAll('table.padding');

                const ncUsernames = Array.from(managerTables)
                    .map(table => table.querySelector('a[href*="p=profile"]')?.textContent.trim())
                    .filter(Boolean);

                return ncUsernames.includes(username);
            } catch (error) {
                console.error('Failed to validate NC/NCA status:', error);
                return false;
            }
        }
    }

    class NTPlayerSearcher {
        constructor() {
            this.requestQueue = new RequestQueue(5, 100);
            this.chunkProcessor = new ChunkProcessor(25);
            this.searchValues = { speed: 0, stamina: 0, playIntelligence: 0, passing: 0, shooting: 0, heading: 0, keeping: 0, ballControl: 0, tackling: 0, aerialPassing: 0, setPlays: 0, experience: 0, minAge: 16, maxAge: 96, totalBalls: 9, country: '', countryData: null };
            this.isSearching = false;
            this.teamIds = new Set();
            this.playerIds = new Map();
            this.processedLeagues = 0;
            this.totalLeagues = 0;
            this.validPlayers = new Map();
            this.ncChecker = new NationalTeamChecker();
        }

        exportToExcel() {
            if (this.validPlayers.size === 0) return;

            const worksheet = XLSX.utils.json_to_sheet(
                Array.from(this.validPlayers.values()).map(player => player.toExcelRow())
            );

            const workbook = XLSX.utils.book_new();
            XLSX.utils.book_append_sheet(workbook, worksheet, "Players");

            const date = new Date().toISOString().split('T')[0];
            XLSX.writeFile(workbook, `mz_players_${date}.xlsx`);
        }

        async fetchTop100Players(country, page = 0, isU21 = false) {
            try {
                const baseUrl = `https://mzlive.eu/mzlive.php?action=list&type=top100&mode=players&country=${country}&currency=EUR`;
                const url = isU21 ? `${baseUrl}&age=u21&page=${page}` : `${baseUrl}&page=${page}`;

                const response = await this.requestQueue.add(() =>
                    new Promise((resolve, reject) => {
                        GM_xmlhttpRequest({
                            method: 'GET',
                            url,
                            onload: res => resolve(res),
                            onerror: err => reject(err)
                        });
                    })
                );

                const data = JSON.parse(response.responseText);
                const players = data.players || [];

                const playerEntries = players.map(player => [
                    player.id.toString(),
                    {
                        id: player.id.toString(),
                        name: player.name,
                        teamName: player.team_name,
                        age: player.age,
                        value: parseInt(player.value),
                        salary: 0
                    }
                ]);

                this.playerIds = new Map([...this.playerIds, ...playerEntries]);

                return players.map(player => player.id.toString());
            } catch (error) {
                this.logger.log(`Error fetching top 100 players: ${error.message}`, 'error');
                return [];
            }
        }

        async fetchAllTop100Players(country) {
            const maxPages = MASSIVE_COUNTRIES.includes(country) ? 20 : 5;
            const isU21 = this.searchValues.maxAge <= 21;
            const pages = Array.from({ length: maxPages + 1 }, (_, i) => i);

            const chunkSize = 5;
            const results = [];

            for (let i = 0; i < pages.length; i += chunkSize) {
                const chunk = pages.slice(i, i + chunkSize);
                const chunkResults = await Promise.all(
                    chunk.map(page => this.fetchTop100Players(country, page, isU21))
                );
                results.push(...chunkResults);
                await new Promise(res => setTimeout(res, 100));
            }

            return results.flat();
        }

        async fetchCountriesList() {
            return new Promise((resolve, reject) => {
                GM_xmlhttpRequest({
                    method: 'GET',
                    url: 'https://pub-02de1c06eac643f992bb26daeae5c7a0.r2.dev/json/countriesData.json',
                    onload: res => resolve(JSON.parse(res.responseText)),
                    onerror: err => reject(err)
                });
            });
        }

        async fetchUserCountry() {
            const usernameElem = document.querySelector('#header-username');
            if (!usernameElem) return null;
            const username = usernameElem.textContent.trim();
            const response = await fetch(`https://www.managerzone.com/xml/manager_data.php?sport_id=1&username=${username}`);
            const text = await response.text();
            const parser = new DOMParser();
            const xmlDoc = parser.parseFromString(text, 'text/xml');
            return xmlDoc.querySelector('UserData')?.getAttribute('countryShortname') || null;
        }

        async init() {
            const isNC = await this.ncChecker.validate();
            if (!isNC) return;

            const loading = this.showLoading();
            loading.classList.add('visible');

            try {
                const [countries, userCountry] = await Promise.all([
                    this.fetchCountriesList(),
                    this.fetchUserCountry()
                ]);

                this.countries = countries;
                this.userCountry = userCountry;
                this.searchValues.country = userCountry;

                await this.appendSearchTab();
                this.setUpEvents();

                const logContainer = document.querySelector('.mz-search-log');
                this.logger = new Logger(logContainer);

                const progressBar = document.querySelector('.mz-progress-fill');
                this.progressManager = new ProgressManager(progressBar);
            } finally {
                loading.classList.remove('visible');
                setTimeout(() => loading.remove(), 300);
            }
        }

        showLoading() {
            const loading = document.createElement('div');
            loading.className = 'mz-loading';
            const spinner = document.createElement('div');
            spinner.className = 'mz-spinner';
            loading.appendChild(spinner);
            document.body.appendChild(loading);
            return loading;
        }

        async getLeagueIds(countryCode) {
            try {
                const response = await this.requestQueue.add(() =>
                    new Promise((resolve, reject) => {
                        GM_xmlhttpRequest({
                            method: 'GET',
                            url: `https://mzlive.eu/mzlive.php?action=list&type=leagues&country=${countryCode}`,
                            onload: res => resolve(res),
                            onerror: err => reject(err)
                        });
                    })
                );

                const leagues = JSON.parse(response.responseText);
                const maxDivision = MASSIVE_COUNTRIES.includes(countryCode) ? 6 : 3;

                return leagues.filter(league => {
                    const name = league.name.toLowerCase();
                    if (name.startsWith('div')) {
                        const divLevel = parseInt(name.split('.')[0].replace('div', ''));
                        return divLevel <= maxDivision;
                    }
                    return true;
                }).map(league => league.id);
            } catch (error) {
                this.logger.log(`Error fetching leagues: ${error.message}`, 'error');
                throw error;
            }
        }

        async getTeamIds(leagueId) {
            try {
                const response = await this.requestQueue.add(() =>
                    fetch(`https://www.managerzone.com/xml/team_league.php?sport_id=1&league_id=${leagueId}`)
                );
                const text = await response.text();
                const parser = new DOMParser();
                const xmlDoc = parser.parseFromString(text, "text/xml");
                const teams = xmlDoc.getElementsByTagName('Team');
                return Array.from(teams).map(team => team.getAttribute('teamId'));
            } catch (error) {
                this.logger.log(`Error fetching teams for league ${leagueId}: ${error.message}`, 'error');
                return [];
            }
        }

        async processLeagueBatch(leagueIds) {
            await this.chunkProcessor.process(
                leagueIds,
                async (leagueId) => {
                    try {
                        const teamIds = await this.getTeamIds(leagueId);
                        teamIds.forEach(id => this.teamIds.add(id));
                        this.processedLeagues++;
                        this.logger.log(`Processed league ${leagueId}`);
                    } catch (error) {
                        this.logger.log(`Failed to process league ${leagueId}: ${error}`, 'error');
                    }
                },
                (processed, total) => {
                    const progressPercent = (processed / total) * 100;
                    this.progressManager.update(progressPercent);
                }
            );
        }

        async fetchPlayerList(teamId) {
            try {
                const response = await this.requestQueue.add(() =>
                    fetch(`https://www.managerzone.com/xml/team_playerlist.php?sport_id=1&team_id=${teamId}`)
                );
                const text = await response.text();
                const parser = new DOMParser();
                const xmlDoc = parser.parseFromString(text, "text/xml");
                const teamPlayers = xmlDoc.querySelector('TeamPlayers');
                const teamName = teamPlayers?.getAttribute('teamName') || '';
                const players = xmlDoc.getElementsByTagName('Player');
                const targetCountry = this.searchValues.country.toLowerCase();

                const validPlayers = Array.from(players).filter(player => {
                    const age = parseInt(player.getAttribute('age'));
                    const countryCode = player.getAttribute('countryShortname').toLowerCase();
                    return age >= this.searchValues.minAge &&
                        age <= this.searchValues.maxAge &&
                        countryCode === targetCountry;
                });

                validPlayers.forEach(player => {
                    const playerId = player.getAttribute('id');
                    const playerName = player.getAttribute('name');
                    const value = parseInt(player.getAttribute('value')) || 0;
                    const salary = parseInt(player.getAttribute('salary')) || 0;
                    const age = parseInt(player.getAttribute('age'));

                    if (playerId) {
                        this.playerIds.set(playerId, {
                            id: playerId,
                            name: playerName,
                            teamName: teamName,
                            age: age,
                            value: value,
                            salary: salary
                        });
                    }
                });
            } catch (error) {
                this.logger.log(`Error fetching players for team ${teamId}: ${error.message}`, 'error');
            }
        }

        async processTeamBatch(teamIds) {
            await this.chunkProcessor.process(
                teamIds,
                async (teamId) => {
                    await this.fetchPlayerList(teamId);
                    this.logger.log(`Processed team ${teamId}`);
                }
            );
        }

        async searchForPlayers() {
            if (!this.searchValues.country) {
                this.logger.log('No country selected', 'error');
                return;
            }

            this.teamIds = new Set();
            this.playerIds = new Map();
            this.processedLeagues = 0;
            this.validPlayers = new Map();

            const countryCode = this.searchValues.country;
            this.logger.log(`Starting search for country ${countryCode}`);

            try {
                if (this.searchValues.maxAge > 18) {
                    await this.fetchAllTop100Players(countryCode);
                    this.logger.log(`Found ${this.playerIds.size} players from MZLists`);
                }

                const leagueIds = await this.getLeagueIds(countryCode);
                this.totalLeagues = leagueIds.length;
                this.logger.log(`Found ${leagueIds.length} leagues to process`);

                await this.processLeagueBatch(leagueIds);

                this.logger.log('Processing teams...');
                await this.processTeamBatch(Array.from(this.teamIds));

                const ntPlayerParser = new NTPlayerParser(this.searchValues);
                const { ntid, cid } = this.searchValues.countryData;

                this.logger.log('Processing player skills...');
                const playerEntries = Array.from(this.playerIds.entries());
                let processedCount = 0;

                await this.chunkProcessor.process(
                    playerEntries,
                    async ([playerId, playerData]) => {
                        const parsedData = await ntPlayerParser.fetchAndParsePlayer(playerId, ntid, cid);
                        if (parsedData && parsedData.totalBalls >= this.searchValues.totalBalls) {
                            this.validPlayers.set(playerId, new PlayerData(
                                playerId,
                                playerData.name,
                                playerData.teamName,
                                playerData.age,
                                playerData.value,
                                playerData.salary,
                                parsedData.totalBalls,
                                parsedData.skills
                            ));
                            this.logger.log(`Player ${playerData.name} (${playerId}) meets the specified requirements`);
                        }
                        processedCount++;
                        if (processedCount % 10 === 0) {
                            this.progressManager.update((processedCount / playerEntries.length) * 100);
                        }
                    }
                );

                this.logger.log('Finishing…');
                await new Promise(resolve => setTimeout(resolve, 500));

                const finalCount = this.validPlayers.size;
                this.logger.log(`Done: found ${finalCount} valid players`);
                document.querySelector('.mz-results-button').style.display = finalCount > 0 ? "inline-block" : "none";

                return Array.from(this.validPlayers.keys());
            } catch (error) {
                this.logger.log(`Error during search: ${error.message}`, 'error');
                console.error('Search failed:', error);
                throw error;
            }
        }

        async performSearch() {
            if (this.isSearching || !this.searchValues.country) return;

            this.isSearching = true;
            const searchButton = document.querySelector('.mz-search-button');
            searchButton.disabled = true;

            const loading = this.showLoading();
            const progress = document.querySelector('.mz-progress');
            const logContainer = document.querySelector('.mz-search-log');
            const resultsButton = document.querySelector('.mz-results-button');

            loading.classList.add('visible');
            progress.classList.add('visible');
            logContainer.innerHTML = '';
            resultsButton.style.display = 'none';

            try {
                await this.searchForPlayers();
            } catch (error) {
                this.logger.log(`Error during search: ${error.message}`, 'error');
                console.error('Search failed:', error);
            } finally {
                this.isSearching = false;
                searchButton.disabled = false;
                progress.classList.remove('visible');
                loading.classList.remove('visible');
                setTimeout(() => loading.remove(), 300);
            }
        }

        getFiltersAppliedText() {
            const filters = [];
            if (this.searchValues.country) {
                filters.push(`Country: ${this.searchValues.country}`);
            }
            filters.push(`Age: ${this.searchValues.minAge} - ${this.searchValues.maxAge}`);
            filters.push(`Total Balls >= ${this.searchValues.totalBalls}`);

            const skillFields = [
                'speed', 'stamina', 'playIntelligence', 'passing', 'shooting',
                'heading', 'keeping', 'ballControl', 'tackling', 'aerialPassing',
                'setPlays', 'experience'
            ];

            skillFields.forEach(skill => {
                if (this.searchValues[skill] > 0) {
                    filters.push(`${this.formatSkillName(skill)} >= ${this.searchValues[skill]}`);
                }
            });

            return filters.join(', ');
        }

        showResults() {
            if (this.validPlayers.size === 0) {
                alert("No valid players found.");
                return;
            }

            const modal = document.createElement('div');
            modal.className = 'mz-results-modal';

            const modalHeader = document.createElement('div');
            modalHeader.className = 'mz-results-header';

            const headerControls = document.createElement('div');
            headerControls.className = 'mz-header-controls';

            const closeButton = document.createElement('button');
            closeButton.className = 'mz-results-close';
            closeButton.innerHTML = '&times;';

            const exportButton = document.createElement('button');
            exportButton.className = 'mz-export-button';
            exportButton.textContent = 'Export';
            exportButton.onclick = () => this.exportToExcel();

            headerControls.appendChild(exportButton);
            headerControls.appendChild(closeButton);

            modalHeader.innerHTML = `
    <div>
        <h2 class="mz-results-title">Search Results</h2>
        <div class="mz-results-total">Total players found: ${this.validPlayers.size}</div>
        <div class="mz-results-filters" style="font-size: 0.9rem; color: #aaa; margin-top: 0.5rem;">
            Filters applied: ${this.getFiltersAppliedText()}
        </div>
    </div>
`;
            modalHeader.appendChild(headerControls);

            const modalContent = document.createElement('div');
            modalContent.className = 'mz-results-content';

            const playersContainer = document.createElement('div');
            playersContainer.className = 'mz-players-container';

            const paginationContainer = document.createElement('div');
            paginationContainer.className = 'mz-results-pagination';

            const playersArray = Array.from(this.validPlayers.values())
                .sort((a, b) => b.totalBalls - a.totalBalls);

            let currentPage = 1;
            const totalPages = Math.ceil(playersArray.length / PLAYERS_PER_PAGE);

            const renderPage = (page) => {
                playersContainer.innerHTML = '';
                const startIndex = (page - 1) * PLAYERS_PER_PAGE;
                const pagePlayers = playersArray.slice(startIndex, startIndex + PLAYERS_PER_PAGE);

                const paginationMarkup = totalPages > 1 ? `
                    <button class="mz-pagination-button" ${page === 1 ? 'disabled' : ''} data-action="prev">Previous</button>
                    <span class="mz-pagination-info">Page ${page} of ${totalPages}</span>
                    <button class="mz-pagination-button" ${page === totalPages ? 'disabled' : ''} data-action="next">Next</button>
                ` : '';

                paginationContainer.innerHTML = paginationMarkup;

                if (totalPages > 1) {
                    const prevBtn = paginationContainer.querySelector('[data-action="prev"]');
                    const nextBtn = paginationContainer.querySelector('[data-action="next"]');

                    if (prevBtn) {
                        prevBtn.addEventListener('click', () => {
                            if (currentPage > 1) {
                                currentPage--;
                                renderPage(currentPage);
                            }
                        });
                    }

                    if (nextBtn) {
                        nextBtn.addEventListener('click', () => {
                            if (currentPage < totalPages) {
                                currentPage++;
                                renderPage(currentPage);
                            }
                        });
                    }
                }

                pagePlayers.forEach(player => {
                    const skillBars = Object.entries(player.skills)
                        .map(([skill, value]) => `
                            <div class="mz-skill-row">
                                <span class="mz-skill-name">${this.formatSkillName(skill)}</span>
                                <div class="mz-skill-value">
                                    <div class="mz-skill-level">
                                        <div class="mz-skill-fill" style="width: ${(value / 10) * 100}%"></div>
                                    </div>
                                    <span class="mz-skill-number">${value}</span>
                                </div>
                            </div>
                        `).join('');

                    const playerCard = document.createElement('div');
                    playerCard.className = 'mz-player-card';
                    playerCard.innerHTML = `
                        <div class="mz-player-header">
                            <div class="mz-player-info">
                                <h3 class="mz-player-name">
                                    <a href="https://www.managerzone.com/?p=players&pid=${player.id}"
                                       target="_blank">${player.name} (${player.id})</a>
                                </h3>
                                <div class="mz-player-details">
                                    <div>Team: ${player.teamName}</div>
                                    <div>Age: ${player.age}</div>
                                    <div>Value: ${new Intl.NumberFormat('en-US').format(player.value)} USD</div>
                                    <div>Total Balls: ${player.totalBalls}</div>
                                </div>
                            </div>
                        </div>
                        <div class="mz-player-skills">
                            ${skillBars}
                        </div>
                    `;
                    playersContainer.appendChild(playerCard);
                });
            };

            const filtersInfo = document.createElement('div');
            filtersInfo.className = 'mz-results-filters';
            filtersInfo.innerHTML = `Filters applied: ${this.getFiltersAppliedText()}`;

            modalContent.appendChild(filtersInfo);
            modalContent.appendChild(paginationContainer);
            modalContent.appendChild(playersContainer);

            modal.appendChild(modalHeader);
            modal.appendChild(modalContent);
            document.body.appendChild(modal);

            renderPage(currentPage);

            closeButton.addEventListener('click', () => {
                modal.remove();
            });

            document.addEventListener('keydown', (e) => {
                if (e.key === 'Escape') {
                    modal.remove();
                }
            });
        }

        formatSkillName(skill) {
            const names = {
                speed: 'Speed',
                stamina: 'Stamina',
                playIntelligence: 'Play Intelligence',
                passing: 'Short Passing',
                shooting: 'Shooting',
                heading: 'Heading',
                keeping: 'Keeping',
                ballControl: 'Ball Control',
                tackling: 'Tackling',
                aerialPassing: 'Aerial Passing',
                setPlays: 'Set Plays',
                experience: 'Experience'
            };
            return names[skill] || skill;
        }

        async appendSearchTab() {
            const tabList = document.querySelector('ul.ui-tabs-nav');
            if (!tabList) return;

            const searchTab = document.createElement('li');
            searchTab.className = 'ui-state-default ui-corner-top';
            searchTab.innerHTML = '<a href="#" class="ui-tabs-anchor">NTSearch</a>';
            tabList.appendChild(searchTab);

            const searchContainer = document.createElement('div');
            searchContainer.className = 'mz-search-container';

            const 씨발 = k => ({
                go: {
                    en: 'Go', pt: 'Buscar', es: 'Ir', fr: 'Aller',
                    de: 'Los', it: 'Vai', nl: 'Ga', ru: 'Поехали',
                    ja: '行く', zh: '去', ko: '가자', sv: 'Gå',
                    no: 'Gå', da: 'Gå', fi: 'Mene', pl: 'Idź',
                    cs: 'Jdi', sk: 'Choď', hu: 'Menj', tr: 'Git',
                    el: 'Πήγαινε', ro: 'Du-te', bg: 'Отиди', hr: 'Idi',
                    sr: 'Иди', lt: 'Eik', lv: 'Ejam', et: 'Mine',
                    sl: 'Pojdi', is: 'Fara', ar: 'انطلق', hi: 'जाओ',
                    bn: 'চলো', ur: 'چلو', vi: 'Đi', th: 'ไป',
                    id: 'Pergi', ms: 'Pergi', fa: 'برو', he: 'לך',
                    ca: 'Vés'
                }[navigator.language.slice(0, 2)] || 'Go'
            })[k];

            const skillFields = [
                ['speed', 'Speed'],
                ['stamina', 'Stamina'],
                ['playIntelligence', 'Play Intel.'],
                ['passing', 'Short Passing'],
                ['shooting', 'Shooting'],
                ['heading', 'Heading'],
                ['keeping', 'Keeping'],
                ['ballControl', 'Ball Control'],
                ['tackling', 'Tackling'],
                ['aerialPassing', 'Aerial Passing'],
                ['setPlays', 'Set Plays'],
                ['experience', 'Experience']
            ];

            const skillsHTML = skillFields.map(([field, label]) => `
                <div class="mz-search-field">
                    <label>Min. ${label}</label>
                    <select name="${field}">
                        ${this.generateOptions(10)}
                    </select>
                </div>
            `).join('');

            searchContainer.innerHTML = `
                <div class="mz-search-header">
                    <h2>MZ - NT Player Search</h2>
                </div>
                <div class="mz-search-grid">
                    ${skillsHTML}
                    <div class="mz-search-field">
                        <label>Min. TotalBalls</label>
                        <select name="totalBalls">
                            ${this.generateOptions(110, 9)}
                        </select>
                    </div>
                    <div class="mz-search-field">
                        <label>Min. Age</label>
                        <select name="minAge">
                            ${this.generateOptions(96, 16)}
                        </select>
                    </div>
                    <div class="mz-search-field">
                        <label>Max. Age</label>
                        <select name="maxAge">
                            ${this.generateOptions(96, 16)}
                        </select>
                    </div>
                    <div class="mz-country-select mz-search-field">
                        <label>Country</label>
                        <select name="country" required>
                            ${this.generateCountryOptions()}
                        </select>
                    </div>
                </div>
                <div class="mz-search-buttons">
                    <button class="mz-search-button">${씨발('go')}</button>
                    <button class="mz-results-button" style="display: none;">Show Results</button>
                </div>
                <div class="mz-progress">
                    <div class="mz-progress-info">
                        <span>Scanning players...</span>
                    </div>
                    <div class="mz-progress-bar">
                        <div class="mz-progress-fill"></div>
                    </div>
                </div>
                <div class="mz-search-log"></div>
                <a href="https://www.managerzone.com/?p=guestbook&uid=8577497"
                   class="mz-guestbook-link"
                   title="Questions?">
                    <i class="fa-solid fa-book"></i>
                </a>`;

            document.body.appendChild(searchContainer);
        }

        generateCountryOptions() {
            return `
                <option value="">Select country</option>
                ${this.countries.map(country => {
                const isSelected = country.code === this.userCountry;
                if (isSelected) {
                    this.searchValues.countryData = {
                        ntid: country.ntid,
                        cid: country.cid
                    };
                }
                const displayName = country.name === 'Czech Republic' ? 'Czechia' :
                    country.name === 'Macedonia' ? 'North Macedonia' :
                        country.name;
                return `
                        <option value="${country.code}"
                                data-ntid="${country.ntid}"
                                data-cid="${country.cid}"
                                ${isSelected ? 'selected' : ''}>
                            ${displayName}
                        </option>`;
            }).join('')}
            `;
        }

        generateOptions(max, min = 0) {
            return Array.from({ length: max - min + 1 }, (_, i) => {
                const value = i + min;
                return `<option value="${value}">${value}</option>`;
            }).join('');
        }

        handleSelectChange(e) {
            const select = e.target;
            if (select.name === 'country') {
                const option = select.selectedOptions[0];
                this.searchValues.country = select.value;
                this.searchValues.countryData = {
                    ntid: option.dataset.ntid,
                    cid: option.dataset.cid
                };
            } else {
                this.searchValues[select.name] = parseInt(select.value);
            }
        }

        setUpEvents() {
            const searchTab = document.querySelector('ul.ui-tabs-nav li:last-child');
            const searchContainer = document.querySelector('.mz-search-container');
            const searchButton = searchContainer.querySelector('.mz-search-button');
            const resultsButton = searchContainer.querySelector('.mz-results-button');
            const selects = searchContainer.querySelectorAll('select');

            selects.forEach(select => {
                select.addEventListener('change', (e) => this.handleSelectChange(e));
            });

            searchTab.addEventListener('click', (e) => {
                e.preventDefault();
                searchContainer.classList.add('visible');
            });

            document.addEventListener('click', (e) => {
                if (!searchContainer.contains(e.target) &&
                    !searchTab.contains(e.target) &&
                    searchContainer.classList.contains('visible')) {
                    searchContainer.classList.remove('visible');
                }
            });

            document.addEventListener('keydown', (e) => {
                if (e.key === 'Escape' && searchContainer.classList.contains('visible')) {
                    searchContainer.classList.remove('visible');
                }
            });

            searchButton.addEventListener('click', () => this.performSearch());
            resultsButton.addEventListener('click', () => this.showResults());
        }
    }

    new NTPlayerSearcher().init();
})();