// ==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 // @downloadURL none // ==/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}¤cy=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 = '×'; 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 = `