// ==UserScript==
// @name MZ - Country Transfer Monitor
// @namespace douglaskampl
// @version 1.0
// @description Monitors transfers 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;
cursor: pointer;
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;
}
#country-monitor-flag:hover {
transform: scale(1.1);
}
#country-monitor-name {
margin-left: 5px;
font-size: 12px;
color: #666;
display: none;
}
#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;
}
`);
const CONFIG = {
CHECK_INTERVAL_HOURS: 24,
TOAST_DURATION: 5000,
DEFAULT_COUNTRY_NAME: 'Country',
DEFAULT_COUNTRY_CID: null,
COUNTRIES_JSON_URL: 'https://pub-02de1c06eac643f992bb26daeae5c7a0.r2.dev/json/countries.json'
};
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);
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 => {
console.error('Error fetching countries:', 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 = "Monitor players";
const countryName = document.createElement('span');
countryName.id = 'country-monitor-name';
countryName.textContent = selectedCountry.name;
monitorWrapper.appendChild(flagImg);
monitorWrapper.appendChild(countryName);
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);
monitorWrapper.addEventListener('click', (e) => {
if (e.target.id !== 'country-search') {
if (selector.classList.contains('visible')) {
hideSelector(selector);
} else {
showSelector(selector);
search.focus();
}
}
});
document.addEventListener('click', (e) => {
if (!monitorWrapper.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 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) {}
}
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);
}
}
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}`;
}
});
}
fetchCountries();
setInterval(checkTransferMarket, 30 * 60 * 1000);
})();