MZ - GB Message Sender

Sends a guestbook message to multiple users

  1. // ==UserScript==
  2. // @name MZ - GB Message Sender
  3. // @namespace douglaskampl
  4. // @version 1.6
  5. // @description Sends a guestbook message to multiple users
  6. // @author Douglas
  7. // @match https://www.managerzone.com/?p=guestbook
  8. // @icon https://www.google.com/s2/favicons?sz=64&domain=managerzone.com
  9. // @grant GM_addStyle
  10. // @run-at document-idle
  11. // @license MIT
  12. // ==/UserScript==
  13.  
  14. (function () {
  15. 'use strict';
  16.  
  17. const SCRIPT_ID = 'GB Message Sender';
  18. const Logger = {
  19. log: (message) => console.log(`[${SCRIPT_ID}] ${message}`),
  20. info: (message) => console.info(`[${SCRIPT_ID}] INFO: ${message}`),
  21. warn: (message) => console.warn(`[${SCRIPT_ID}] WARNING: ${message}`),
  22. error: (message, errorObject) => console.error(`[${SCRIPT_ID}] ERROR: ${message}`, errorObject || ''),
  23. groupStart: (label) => console.groupCollapsed(`[${SCRIPT_ID}] ${label}`),
  24. groupEnd: () => console.groupEnd(),
  25. };
  26.  
  27. const BRAZIL_LEAGUE_IDS_RANGE = { start: 26187, end: 26307 };
  28. const BRAZIL_CUP_IDS_1 = [32066, 32070, 32075, 32076, 32078, 32080];
  29. const BRAZIL_CUP_ID_2 = 31762;
  30. const CUP_DIVISIONS = [4, 5];
  31.  
  32. GM_addStyle(`
  33. @import url('https://fonts.googleapis.com/css2?family=Montserrat:wght@400;700;900&display=swap');
  34.  
  35. :root {
  36. --jec-red: #D20A10;
  37. --jec-red-hover: #A9080D;
  38. --jec-black: #1A1A1A;
  39. --jec-white: #FFFFFF;
  40. --mz-blue: #0076B8;
  41. --mz-blue-hover: #005A8D;
  42. --arg-blue: #74ACDF;
  43. --arg-white: #FFFFFF;
  44. --arg-gold: #F7B509;
  45. --font-main: 'Montserrat', sans-serif;
  46. }
  47. .bbcode {
  48. display: flex;
  49. align-items: flex-start;
  50. gap: 15px;
  51. }
  52. .markItUpContainer {
  53. flex-grow: 1;
  54. }
  55. #gb-sender-panel {
  56. flex-shrink: 0;
  57. width: 220px;
  58. border: 1px solid #333;
  59. border-top: 3px solid var(--jec-red);
  60. padding: 15px;
  61. background: linear-gradient(145deg, #2e2e2e, #1a1a1a);
  62. border-radius: 6px;
  63. text-align: center;
  64. box-shadow: 0 4px 15px rgba(0,0,0,0.3);
  65. font-family: var(--font-main);
  66. }
  67. #gb-sender-panel h3 {
  68. margin: 0 0 15px 0;
  69. color: var(--jec-white);
  70. font-weight: 700;
  71. font-size: 16px;
  72. border-bottom: 1px solid #444;
  73. padding-bottom: 10px;
  74. letter-spacing: 0.5px;
  75. }
  76. .custom-button { background-color: var(--mz-blue); color: var(--jec-white); position: relative; overflow: hidden; transition: all 0.3s ease; }
  77. .custom-button:hover { background-color: var(--mz-blue-hover); transform: translateY(-2px); box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); }
  78. .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; }
  79. .custom-button:hover:after { left: 100%; }
  80. .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; }
  81. .modal.show { opacity: 1; }
  82. .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; }
  83. .modal.show .modal-content { transform: translateY(0); opacity: 1; }
  84. .modal-header { border-bottom: 3px solid var(--jec-red); padding-bottom: 15px; margin-bottom: 20px; display: flex; justify-content: space-between; align-items: center; }
  85. .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); }
  86. .close { color: var(--jec-white); float: right; font-size: 28px; font-weight: bold; cursor: pointer; transition: color 0.2s ease; }
  87. .close:hover, .close:focus { color: var(--jec-red); text-decoration: none; }
  88. .form-group { margin-bottom: 20px; }
  89. .form-group label { display: block; margin-bottom: 8px; font-weight: 700; color: var(--jec-red); }
  90. .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); }
  91. .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); }
  92. .form-group textarea { height: 120px; resize: vertical; }
  93. .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; }
  94. .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); }
  95. .send-button:disabled { background: #555; color: #999; cursor: not-allowed; transform: none; box-shadow: none; }
  96. .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; }
  97. .status-area.show { display: block; animation: fadeIn 0.5s ease forwards; }
  98. .status-message { margin: 8px 0; padding: 5px 10px; border-radius: 4px; animation: slideIn 0.3s ease forwards; }
  99. @keyframes slideIn { from { opacity: 0; transform: translateY(-10px); } to { opacity: 1; transform: translateY(0); } }
  100. @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
  101. .status-success { color: #7CFC00; background-color: rgba(124, 252, 0, 0.1); border-left: 3px solid #7CFC00; }
  102. .status-error { color: #FF6B6B; background-color: rgba(255, 107, 107, 0.1); border-left: 3px solid #FF6B6B; }
  103. .status-info { color: #FFA500; background-color: rgba(255, 165, 0, 0.1); border-left: 3px solid #FFA500; }
  104. .footer { margin-top: 20px; padding-top: 15px; border-top: 1px solid #333; text-align: center; color: #AAA; font-size: 14px; }
  105. #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); }
  106. .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; }
  107. .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%; }
  108. .toggle-option { flex: 1; padding: 10px; text-align: center; cursor: pointer; border-radius: 4px; transition: all 0.3s ease; color: #AAA; font-weight: 700; }
  109. .toggle-option.active { background-color: var(--jec-red); color: var(--jec-white); box-shadow: inset 0 2px 5px rgba(0, 0, 0, 0.4); }
  110. .toggle-option:not(.active):hover { background-color: #333; color: var(--jec-white); }
  111. .input-container { display: none; }
  112. .input-container.active { display: block; animation: fadeIn 0.3s ease; }
  113. `);
  114.  
  115. window.addEventListener('load', function() {
  116. Logger.log('Script loaded. Initializing.');
  117. const bbcodeContainer = document.querySelector('.bbcode');
  118. if (!bbcodeContainer) {
  119. Logger.error('Could not find the target .bbcode container. Script will not run.');
  120. return;
  121. }
  122.  
  123. const senderPanel = document.createElement('div');
  124. senderPanel.id = 'gb-sender-panel';
  125. senderPanel.innerHTML = `
  126. <h3>GB MESSAGE SENDER</h3>
  127. <a href="#" class="mzbtn button_account buttondiv custom-button" id="sendMessagesBtn">
  128. <span class="buttonClassMiddle"><span style="white-space: nowrap">Open</span></span><span class="buttonClassRight">&nbsp;</span>
  129. </a>
  130. `;
  131. bbcodeContainer.appendChild(senderPanel);
  132. Logger.log('Sender panel injected into the page.');
  133.  
  134. const modal = document.createElement('div');
  135. modal.className = 'modal';
  136. modal.id = 'messageModal';
  137. modal.innerHTML = `
  138. <div class="modal-content">
  139. <div class="progress-bar" id="progressBar"></div>
  140. <div class="modal-header">
  141. <h2 class="modal-title">Message Sender</h2>
  142. <span class="close">&times;</span>
  143. </div>
  144. <div class="form-group">
  145. <label for="messageText">Message:</label>
  146. <textarea id="messageText" placeholder="Your message goes here"></textarea>
  147. </div>
  148. <div class="toggle-container">
  149. <div class="toggle-option active" data-target="users-input">Users</div>
  150. <div class="toggle-option" data-target="federation-input">Federation</div>
  151. <div class="toggle-option" data-target="brazil-users-input"><img src="https://flagcdn.com/w20/br.png" style="vertical-align: middle; margin-right: 8px; height: 12px;"> BR Users</div>
  152. </div>
  153. <div class="input-container active" id="users-input">
  154. <div class="form-group">
  155. <label for="usersInput">Usernames (comma separated):</label>
  156. <input type="text" id="usersInput" placeholder="user1, user2, user3">
  157. </div>
  158. </div>
  159. <div class="input-container" id="federation-input">
  160. <div class="form-group">
  161. <label for="federationId">Federation ID:</label>
  162. <input type="text" id="federationId" placeholder="Enter federation ID (e.g. 63)">
  163. </div>
  164. </div>
  165. <div class="input-container" id="brazil-users-input">
  166. <p style="color: #ccc; font-size: 14px; text-align: center;">This option will send a message to a list of active Brazilian users.</p>
  167. </div>
  168. <button id="sendButton" class="send-button">Send Messages</button>
  169. <div id="statusArea" class="status-area">
  170. <div id="statusMessages"></div>
  171. </div>
  172. <div class="footer">
  173. <span id="gordola-credit">requested by gordola</span>
  174. <svg width="18" height="12" viewBox="0 0 18 12" style="vertical-align: middle; margin-left: 5px;">
  175. <rect width="18" height="4" fill="var(--arg-blue)" /><rect y="4" width="18" height="4" fill="var(--arg-white)" /><rect y="8" width="18" height="4" fill="var(--arg-blue)" />
  176. <circle cx="9" cy="6" r="1.5" fill="var(--arg-gold)" />
  177. </svg>
  178. </div>
  179. </div>
  180. `;
  181. document.body.appendChild(modal);
  182. Logger.log('Message modal elements created and appended to the body.');
  183.  
  184. document.getElementById('sendMessagesBtn').addEventListener('click', function(e) {
  185. e.preventDefault();
  186. Logger.log('Open modal button clicked.');
  187. const modalEl = document.getElementById('messageModal');
  188. modalEl.style.display = 'block';
  189. setTimeout(() => modalEl.classList.add('show'), 10);
  190. });
  191.  
  192. const toggleOptions = document.querySelectorAll('.toggle-option');
  193. toggleOptions.forEach(option => {
  194. option.addEventListener('click', function() {
  195. toggleOptions.forEach(opt => opt.classList.remove('active'));
  196. this.classList.add('active');
  197. const targetId = this.getAttribute('data-target');
  198. document.querySelectorAll('.input-container').forEach(container => container.classList.remove('active'));
  199. document.getElementById(targetId).classList.add('active');
  200. });
  201. });
  202.  
  203. document.querySelector('.close').addEventListener('click', function() {
  204. Logger.log('Modal close button clicked.');
  205. const modalEl = document.getElementById('messageModal');
  206. modalEl.classList.remove('show');
  207. setTimeout(() => { modalEl.style.display = 'none'; }, 300);
  208. });
  209.  
  210. window.addEventListener('click', function(event) {
  211. const modalEl = document.getElementById('messageModal');
  212. if (event.target === modalEl) {
  213. Logger.log('Clicked outside the modal. Closing.');
  214. modalEl.classList.remove('show');
  215. setTimeout(() => { modalEl.style.display = 'none'; }, 300);
  216. }
  217. });
  218.  
  219. document.getElementById('sendButton').addEventListener('click', async function() {
  220. Logger.groupStart('Message sending process initiated.');
  221. const message = document.getElementById('messageText').value.trim();
  222. if (!message) {
  223. alert('Please enter a message.');
  224. Logger.warn('Send attempt failed: message text was empty.');
  225. Logger.groupEnd();
  226. return;
  227. }
  228. Logger.log('Message content retrieved.');
  229.  
  230. const sendButton = this;
  231. const statusArea = document.getElementById('statusArea');
  232. const statusMessages = document.getElementById('statusMessages');
  233. const progressBar = document.getElementById('progressBar');
  234. statusArea.classList.add('show');
  235. statusMessages.innerHTML = '';
  236. updateProgressBar(progressBar, 0);
  237.  
  238. const activeMode = document.querySelector('.toggle-option.active').getAttribute('data-target');
  239. Logger.info(`Operating in mode: ${activeMode}`);
  240.  
  241. sendButton.disabled = true;
  242. try {
  243. switch (activeMode) {
  244. case 'users-input':
  245. await handleManualUsers(message, progressBar);
  246. break;
  247. case 'federation-input':
  248. await handleFederationUsers(message, progressBar);
  249. break;
  250. case 'brazil-users-input':
  251. await handleBrazilianUsers(message, progressBar);
  252. break;
  253. }
  254. Logger.log('Selected handler finished execution.');
  255. } catch (error) {
  256. addStatus(`A critical error occurred: ${error.message}`, 'status-error');
  257. Logger.error('A critical, unhandled error occurred in the main process.', error);
  258. } finally {
  259. sendButton.disabled = false;
  260. Logger.log('Message sending process finished.');
  261. Logger.groupEnd();
  262. }
  263. });
  264.  
  265. async function handleManualUsers(message, progressBar) {
  266. Logger.groupStart('Handling manual user input.');
  267. const usersText = document.getElementById('usersInput').value.trim();
  268. if (!usersText) {
  269. alert('Please enter at least one username.');
  270. Logger.warn('Manual user handling aborted: no usernames provided.');
  271. Logger.groupEnd();
  272. return;
  273. }
  274. const users = usersText.split(',').map(u => u.trim()).filter(Boolean);
  275. if (users.length === 0) {
  276. alert('Please enter valid usernames.');
  277. Logger.warn('Manual user handling aborted: no valid usernames after trimming.');
  278. Logger.groupEnd();
  279. return;
  280. }
  281. addStatus(`Found ${users.length} users in the list.`, 'status-info');
  282. Logger.info(`Processing ${users.length} usernames from input.`);
  283. const userIds = await getUserIdsFromUsernames(users, progressBar);
  284. await sendMessagesToUserIds(userIds, message, progressBar);
  285. Logger.groupEnd();
  286. }
  287.  
  288. async function handleFederationUsers(message, progressBar) {
  289. Logger.groupStart('Handling federation users.');
  290. const federationId = document.getElementById('federationId').value.trim();
  291. if (!federationId || !/^\d+$/.test(federationId)) {
  292. alert('Please enter a valid, numeric federation ID.');
  293. Logger.warn(`Federation handling aborted: invalid ID provided ('${federationId}').`);
  294. Logger.groupEnd();
  295. return;
  296. }
  297. addStatus(`Fetching members for federation ID: ${federationId}... Please wait.`, 'status-info');
  298. Logger.info(`Fetching members for federation ID: ${federationId}.`);
  299. const users = await getFederationMembers(federationId);
  300. if (users.length === 0) {
  301. addStatus('No users found in federation.', 'status-error');
  302. Logger.warn(`No users found for federation ID: ${federationId}.`);
  303. Logger.groupEnd();
  304. return;
  305. }
  306. addStatus(`Found ${users.length} federation members.`, 'status-info');
  307. Logger.info(`Found ${users.length} federation members.`);
  308. const userIds = await getUserIdsFromUsernames(users, progressBar);
  309. await sendMessagesToUserIds(userIds, message, progressBar);
  310. Logger.groupEnd();
  311. }
  312.  
  313. async function handleBrazilianUsers(message, progressBar) {
  314. Logger.groupStart('Handling active Brazilian users.');
  315. addStatus('Phase 1: Collecting Team IDs from Brazilian sources...', 'status-info');
  316. Logger.log('Starting Phase 1: Collecting Team IDs.');
  317. const teamIds = new Set();
  318. const leagueFetches = [];
  319. for (let lid = BRAZIL_LEAGUE_IDS_RANGE.start; lid <= BRAZIL_LEAGUE_IDS_RANGE.end; lid++) {
  320. leagueFetches.push(fetchTeamIdsFromLeague(lid));
  321. }
  322. const cupFetches = [];
  323. for (const cid of BRAZIL_CUP_IDS_1) {
  324. for (const div of CUP_DIVISIONS) { cupFetches.push(fetchTeamIdsFromCup(cid, div, 0)); }
  325. }
  326. for (const offset of [0, 20, 40]) { cupFetches.push(fetchTeamIdsFromCup(BRAZIL_CUP_ID_2, 4, offset)); }
  327. for (const offset of [0, 20]) { cupFetches.push(fetchTeamIdsFromCup(BRAZIL_CUP_ID_2, 5, offset)); }
  328. const allFetches = [...leagueFetches, ...cupFetches];
  329. let completedFetches = 0;
  330. const fetchPromises = allFetches.map(p => p.then(newTeamIds => {
  331. newTeamIds.forEach(id => teamIds.add(id));
  332. completedFetches++;
  333. updateProgressBar(progressBar, (completedFetches / allFetches.length) * 50);
  334. return newTeamIds;
  335. }));
  336. await Promise.all(fetchPromises);
  337. addStatus(`Collected ${teamIds.size} unique team IDs.`, 'status-success');
  338. Logger.info(`Phase 1 complete. Collected ${teamIds.size} unique team IDs.`);
  339. addStatus('Phase 2: Converting Team IDs to User IDs...', 'status-info');
  340. Logger.log('Starting Phase 2: Converting Team IDs to User IDs.');
  341. const userIds = new Set();
  342. const teamIdArray = Array.from(teamIds);
  343. let processedTeams = 0;
  344. for (const teamId of teamIdArray) {
  345. const userId = await getUserIdByTeamId(teamId);
  346. if (userId) { userIds.add(userId); }
  347. processedTeams++;
  348. updateProgressBar(progressBar, 50 + (processedTeams / teamIdArray.length) * 25);
  349. }
  350. addStatus(`Found ${userIds.size} unique active users.`, 'status-success');
  351. Logger.info(`Phase 2 complete. Resolved to ${userIds.size} unique user IDs.`);
  352. addStatus('Phase 3: Sending messages...', 'status-info');
  353. Logger.log('Starting Phase 3: Sending messages.');
  354. await sendMessagesToUserIds(Array.from(userIds), message, progressBar, 75);
  355. Logger.groupEnd();
  356. }
  357.  
  358. async function getUserIdsFromUsernames(usernames, progressBar) {
  359. Logger.groupStart('Resolving usernames to user IDs.');
  360. const userIds = new Set();
  361. let processedCount = 0;
  362. const promises = usernames.map(username => getUserIdByUsername(username).then(userId => {
  363. if (userId) {
  364. userIds.add(userId);
  365. addStatus(`-> ${username} User ID: ${userId}`, 'status-info');
  366. Logger.log(`Resolved: ${username} -> ${userId}`);
  367. } else {
  368. addStatus(`Could not find User ID for ${username}`, 'status-error');
  369. Logger.warn(`Could not resolve username: ${username}`);
  370. }
  371. processedCount++;
  372. updateProgressBar(progressBar, (processedCount / usernames.length) * 100);
  373. }));
  374. await Promise.all(promises);
  375. Logger.info(`Resolved ${userIds.size} unique IDs from ${usernames.length} usernames.`);
  376. Logger.groupEnd();
  377. return Array.from(userIds);
  378. }
  379.  
  380. async function sendMessagesToUserIds(userIds, message, progressBar, baseProgress = 0) {
  381. Logger.groupStart(`Sending messages to ${userIds.length} users.`);
  382. let processedMessages = 0;
  383. const totalMessages = userIds.length;
  384. if (totalMessages === 0) {
  385. addStatus('No valid users to send messages to.', 'status-error');
  386. Logger.warn('Message sending aborted: user ID list is empty.');
  387. updateProgressBar(progressBar, 100);
  388. Logger.groupEnd();
  389. return;
  390. }
  391. for (const userId of userIds) {
  392. try {
  393. Logger.log(`Attempting to send message to User ID: ${userId}`);
  394. await sendMessage(userId, message);
  395. addStatus(`Message sent to User ID: ${userId}`, 'status-success');
  396. Logger.info(`Successfully sent message to User ID: ${userId}`);
  397. } catch (error) {
  398. addStatus(`Error sending to User ID ${userId}: ${error.message}`, 'status-error');
  399. Logger.error(`Failed to send message to User ID: ${userId}`, error);
  400. }
  401. processedMessages++;
  402. const progress = baseProgress + (processedMessages / totalMessages) * (100 - baseProgress);
  403. updateProgressBar(progressBar, progress);
  404. if (processedMessages < totalMessages) {
  405. addStatus(`Waiting 6 seconds...`, 'status-info');
  406. Logger.log('Pausing for 6 seconds between sends.');
  407. await new Promise(resolve => setTimeout(resolve, 6000));
  408. }
  409. }
  410. addStatus('All messages have been sent!', 'status-success');
  411. Logger.info('Finished sending all messages in the queue.');
  412. updateProgressBar(progressBar, 100);
  413. Logger.groupEnd();
  414. }
  415.  
  416. async function fetchTeamIdsFromLeague(leagueId) {
  417. try {
  418. const response = await fetch(`https://www.managerzone.com/xml/team_league.php?sport_id=1&league_id=${leagueId}`);
  419. const xmlText = await response.text();
  420. return Array.from(xmlText.matchAll(/<Team[^>]*teamId="(\d+)"[^>]*>/g)).map(match => match[1]);
  421. } catch (error) {
  422. Logger.error(`Failed to fetch team IDs from league ${leagueId}.`, error);
  423. return [];
  424. }
  425. }
  426.  
  427. async function fetchTeamIdsFromCup(cupId, division, offset) {
  428. try {
  429. const response = await fetch(`https://www.managerzone.com/?p=cups&sub=find_participants&cid=${cupId}&div=${division}&offset=${offset}`, { method: 'POST' });
  430. const htmlText = await response.text();
  431. return Array.from(htmlText.matchAll(/tid=(\d+)/g)).map(match => match[1]);
  432. } catch (error) {
  433. Logger.error(`Failed to fetch team IDs from cup ${cupId} (div: ${division}, offset: ${offset}).`, error);
  434. return [];
  435. }
  436. }
  437.  
  438. function extractUsernames(html) {
  439. const container = document.createElement('div');
  440. container.innerHTML = html;
  441. const userLinks = container.querySelectorAll('a[href*="p=profile&uid="]');
  442. return Array.from(userLinks).map(link => link.textContent.trim());
  443. }
  444.  
  445. async function getFederationMembers(federationId) {
  446. const allUsernames = new Set();
  447. let offset = 0;
  448. let hasMorePages = true;
  449. Logger.log(`Initiating fetch for all members of federation ${federationId}.`);
  450. while (hasMorePages) {
  451. try {
  452. const url = `https://www.managerzone.com/ajax.php?p=federations&sub=federation_members&fid=${federationId}&offset=${offset}&sport=soccer`;
  453. const response = await fetch(url);
  454. const data = await response.json();
  455. if (!data || !data[0] || !data[0].length) {
  456. hasMorePages = false;
  457. continue;
  458. }
  459. const newUsers = extractUsernames(data[0]);
  460. Logger.log(`Fetched page with offset ${offset}. Found ${newUsers.length} new potential users.`);
  461. if (newUsers.length === 0) {
  462. hasMorePages = false;
  463. continue;
  464. }
  465. newUsers.forEach(user => allUsernames.add(user));
  466. hasMorePages = data[1] && data[1].includes(`offset=${offset + 10}`);
  467. offset += 10;
  468. if (hasMorePages) {
  469. Logger.log('More members detected, pausing before next fetch.');
  470. await new Promise(resolve => setTimeout(resolve, 1000));
  471. }
  472. } catch (error) {
  473. Logger.error(`Failed while fetching members at offset ${offset}.`, error);
  474. hasMorePages = false;
  475. }
  476. }
  477. Logger.info(`Finished fetching federation members. Total unique users found: ${allUsernames.size}`);
  478. return Array.from(allUsernames);
  479. }
  480.  
  481. function addStatus(message, className) {
  482. const statusMessages = document.getElementById('statusMessages');
  483. const messageElement = document.createElement('div');
  484. messageElement.className = `status-message ${className}`;
  485. messageElement.textContent = message;
  486. statusMessages.appendChild(messageElement);
  487. statusMessages.scrollTop = statusMessages.scrollHeight;
  488. }
  489.  
  490. function updateProgressBar(progressBar, percentage) {
  491. progressBar.style.width = `${Math.min(100, percentage)}%`;
  492. }
  493.  
  494. async function getUserIdByUsername(username) {
  495. try {
  496. const response = await fetch(`https://www.managerzone.com/xml/manager_data.php?sport_id=1&username=${encodeURIComponent(username)}`);
  497. const text = await response.text();
  498. const match = text.match(/<UserData[^>]*userId="(\d+)"/);
  499. return match ? match[1] : null;
  500. } catch (error) {
  501. Logger.error(`Network or parse error when fetching user ID for username: ${username}`, error);
  502. return null;
  503. }
  504. }
  505.  
  506. async function getUserIdByTeamId(teamId) {
  507. try {
  508. const response = await fetch(`https://www.managerzone.com/xml/manager_data.php?sport_id=1&team_id=${teamId}`);
  509. const text = await response.text();
  510. const match = text.match(/<UserData[^>]*userId="(\d+)"/);
  511. if (!match) {
  512. Logger.warn(`Could not find a user ID for team ID ${teamId}. The team might be inactive or hold a vacancy.`);
  513. }
  514. return match ? match[1] : null;
  515. } catch (error) {
  516. Logger.error(`Network or parse error when fetching user ID for team ID: ${teamId}`, error);
  517. return null;
  518. }
  519. }
  520.  
  521. async function sendMessage(userId, message) {
  522. const response = await fetch(`https://www.managerzone.com/ajax.php?p=messageBoard&sub=write&template=1&ident_id=${userId}&sport=soccer`, {
  523. method: 'POST',
  524. body: new URLSearchParams({ 'msg': message }),
  525. headers: {
  526. 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
  527. 'X-Requested-With': 'XMLHttpRequest'
  528. }
  529. });
  530. if (!response.ok) {
  531. throw new Error(`Server responded with status ${response.status}`);
  532. }
  533. return true;
  534. }
  535. });
  536. })();