// ==UserScript==
// @name MZ - GB Message Sender
// @namespace douglaskampl
// @version 1.3
// @description Sends a guestbook message to multiple users
// @author Douglas
// @match https://www.managerzone.com/?p=guestbook
// @icon https://www.google.com/s2/favicons?sz=64&domain=managerzone.com
// @grant GM_addStyle
// @run-at document-idle
// @license MIT
// @downloadURL https://update.greasyfork.icu/scripts/528077/MZ%20-%20GB%20Message%20Sender.user.js
// @updateURL https://update.greasyfork.icu/scripts/528077/MZ%20-%20GB%20Message%20Sender.meta.js
// ==/UserScript==
(function () {
'use strict';
const BRAZIL_LEAGUE_IDS_RANGE = { start: 26187, end: 26307 };
const BRAZIL_CUP_IDS_1 = [32066, 32070, 32075, 32076, 32078, 32080];
const BRAZIL_CUP_ID_2 = 31762;
const CUP_DIVISIONS = [4, 5];
GM_addStyle(`
@import url('https://fonts.googleapis.com/css2?family=Montserrat:wght@400;700;900&display=swap');
:root {
--jec-red: #D20A10;
--jec-red-hover: #A9080D;
--jec-black: #1A1A1A;
--jec-white: #FFFFFF;
--mz-blue: #0076B8;
--mz-blue-hover: #005A8D;
--arg-blue: #74ACDF;
--arg-white: #FFFFFF;
--arg-gold: #F7B509;
--font-main: 'Montserrat', sans-serif;
}
.bbcode {
display: flex;
align-items: flex-start;
gap: 15px;
}
.markItUpContainer {
flex-grow: 1;
}
#gb-sender-panel {
flex-shrink: 0;
width: 220px;
border: 1px solid #333;
border-top: 3px solid var(--jec-red);
padding: 15px;
background: linear-gradient(145deg, #2e2e2e, #1a1a1a);
border-radius: 6px;
text-align: center;
box-shadow: 0 4px 15px rgba(0,0,0,0.3);
font-family: var(--font-main);
}
#gb-sender-panel h3 {
margin: 0 0 15px 0;
color: var(--jec-white);
font-weight: 700;
font-size: 16px;
border-bottom: 1px solid #444;
padding-bottom: 10px;
letter-spacing: 0.5px;
}
.custom-button { background-color: var(--mz-blue); color: var(--jec-white); position: relative; overflow: hidden; transition: all 0.3s ease; }
.custom-button:hover { background-color: var(--mz-blue-hover); transform: translateY(-2px); box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); }
.custom-button:after { content: ''; position: absolute; top: 0; left: -100%; width: 100%; height: 100%; background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent); transition: 0.5s; }
.custom-button:hover:after { left: 100%; }
.modal { font-family: var(--font-main); display: none; position: fixed; z-index: 9999; left: 0; top: 0; width: 100%; height: 100%; overflow: auto; background-color: rgba(0, 0, 0, 0.7); opacity: 0; transition: opacity 0.3s ease; }
.modal.show { opacity: 1; }
.modal-content { background-color: var(--jec-black); color: var(--jec-white); margin: 10% auto; padding: 25px; border: 2px solid var(--jec-red); width: 80%; max-width: 600px; border-radius: 8px; box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3); transform: translateY(-50px); opacity: 0; transition: all 0.4s ease; position: relative; }
.modal.show .modal-content { transform: translateY(0); opacity: 1; }
.modal-header { border-bottom: 3px solid var(--jec-red); padding-bottom: 15px; margin-bottom: 20px; display: flex; justify-content: space-between; align-items: center; }
.modal-title { font-weight: 900; color: var(--jec-white); margin: 0; font-size: 24px; text-shadow: 1px 1px 3px rgba(0, 0, 0, 0.3); }
.close { color: var(--jec-white); float: right; font-size: 28px; font-weight: bold; cursor: pointer; transition: color 0.2s ease; }
.close:hover, .close:focus { color: var(--jec-red); text-decoration: none; }
.form-group { margin-bottom: 20px; }
.form-group label { display: block; margin-bottom: 8px; font-weight: 700; color: var(--jec-red); }
.form-group input, .form-group textarea { width: 100%; padding: 12px; background-color: #2a2a2a; color: var(--jec-white); border: 1px solid #444; border-radius: 6px; box-sizing: border-box; transition: all 0.3s ease; font-family: var(--font-main); }
.form-group input:focus, .form-group textarea:focus { outline: none; border-color: var(--jec-red); box-shadow: 0 0 8px rgba(210, 10, 16, 0.5); }
.form-group textarea { height: 120px; resize: vertical; }
.send-button { font-family: var(--font-main); background: linear-gradient(to right, var(--jec-red), var(--jec-red-hover)); color: var(--jec-white); padding: 12px 20px; border: none; border-radius: 6px; cursor: pointer; font-weight: 700; letter-spacing: 0.5px; transition: all 0.3s ease; position: relative; overflow: hidden; }
.send-button:hover { background: linear-gradient(to right, var(--jec-red-hover), var(--jec-red)); transform: translateY(-2px); box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2); }
.send-button:disabled { background: #555; cursor: not-allowed; transform: none; box-shadow: none; }
.status-area { margin-top: 20px; padding: 15px; border: 1px solid #444; border-radius: 6px; max-height: 180px; overflow-y: auto; display: none; background-color: #222; transition: all 0.3s ease; }
.status-area.show { display: block; animation: fadeIn 0.5s ease forwards; }
.status-message { margin: 8px 0; padding: 5px 10px; border-radius: 4px; animation: slideIn 0.3s ease forwards; }
@keyframes slideIn { from { opacity: 0; transform: translateY(-10px); } to { opacity: 1; transform: translateY(0); } }
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
.status-success { color: #7CFC00; background-color: rgba(124, 252, 0, 0.1); border-left: 3px solid #7CFC00; }
.status-error { color: #FF6B6B; background-color: rgba(255, 107, 107, 0.1); border-left: 3px solid #FF6B6B; }
.status-info { color: #FFA500; background-color: rgba(255, 165, 0, 0.1); border-left: 3px solid #FFA500; }
.footer { margin-top: 20px; padding-top: 15px; border-top: 1px solid #333; text-align: center; color: #AAA; font-size: 14px; }
#gordola-credit { font-weight: 700; background-image: linear-gradient(to bottom, var(--arg-blue) 45%, var(--arg-white) 45%, var(--arg-white) 55%, var(--arg-blue) 55%); color: transparent; -webkit-background-clip: text; background-clip: text; text-shadow: 1px 1px 2px rgba(0,0,0,0.5); }
.progress-bar { height: 4px; width: 0%; background: linear-gradient(to right, var(--jec-red), var(--jec-white)); position: absolute; top: 0; left: 0; transition: width 0.5s ease; }
.toggle-container { display: flex; justify-content: center; margin-bottom: 20px; background-color: var(--jec-black); border: 1px solid #333; border-radius: 6px; padding: 4px; width: 100%; }
.toggle-option { flex: 1; padding: 10px; text-align: center; cursor: pointer; border-radius: 4px; transition: all 0.3s ease; color: #AAA; font-weight: 700; }
.toggle-option.active { background-color: var(--jec-red); color: var(--jec-white); box-shadow: inset 0 2px 5px rgba(0, 0, 0, 0.4); }
.toggle-option:not(.active):hover { background-color: #333; color: var(--jec-white); }
.input-container { display: none; }
.input-container.active { display: block; animation: fadeIn 0.3s ease; }
.spinner-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.6); z-index: 10000; display: flex; justify-content: center; align-items: center; border-radius: 8px; }
.spinner { border: 5px solid #f3f3f3; border-top: 5px solid var(--jec-red); border-radius: 50%; width: 50px; height: 50px; animation: spin 1s linear infinite; }
@keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
`);
window.addEventListener('load', function() {
const bbcodeContainer = document.querySelector('.bbcode');
if (!bbcodeContainer) return;
const senderPanel = document.createElement('div');
senderPanel.id = 'gb-sender-panel';
senderPanel.innerHTML = `
GB MESSAGE SENDER
Open
`;
bbcodeContainer.appendChild(senderPanel);
const modal = document.createElement('div');
modal.className = 'modal';
modal.id = 'messageModal';
modal.innerHTML = `
Users
Federation

BR Users
`;
document.body.appendChild(modal);
document.getElementById('sendMessagesBtn').addEventListener('click', function(e) {
e.preventDefault();
const modalEl = document.getElementById('messageModal');
modalEl.style.display = 'block';
setTimeout(() => modalEl.classList.add('show'), 10);
});
const toggleOptions = document.querySelectorAll('.toggle-option');
toggleOptions.forEach(option => {
option.addEventListener('click', function() {
toggleOptions.forEach(opt => opt.classList.remove('active'));
this.classList.add('active');
const targetId = this.getAttribute('data-target');
document.querySelectorAll('.input-container').forEach(container => container.classList.remove('active'));
document.getElementById(targetId).classList.add('active');
});
});
document.querySelector('.close').addEventListener('click', function() {
const modalEl = document.getElementById('messageModal');
modalEl.classList.remove('show');
setTimeout(() => { modalEl.style.display = 'none'; }, 300);
});
window.addEventListener('click', function(event) {
const modalEl = document.getElementById('messageModal');
if (event.target === modalEl) {
modalEl.classList.remove('show');
setTimeout(() => { modalEl.style.display = 'none'; }, 300);
}
});
const spinnerOverlay = document.getElementById('modal-spinner-overlay');
const showSpinner = () => { spinnerOverlay.style.display = 'flex'; };
const hideSpinner = () => { spinnerOverlay.style.display = 'none'; };
document.getElementById('sendButton').addEventListener('click', async function() {
const message = document.getElementById('messageText').value.trim();
if (!message) {
alert('Please enter a message.');
return;
}
const statusArea = document.getElementById('statusArea');
const statusMessages = document.getElementById('statusMessages');
const progressBar = document.getElementById('progressBar');
statusArea.classList.add('show');
statusMessages.innerHTML = '';
updateProgressBar(progressBar, 0);
const activeMode = document.querySelector('.toggle-option.active').getAttribute('data-target');
showSpinner();
try {
switch (activeMode) {
case 'users-input':
await handleManualUsers(message, progressBar);
break;
case 'federation-input':
await handleFederationUsers(message, progressBar);
break;
case 'brazil-users-input':
await handleBrazilianUsers(message, progressBar);
break;
}
} catch (error) {
addStatus(`A critical error occurred: ${error.message}`, 'status-error');
} finally {
hideSpinner();
}
});
async function handleManualUsers(message, progressBar) {
const usersText = document.getElementById('usersInput').value.trim();
if (!usersText) { alert('Please enter at least one username.'); return; }
const users = usersText.split(',').map(u => u.trim()).filter(Boolean);
if (users.length === 0) { alert('Please enter valid usernames.'); return; }
addStatus(`Found ${users.length} users in the list.`, 'status-info');
const userIds = await getUserIdsFromUsernames(users, progressBar);
await sendMessagesToUserIds(userIds, message, progressBar);
}
async function handleFederationUsers(message, progressBar) {
const federationId = document.getElementById('federationId').value.trim();
if (!federationId || !/^\d+$/.test(federationId)) { alert('Please enter a valid, numeric federation ID.'); return; }
addStatus(`Fetching members for federation ID: ${federationId}...`, 'status-info');
const users = await getFederationMembers(federationId);
if (users.length === 0) { addStatus('No users found in federation.', 'status-error'); return; }
addStatus(`Found ${users.length} federation members.`, 'status-info');
const userIds = await getUserIdsFromUsernames(users, progressBar);
await sendMessagesToUserIds(userIds, message, progressBar);
}
async function handleBrazilianUsers(message, progressBar) {
addStatus('Phase 1: Collecting Team IDs from Brazilian sources...', 'status-info');
const teamIds = new Set();
const leagueFetches = [];
for (let lid = BRAZIL_LEAGUE_IDS_RANGE.start; lid <= BRAZIL_LEAGUE_IDS_RANGE.end; lid++) {
leagueFetches.push(fetchTeamIdsFromLeague(lid));
}
const cupFetches = [];
for (const cid of BRAZIL_CUP_IDS_1) {
for (const div of CUP_DIVISIONS) { cupFetches.push(fetchTeamIdsFromCup(cid, div, 0)); }
}
for (const offset of [0, 20, 40]) { cupFetches.push(fetchTeamIdsFromCup(BRAZIL_CUP_ID_2, 4, offset)); }
for (const offset of [0, 20]) { cupFetches.push(fetchTeamIdsFromCup(BRAZIL_CUP_ID_2, 5, offset)); }
const allFetches = [...leagueFetches, ...cupFetches];
let completedFetches = 0;
const fetchPromises = allFetches.map(p => p.then(newTeamIds => {
newTeamIds.forEach(id => teamIds.add(id));
completedFetches++;
updateProgressBar(progressBar, (completedFetches / allFetches.length) * 50);
return newTeamIds;
}));
await Promise.all(fetchPromises);
addStatus(`Collected ${teamIds.size} unique team IDs.`, 'status-success');
addStatus('Phase 2: Converting Team IDs to User IDs...', 'status-info');
const userIds = new Set();
const teamIdArray = Array.from(teamIds);
let processedTeams = 0;
for (const teamId of teamIdArray) {
const userId = await getUserIdByTeamId(teamId);
if (userId) { userIds.add(userId); }
processedTeams++;
updateProgressBar(progressBar, 50 + (processedTeams / teamIdArray.length) * 25);
}
addStatus(`Found ${userIds.size} unique active users.`, 'status-success');
addStatus('Phase 3: Sending messages...', 'status-info');
await sendMessagesToUserIds(Array.from(userIds), message, progressBar, 75);
}
async function getUserIdsFromUsernames(usernames, progressBar) {
const userIds = new Set();
let processedCount = 0;
const promises = usernames.map(username => getUserIdByUsername(username).then(userId => {
if (userId) {
userIds.add(userId);
addStatus(`Resolved ${username} -> User ID: ${userId}`, 'status-info');
} else {
addStatus(`Could not find User ID for ${username}`, 'status-error');
}
processedCount++;
updateProgressBar(progressBar, (processedCount / usernames.length) * 100);
}));
await Promise.all(promises);
return Array.from(userIds);
}
async function sendMessagesToUserIds(userIds, message, progressBar, baseProgress = 0) {
let processedMessages = 0;
const totalMessages = userIds.length;
if (totalMessages === 0) {
addStatus('No valid users to send messages to.', 'status-error');
updateProgressBar(progressBar, 100);
return;
}
for (const userId of userIds) {
try {
await sendMessage(userId, message);
addStatus(`Message sent to User ID: ${userId}`, 'status-success');
} catch (error) {
addStatus(`Error sending to User ID ${userId}: ${error.message}`, 'status-error');
}
processedMessages++;
const progress = baseProgress + (processedMessages / totalMessages) * (100 - baseProgress);
updateProgressBar(progressBar, progress);
if (processedMessages < totalMessages) {
addStatus(`Waiting 5 seconds...`, 'status-info');
await new Promise(resolve => setTimeout(resolve, 5000));
}
}
addStatus('All messages have been sent!', 'status-success');
updateProgressBar(progressBar, 100);
}
async function fetchTeamIdsFromLeague(leagueId) {
try {
const response = await fetch(`https://www.managerzone.com/xml/team_league.php?sport_id=1&league_id=${leagueId}`);
const xmlText = await response.text();
return Array.from(xmlText.matchAll(/]*teamId="(\d+)"[^>]*>/g)).map(match => match[1]);
} catch (error) { return []; }
}
async function fetchTeamIdsFromCup(cupId, division, offset) {
try {
const response = await fetch(`https://www.managerzone.com/?p=cups&sub=find_participants&cid=${cupId}&div=${division}&offset=${offset}`, { method: 'POST' });
const htmlText = await response.text();
return Array.from(htmlText.matchAll(/tid=(\d+)/g)).map(match => match[1]);
} catch (error) { return []; }
}
async function getFederationMembers(federationId) {
const users = new Set();
let offset = 0;
let hasMoreMembers = true;
while (hasMoreMembers) {
try {
const url = `https://www.managerzone.com/ajax.php?p=federations&sub=federation_members&fid=${federationId}&offset=${offset}&sport=soccer`;
const response = await fetch(url);
const data = await response.json();
if (!data[0] || !data[0].length) { hasMoreMembers = false; break; }
const newUsers = Array.from(data[0].matchAll(/([^<]+)<\/a>/g)).map(match => match[1].trim());
if (newUsers.length === 0) { hasMoreMembers = false; break; }
newUsers.forEach(user => users.add(user));
hasMoreMembers = data[1] && data[1].includes(`offset=${offset + 10}`);
offset += 10;
if(hasMoreMembers) await new Promise(resolve => setTimeout(resolve, 1000));
} catch (error) { hasMoreMembers = false; }
}
return Array.from(users);
}
function addStatus(message, className) {
const statusMessages = document.getElementById('statusMessages');
const messageElement = document.createElement('div');
messageElement.className = `status-message ${className}`;
messageElement.textContent = message;
statusMessages.appendChild(messageElement);
statusMessages.scrollTop = statusMessages.scrollHeight;
}
function updateProgressBar(progressBar, percentage) {
progressBar.style.width = `${Math.min(100, percentage)}%`;
}
async function getUserIdByUsername(username) {
try {
const response = await fetch(`https://www.managerzone.com/xml/manager_data.php?sport_id=1&username=${encodeURIComponent(username)}`);
const text = await response.text();
const match = text.match(/]*userId="(\d+)"/);
return match ? match[1] : null;
} catch (error) { return null; }
}
async function getUserIdByTeamId(teamId) {
try {
const response = await fetch(`https://www.managerzone.com/xml/manager_data.php?sport_id=1&team_id=${teamId}`);
const text = await response.text();
const match = text.match(/]*userId="(\d+)"/);
return match ? match[1] : null;
} catch (error) { return null; }
}
async function sendMessage(userId, message) {
const response = await fetch(`https://www.managerzone.com/ajax.php?p=messageBoard&sub=write&template=1&ident_id=${userId}&sport=soccer`, {
method: 'POST',
body: new URLSearchParams({ 'msg': message }),
headers: {
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
'X-Requested-With': 'XMLHttpRequest'
}
});
if (!response.ok) { throw new Error(`Server responded with status ${response.status}`); }
return true;
}
});
})();