您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Greasy Fork is available in English.
Monitors the transfer market for players from a specific country
当前为
// ==UserScript== // @name MZ - Country Transfer Monitor // @namespace douglaskampl // @version 1.3 // @description Monitors the transfer market for players from a specific country // @author Douglas // @match https://www.managerzone.com/* // @icon https://www.google.com/s2/favicons?sz=64&domain=managerzone.com // @grant GM_setValue // @grant GM_getValue // @grant GM_addStyle // @grant GM_getResourceText // @require https://cdn.jsdelivr.net/npm/sweetalert2@11 // @resource SWEETALERT2_CSS https://cdn.jsdelivr.net/npm/sweetalert2@11/dist/sweetalert2.min.css // @run-at document-idle // @license MIT // ==/UserScript== (function () { 'use strict'; GM_addStyle(GM_getResourceText('SWEETALERT2_CSS')); GM_addStyle(` #country-monitor-wrapper { display: flex; align-items: center; margin-left: 15px; position: relative; } #country-monitor-flag { width: 22px; height: 17px; object-fit: contain; vertical-align: middle; border: 1px solid #ddd; border-radius: 2px; transition: transform 0.2s ease; cursor: pointer; } #country-monitor-flag:hover { transform: scale(1.1); } #country-monitor-name { margin-left: 5px; font-size: 12px; color: #666; display: none; } #rare-finder-btn { margin-left: 10px; width: 24px; height: 24px; display: flex; align-items: center; justify-content: center; background: #3498db; color: white; border-radius: 50%; cursor: pointer; transition: background 0.2s ease, transform 0.2s ease; font-size: 14px; } #rare-finder-btn:hover { background: #2980b9; transform: scale(1.1); } #rare-countries-results { display: flex; flex-wrap: wrap; margin-left: 10px; align-items: center; } .rare-country-result { display: flex; align-items: center; margin-right: 10px; background: rgba(52, 152, 219, 0.1); padding: 4px 8px; border-radius: 12px; cursor: pointer; transition: background 0.2s; } .rare-country-result:hover { background: rgba(52, 152, 219, 0.2); } .rare-country-result img { width: 16px; height: 12px; border: 1px solid #ddd; margin-right: 5px; } .rare-country-result span { font-size: 11px; color: #e74c3c; font-weight: bold; } #country-selector { position: absolute; top: 100%; right: 0; z-index: 9999; background: #2c3e50; color: #fff; border: 1px solid #34495e; border-radius: 4px; box-shadow: 0 4px 15px rgba(0,0,0,0.2); width: 300px; max-height: 400px; overflow-y: auto; padding: 10px; display: none; opacity: 0; transform: translateY(-10px); transition: opacity 0.3s ease, transform 0.3s ease; } #country-selector.visible { opacity: 1; transform: translateY(0); } #country-selector::-webkit-scrollbar { width: 8px; } #country-selector::-webkit-scrollbar-track { background: #34495e; border-radius: 4px; } #country-selector::-webkit-scrollbar-thumb { background: #7f8c8d; border-radius: 4px; } #country-selector::-webkit-scrollbar-thumb:hover { background: #95a5a6; } #country-search { width: 100%; padding: 8px; margin-bottom: 10px; border: 1px solid #34495e; background-color: #34495e; color: white; border-radius: 4px; } #country-search::placeholder { color: #bdc3c7; } .country-option { display: flex; align-items: center; padding: 6px 10px; cursor: pointer; border-radius: 4px; color: #ecf0f1; } .country-option:hover { background: #34495e; } .country-option img { width: 24px; height: 16px; margin-right: 10px; object-fit: contain; border: 1px solid #34495e; } .country-option.selected { background: #3498db; } #search-status { margin-left: 10px; font-size: 12px; color: #95a5a6; } .rare-country-item { display: flex; align-items: center; margin-bottom: 8px; padding: 8px; border-radius: 4px; background: rgba(52, 73, 94, 0.1); cursor: pointer; } .rare-country-item:hover { background: rgba(52, 73, 94, 0.2); } .rare-country-flag { width: 24px; height: 16px; margin-right: 10px; border: 1px solid #ddd; } .rare-country-name { flex-grow: 1; } .rare-country-count { font-weight: bold; color: #e74c3c; margin-left: 10px; } .swal2-html-container { max-height: 400px; overflow-y: auto; } `); const CONFIG = { CHECK_INTERVAL_HOURS: 6, TOAST_DURATION: 5000, DEFAULT_COUNTRY_NAME: 'Country', DEFAULT_COUNTRY_CID: null, COUNTRIES_JSON_URL: 'https://pub-02de1c06eac643f992bb26daeae5c7a0.r2.dev/json/countries.json', MAX_RARE_PLAYERS: 15 }; let countries = []; let selectedCountry = { cid: GM_getValue('selectedCountryCid', CONFIG.DEFAULT_COUNTRY_CID), name: GM_getValue('selectedCountryName', CONFIG.DEFAULT_COUNTRY_NAME), code: GM_getValue('selectedCountryCode', '') }; let knownPlayers = GM_getValue('knownPlayers', {}); let lastChecked = GM_getValue('lastChecked', 0); let isScanning = false; function getFlagUrl(code) { const upperCode = (code || '').toUpperCase(); if (upperCode === 'SC') { return 'https://cdn.jsdelivr.net/gh/lipis/flag-icons/flags/4x3/gb-sct.svg'; } else if (upperCode === 'WL') { return 'https://cdn.jsdelivr.net/gh/lipis/flag-icons/flags/4x3/gb-wls.svg'; } else if (upperCode === 'NI') { return 'https://cdn.jsdelivr.net/gh/lipis/flag-icons/flags/4x3/gb-nir.svg'; } else if (upperCode === 'EN') { return 'https://cdn.jsdelivr.net/gh/lipis/flag-icons/flags/4x3/gb-eng.svg'; } else if (upperCode === 'DC') { return 'https://placehold.co/16x12/gray/white?text=MZ'; } return `https://flagcdn.com/16x12/${(code || '').toLowerCase()}.png`; } function cleanupKnownPlayers() { const now = Date.now(); const threeDaysInMs = 3 * 24 * 60 * 60 * 1000; let modified = false; Object.keys(knownPlayers).forEach(playerId => { if (now - knownPlayers[playerId].firstSeen > threeDaysInMs) { delete knownPlayers[playerId]; modified = true; } }); if (modified) { GM_setValue('knownPlayers', knownPlayers); } } function fetchCountries() { fetch(CONFIG.COUNTRIES_JSON_URL) .then(response => response.json()) .then(data => { countries = data; countries.sort((a, b) => a.name.localeCompare(b.name)); buildUI(); }) .catch(error => {}); } function buildUI() { if (!isTransferPage()) return; const searchButton = document.getElementById('tds'); if (!searchButton) return; const monitorWrapper = document.createElement('div'); monitorWrapper.id = 'country-monitor-wrapper'; monitorWrapper.style.display = 'inline-block'; monitorWrapper.style.marginLeft = '10px'; const flagImg = document.createElement('img'); flagImg.id = 'country-monitor-flag'; flagImg.src = selectedCountry.code ? getFlagUrl(selectedCountry.code) : 'https://placehold.co/16x12/gray/white?text=?'; flagImg.alt = selectedCountry.name; flagImg.title = "Select country to monitor"; const rareFinderBtn = document.createElement('div'); rareFinderBtn.id = 'rare-finder-btn'; rareFinderBtn.innerHTML = '🔍'; rareFinderBtn.title = 'Find countries with < 5 19-year-olds'; const rareCountriesResults = document.createElement('div'); rareCountriesResults.id = 'rare-countries-results'; const searchStatus = document.createElement('span'); searchStatus.id = 'search-status'; searchStatus.style.display = 'none'; const countryName = document.createElement('span'); countryName.id = 'country-monitor-name'; countryName.textContent = selectedCountry.name; monitorWrapper.appendChild(flagImg); monitorWrapper.appendChild(countryName); monitorWrapper.appendChild(rareFinderBtn); monitorWrapper.appendChild(searchStatus); monitorWrapper.appendChild(rareCountriesResults); const selector = document.createElement('div'); selector.id = 'country-selector'; const search = document.createElement('input'); search.id = 'country-search'; search.type = 'text'; search.placeholder = 'Search country...'; selector.appendChild(search); countries.forEach(country => { const option = document.createElement('div'); option.className = 'country-option'; if (selectedCountry.cid === country.cid) { option.classList.add('selected'); } const optionImg = document.createElement('img'); optionImg.src = getFlagUrl(country.code); optionImg.alt = country.name; const optionName = document.createElement('span'); optionName.textContent = country.name; option.appendChild(optionImg); option.appendChild(optionName); option.addEventListener('click', () => { selectCountry(country); hideSelector(selector); }); selector.appendChild(option); }); search.addEventListener('input', function() { const query = this.value.toLowerCase(); Array.from(selector.querySelectorAll('.country-option')).forEach(option => { const name = option.querySelector('span').textContent.toLowerCase(); option.style.display = name.includes(query) ? 'flex' : 'none'; }); }); monitorWrapper.appendChild(selector); flagImg.addEventListener('click', (e) => { if (selector.classList.contains('visible')) { hideSelector(selector); } else { showSelector(selector); search.focus(); } }); rareFinderBtn.addEventListener('click', () => { findRareCountriesInline(); }); document.addEventListener('click', (e) => { if (!monitorWrapper.contains(e.target) || (e.target !== flagImg && !selector.contains(e.target))) { hideSelector(selector); } }); searchButton.insertAdjacentElement('afterend', monitorWrapper); if (selectedCountry.cid) { checkTransferMarket(false); } } function showSelector(selector) { selector.style.display = 'block'; selector.offsetHeight; selector.classList.add('visible'); } function hideSelector(selector) { selector.classList.remove('visible'); setTimeout(() => { if (!selector.classList.contains('visible')) { selector.style.display = 'none'; } }, 300); } function selectCountry(country) { selectedCountry = { cid: country.cid, name: country.name, code: country.code }; GM_setValue('selectedCountryCid', country.cid); GM_setValue('selectedCountryName', country.name); GM_setValue('selectedCountryCode', country.code); const flagImg = document.getElementById('country-monitor-flag'); if (flagImg) { flagImg.src = getFlagUrl(country.code); flagImg.alt = country.name; flagImg.title = "Monitor players from: " + country.name; } const countryName = document.getElementById('country-monitor-name'); if (countryName) { countryName.textContent = country.name; } checkTransferMarket(true); } function isTransferPage() { return window.location.href.includes('p=transfer'); } function isTransferSearchPage19() { return window.location.href.includes('p=transfer') && window.location.href.includes('sub=search') && window.location.href.includes('agea=19') && window.location.href.includes('ageb=19'); } function checkTransferMarket(forced = false) { if (!selectedCountry.cid) return; const now = Date.now(); const checkIntervalMs = CONFIG.CHECK_INTERVAL_HOURS * 60 * 60 * 1000; if (!forced && (now - lastChecked < checkIntervalMs)) { return; } cleanupKnownPlayers(); fetch(`https://www.managerzone.com/ajax.php?p=transfer&sub=transfer-search&sport=soccer&issearch=true&u=&nationality=${selectedCountry.cid}&deadline=0&category=&valuea=&valueb=&bida=&bidb=&agea=19&ageb=37&birth_season_low=56&birth_season_high=74&tot_low=0&tot_high=110&s0a=0&s0b=10&s1a=0&s1b=10&s2a=0&s2b=10&s3a=0&s3b=10&s4a=0&s4b=10&s5a=0&s5b=10&s6a=0&s6b=10&s7a=0&s7b=10&s8a=0&s8b=10&s9a=0&s9b=10&s10a=0&s10b=10&s11a=0&s11b=10&s12a=0&s12b=10`) .then(response => response.json()) .then(data => { lastChecked = now; GM_setValue('lastChecked', lastChecked); if (data.totalHits && parseInt(data.totalHits) > 0) { processPlayers(data); } }) .catch(error => {}); } function processPlayers(data) { if (!data.players || data.players.includes("No players found")) { return; } const playersData = data.players; const pidRegex = /href=["'].*?pid=(\d+)/g; let pidMatches = [...playersData.matchAll(pidRegex)]; const playerIdSpanRegex = /player_id_(\d+)/g; let playerIdSpanMatches = [...playersData.matchAll(playerIdSpanRegex)]; const idRegex = /id: (\d+)/g; let idMatches = [...playersData.matchAll(idRegex)]; let allPlayerIds = new Set([ ...pidMatches.map(match => match[1]), ...playerIdSpanMatches.map(match => match[1]), ...idMatches.map(match => match[1]) ]); const playerNameRegex1 = /"([^"]+)"\s+([^<]+)<\/span>/g; const playerNameRegex2 = /player_name">([^<]+)<\/span>/g; let nameMatches1 = [...playersData.matchAll(playerNameRegex1)]; let nameMatches2 = [...playersData.matchAll(playerNameRegex2)]; let players = []; if (nameMatches1.length > 0) { nameMatches1.forEach((match, index) => { if (index < allPlayerIds.size) { const playerName = match[2] ? match[2].trim() : match[1].trim(); const playerId = Array.from(allPlayerIds)[index]; players.push({ id: playerId, name: playerName }); } }); } else if (nameMatches2.length > 0) { nameMatches2.forEach((match, index) => { if (index < allPlayerIds.size) { const playerName = match[1].trim(); const playerId = Array.from(allPlayerIds)[index]; players.push({ id: playerId, name: playerName }); } }); } else if (allPlayerIds.size > 0) { Array.from(allPlayerIds).forEach(id => { players.push({ id: id, name: id }); }); } if (players.length === 0) { try { const jsonString = JSON.stringify(data); const jsonIdRegex = /\d{9}/g; const jsonIdMatches = [...new Set([...jsonString.matchAll(jsonIdRegex)].map(m => m[0]))]; jsonIdMatches.forEach(id => { players.push({ id: id, name: id }); }); } catch (e) {} } if (!isScanning) { const newPlayers = players.filter(player => !knownPlayers[player.id]); if (newPlayers.length > 0) { showPlayerNotification(newPlayers); newPlayers.forEach(player => { knownPlayers[player.id] = { k: player.name, firstSeen: Date.now() }; }); GM_setValue('knownPlayers', knownPlayers); } } return players.length; } function showPlayerNotification(players) { const htmlContent = `<p>New players from ${selectedCountry.name} found (${players.length}). Click "View" to see them on the transfer market.</p>`; Swal.fire({ title: 'New Players!', html: htmlContent, icon: 'info', showCancelButton: true, confirmButtonText: 'View', cancelButtonText: 'Close' }).then((result) => { if (result.isConfirmed) { window.location.href = `https://www.managerzone.com/?p=transfer&sub=search&sport=soccer&nationality=${selectedCountry.cid}`; } }); } function countPlayersFromCountry(country) { return new Promise((resolve, reject) => { fetch(`https://www.managerzone.com/ajax.php?p=transfer&sub=transfer-search&sport=soccer&issearch=true&u=&nationality=${country.cid}&deadline=0&category=&valuea=&valueb=&bida=&bidb=&agea=19&ageb=19&birth_season_low=56&birth_season_high=74&tot_low=0&tot_high=110&s0a=0&s0b=10&s1a=0&s1b=10&s2a=0&s2b=10&s3a=0&s3b=10&s4a=0&s4b=10&s5a=0&s5b=10&s6a=0&s6b=10&s7a=0&s7b=10&s8a=0&s8b=10&s9a=0&s9b=10&s10a=0&s10b=10&s11a=0&s11b=10&s12a=0&s12b=10`) .then(response => response.json()) .then(data => { let playerCount = 0; if (data.totalHits) { playerCount = parseInt(data.totalHits); } resolve({ cid: country.cid, name: country.name, code: country.code, playerCount: playerCount }); }) .catch(() => { resolve({ cid: country.cid, name: country.name, code: country.code, playerCount: 0 }); }); }); } function findRareCountriesInline() { if (isScanning) return; isScanning = true; const rareCountriesResults = document.getElementById('rare-countries-results'); const searchStatus = document.getElementById('search-status'); rareCountriesResults.innerHTML = ''; searchStatus.textContent = 'Scanning countries...'; searchStatus.style.display = 'inline'; const rareCountries = []; let processed = 0; async function runBatch(startIdx, batchSize) { const endIdx = Math.min(startIdx + batchSize, countries.length); const batch = countries.slice(startIdx, endIdx); const promises = batch.map(country => countPlayersFromCountry(country)); try { const results = await Promise.all(promises); processed += results.length; searchStatus.textContent = `Scanned ${processed}/${countries.length}...`; results.forEach(result => { if (result.playerCount > 0 && result.playerCount < CONFIG.MAX_RARE_PLAYERS) { rareCountries.push(result); const countryItem = document.createElement('div'); countryItem.className = 'rare-country-result'; countryItem.title = result.name; countryItem.dataset.cid = result.cid; const flagImg = document.createElement('img'); flagImg.src = getFlagUrl(result.code); flagImg.alt = result.name; const countSpan = document.createElement('span'); countSpan.textContent = result.playerCount; countryItem.appendChild(flagImg); countryItem.appendChild(countSpan); countryItem.addEventListener('click', () => { window.open(`https://www.managerzone.com/?p=transfer&sub=search&sport=soccer&nationality=${result.cid}&agea=19&ageb=19`, '_blank'); }); rareCountriesResults.appendChild(countryItem); } }); if (endIdx < countries.length) { setTimeout(() => runBatch(endIdx, batchSize), 1000); } else { finishScanning(rareCountries); } } catch (error) { if (endIdx < countries.length) { setTimeout(() => runBatch(endIdx, batchSize), 1000); } else { finishScanning(rareCountries); } } } function finishScanning(rareCountries) { isScanning = false; if (rareCountries.length === 0) { searchStatus.textContent = 'No rare countries found'; setTimeout(() => { searchStatus.style.display = 'none'; }, 3000); } else { searchStatus.textContent = `Found ${rareCountries.length} countries`; setTimeout(() => { searchStatus.style.display = 'none'; }, 3000); } } runBatch(0, 5); } function findRareCountries() { if (isScanning) return; isScanning = true; Swal.fire({ title: 'Scanning Countries', html: 'Looking for countries with fewer than 5 19-year-old players on the market.<br>This may take a while...', allowOutsideClick: false, position: 'top-end', didOpen: () => { Swal.showLoading(); } }); const rareCountries = []; let processed = 0; async function runBatch(startIdx, batchSize) { const endIdx = Math.min(startIdx + batchSize, countries.length); const batch = countries.slice(startIdx, endIdx); const promises = batch.map(country => countPlayersFromCountry(country)); try { const results = await Promise.all(promises); processed += results.length; Swal.getHtmlContainer().innerHTML = `Scanned ${processed}/${countries.length} countries...<br>Found ${rareCountries.length} rare countries so far.`; results.forEach(result => { if (result.playerCount > 0 && result.playerCount < CONFIG.MAX_RARE_PLAYERS) { rareCountries.push(result); } }); if (endIdx < countries.length) { setTimeout(() => runBatch(endIdx, batchSize), 1000); } else { displayRareCountries(rareCountries); isScanning = false; } } catch (error) { if (endIdx < countries.length) { setTimeout(() => runBatch(endIdx, batchSize), 1000); } else { displayRareCountries(rareCountries); isScanning = false; } } } runBatch(0, 5); } function displayRareCountries(rareCountries) { if (rareCountries.length === 0) { Swal.fire({ title: 'No Rare Countries Found', text: 'Could not find any countries with fewer than 5 19-year-old players on the market.', icon: 'info', position: 'top-end' }); return; } rareCountries.sort((a, b) => a.playerCount - b.playerCount); let htmlContent = `<p>Found ${rareCountries.length} countries with fewer than 5 19-year-old players:</p><div style="margin-top: 15px;">`; rareCountries.forEach(country => { htmlContent += ` <div class="rare-country-item" data-cid="${country.cid}"> <img src="${getFlagUrl(country.code)}" class="rare-country-flag" alt="${country.name}"> <span class="rare-country-name">${country.name}</span> <span class="rare-country-count">${country.playerCount}</span> </div> `; }); htmlContent += '</div>'; Swal.fire({ title: 'Rare Countries - 19-Year-Olds', html: htmlContent, icon: 'success', confirmButtonText: 'Close', width: 400, position: 'top-end', didOpen: () => { document.querySelectorAll('.rare-country-item').forEach(item => { item.addEventListener('click', () => { const cid = item.getAttribute('data-cid'); const url = `https://www.managerzone.com/?p=transfer&sub=search&sport=soccer&nationality=${cid}&agea=19&ageb=19`; window.open(url, '_blank'); }); }); } }); } function initAgeButtonClick() { if (!isTransferSearchPage19()) return; const x = 30; setTimeout(() => { const ageButton = document.getElementById('ageButton19'); if (ageButton) { console.log('Clicking age button'); ageButton.click(); } else { console.log('Age button not found after timeout'); } }, x * 1000); } function checkPageAndInit() { if (isTransferPage()) { fetchCountries(); } if (isTransferSearchPage19()) { initAgeButtonClick(); } } checkPageAndInit(); setInterval(checkTransferMarket, 30 * 60 * 1000); let lastUrl = location.href; new MutationObserver(() => { const url = location.href; if (url !== lastUrl) { lastUrl = url; setTimeout(checkPageAndInit, 500); } }).observe(document, {subtree: true, childList: true}); })();