MZ - Message Sender

Sends messages to multiple users

当前为 2025-02-25 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name MZ - Message Sender
  3. // @namespace douglaskampl
  4. // @version 1.0
  5. // @description Sends messages 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. // @license MIT
  11. // @run-at document-idle
  12. // ==/UserScript==
  13.  
  14. (function () {
  15. 'use strict';
  16.  
  17. GM_addStyle(`
  18. .custom-button {
  19. margin-left: 10px;
  20. background-color: #74ACDF;
  21. color: white;
  22. position: relative;
  23. overflow: hidden;
  24. transition: all 0.3s ease;
  25. }
  26.  
  27. .custom-button:hover {
  28. background-color: #6B9FD0;
  29. transform: translateY(-2px);
  30. box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
  31. }
  32.  
  33. .custom-button:after {
  34. content: '';
  35. position: absolute;
  36. top: 0;
  37. left: -100%;
  38. width: 100%;
  39. height: 100%;
  40. background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
  41. transition: 0.5s;
  42. }
  43.  
  44. .custom-button:hover:after {
  45. left: 100%;
  46. }
  47.  
  48. .modal {
  49. display: none;
  50. position: fixed;
  51. z-index: 9999;
  52. left: 0;
  53. top: 0;
  54. width: 100%;
  55. height: 100%;
  56. overflow: auto;
  57. background-color: rgba(0, 0, 0, 0.7);
  58. opacity: 0;
  59. transition: opacity 0.3s ease;
  60. }
  61.  
  62. .modal.show {
  63. opacity: 1;
  64. }
  65.  
  66. .modal-content {
  67. background-color: #2E2E2E;
  68. color: #F5F5F5;
  69. margin: 15% auto;
  70. padding: 25px;
  71. border: 2px solid #F0F0F0;
  72. width: 80%;
  73. max-width: 600px;
  74. border-radius: 8px;
  75. box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3);
  76. transform: translateY(-50px);
  77. opacity: 0;
  78. transition: all 0.4s ease;
  79. }
  80.  
  81. .modal.show .modal-content {
  82. transform: translateY(0);
  83. opacity: 1;
  84. }
  85.  
  86. .modal-header {
  87. border-bottom: 3px solid #74ACDF;
  88. padding-bottom: 15px;
  89. margin-bottom: 20px;
  90. display: flex;
  91. justify-content: space-between;
  92. align-items: center;
  93. }
  94.  
  95. .modal-title {
  96. color: #F5F5F5;
  97. margin: 0;
  98. font-size: 22px;
  99. text-shadow: 1px 1px 3px rgba(0, 0, 0, 0.3);
  100. }
  101.  
  102. .close {
  103. color: #F0F0F0;
  104. float: right;
  105. font-size: 28px;
  106. font-weight: bold;
  107. cursor: pointer;
  108. transition: color 0.2s ease;
  109. }
  110.  
  111. .close:hover,
  112. .close:focus {
  113. color: #74ACDF;
  114. text-decoration: none;
  115. }
  116.  
  117. .form-group {
  118. margin-bottom: 20px;
  119. }
  120.  
  121. .form-group label {
  122. display: block;
  123. margin-bottom: 8px;
  124. font-weight: bold;
  125. color: #74ACDF;
  126. }
  127.  
  128. .form-group input,
  129. .form-group textarea {
  130. width: 100%;
  131. padding: 12px;
  132. background-color: #3D3D3D;
  133. color: #F5F5F5;
  134. border: 1px solid #555;
  135. border-radius: 6px;
  136. box-sizing: border-box;
  137. transition: all 0.3s ease;
  138. }
  139.  
  140. .form-group input:focus,
  141. .form-group textarea:focus {
  142. outline: none;
  143. border-color: #74ACDF;
  144. box-shadow: 0 0 8px rgba(116, 172, 223, 0.5);
  145. }
  146.  
  147. .form-group textarea {
  148. height: 120px;
  149. resize: vertical;
  150. }
  151.  
  152. .send-button {
  153. background: linear-gradient(to right, #74ACDF, #85C2F5);
  154. color: white;
  155. padding: 12px 20px;
  156. border: none;
  157. border-radius: 6px;
  158. cursor: pointer;
  159. font-weight: bold;
  160. letter-spacing: 0.5px;
  161. transition: all 0.3s ease;
  162. position: relative;
  163. overflow: hidden;
  164. }
  165.  
  166. .send-button:hover {
  167. background: linear-gradient(to right, #85C2F5, #74ACDF);
  168. transform: translateY(-2px);
  169. box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2);
  170. }
  171.  
  172. .send-button:after {
  173. content: '';
  174. position: absolute;
  175. top: 0;
  176. left: -100%;
  177. width: 100%;
  178. height: 100%;
  179. background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.4), transparent);
  180. transition: 0.5s;
  181. }
  182.  
  183. .send-button:hover:after {
  184. left: 100%;
  185. }
  186.  
  187. .send-button:disabled {
  188. background: #555;
  189. cursor: not-allowed;
  190. transform: none;
  191. box-shadow: none;
  192. }
  193.  
  194. .status-area {
  195. margin-top: 20px;
  196. padding: 15px;
  197. border: 1px solid #444;
  198. border-radius: 6px;
  199. max-height: 180px;
  200. overflow-y: auto;
  201. display: none;
  202. background-color: #222;
  203. transition: all 0.3s ease;
  204. }
  205.  
  206. .status-area.show {
  207. display: block;
  208. animation: fadeIn 0.5s ease forwards;
  209. }
  210.  
  211. .status-message {
  212. margin: 8px 0;
  213. padding: 5px 10px;
  214. border-radius: 4px;
  215. transition: opacity 0.3s ease;
  216. animation: slideIn 0.3s ease forwards;
  217. }
  218.  
  219. @keyframes slideIn {
  220. from {
  221. opacity: 0;
  222. transform: translateY(-10px);
  223. }
  224. to {
  225. opacity: 1;
  226. transform: translateY(0);
  227. }
  228. }
  229.  
  230. @keyframes fadeIn {
  231. from {
  232. opacity: 0;
  233. }
  234. to {
  235. opacity: 1;
  236. }
  237. }
  238.  
  239. .status-success {
  240. color: #7CFC00;
  241. background-color: rgba(124, 252, 0, 0.1);
  242. border-left: 3px solid #7CFC00;
  243. }
  244.  
  245. .status-error {
  246. color: #FF6B6B;
  247. background-color: rgba(255, 107, 107, 0.1);
  248. border-left: 3px solid #FF6B6B;
  249. }
  250.  
  251. .status-info {
  252. color: #74ACDF;
  253. background-color: rgba(116, 172, 223, 0.1);
  254. border-left: 3px solid #74ACDF;
  255. }
  256.  
  257. .footer {
  258. margin-top: 20px;
  259. padding-top: 15px;
  260. border-top: 1px solid #444;
  261. text-align: center;
  262. color: #AAA;
  263. font-size: 12px;
  264. }
  265.  
  266. .footer img {
  267. height: 20px;
  268. vertical-align: middle;
  269. margin-left: 5px;
  270. }
  271.  
  272. .progress-bar {
  273. height: 4px;
  274. width: 0%;
  275. background: linear-gradient(to right, #74ACDF, #FCBF49);
  276. position: absolute;
  277. top: 0;
  278. left: 0;
  279. transition: width 0.5s ease;
  280. }
  281.  
  282. .toggle-container {
  283. display: flex;
  284. justify-content: center;
  285. margin-bottom: 20px;
  286. background-color: #222;
  287. border-radius: 6px;
  288. padding: 4px;
  289. width: 100%;
  290. }
  291.  
  292. .toggle-option {
  293. flex: 1;
  294. padding: 10px;
  295. text-align: center;
  296. cursor: pointer;
  297. border-radius: 4px;
  298. transition: all 0.3s ease;
  299. color: #AAA;
  300. }
  301.  
  302. .toggle-option.active {
  303. background-color: #74ACDF;
  304. color: white;
  305. box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
  306. }
  307.  
  308. .toggle-option:not(.active):hover {
  309. background-color: #333;
  310. color: #DDD;
  311. }
  312.  
  313. .input-container {
  314. display: none;
  315. }
  316.  
  317. .input-container.active {
  318. display: block;
  319. animation: fadeIn 0.3s ease;
  320. }
  321. `);
  322.  
  323. window.addEventListener('load', function() {
  324. const postButton = document.getElementById('formpostbutton');
  325. if (!postButton) return;
  326.  
  327. const sendMessagesButton = document.createElement('div');
  328. sendMessagesButton.style.display = 'inline-block';
  329. sendMessagesButton.innerHTML = `
  330. <a href="#" class="mzbtn button_account buttondiv custom-button" id="sendMessagesBtn">
  331. <span class="buttonClassMiddle"><span style="white-space: nowrap">Send Messages</span></span><span class="buttonClassRight">&nbsp;</span>
  332. </a>
  333. `;
  334. postButton.parentNode.insertBefore(sendMessagesButton, postButton.nextSibling);
  335.  
  336. const modal = document.createElement('div');
  337. modal.className = 'modal';
  338. modal.id = 'messageModal';
  339. modal.innerHTML = `
  340. <div class="modal-content">
  341. <div class="progress-bar" id="progressBar"></div>
  342. <div class="modal-header">
  343. <h2 class="modal-title">メッセージ送信者の翻訳</h2>
  344. <span class="close">&times;</span>
  345. </div>
  346.  
  347. <div class="form-group">
  348. <label for="messageText">Message:</label>
  349. <textarea id="messageText" placeholder="Enter your message here"></textarea>
  350. </div>
  351.  
  352. <div class="toggle-container">
  353. <div class="toggle-option active" data-target="users-input">Users</div>
  354. <div class="toggle-option" data-target="federation-input">Federation</div>
  355. </div>
  356.  
  357. <div class="input-container active" id="users-input">
  358. <div class="form-group">
  359. <label for="usersInput">Usernames (comma separated):</label>
  360. <input type="text" id="usersInput" placeholder="user1, user2, user3">
  361. </div>
  362. </div>
  363.  
  364. <div class="input-container" id="federation-input">
  365. <div class="form-group">
  366. <label for="federationId">Federation ID:</label>
  367. <input type="text" id="federationId" placeholder="Enter fid">
  368. </div>
  369. </div>
  370.  
  371. <button id="sendButton" class="send-button">Send Messages</button>
  372.  
  373. <div id="statusArea" class="status-area">
  374. <div id="statusMessages"></div>
  375. </div>
  376.  
  377. <div class="footer">
  378. requested by gordola
  379. <svg width="18" height="12" viewBox="0 0 18 12" style="vertical-align: middle; margin-left: 5px;">
  380. <rect width="18" height="4" fill="#74ACDF" />
  381. <rect y="4" width="18" height="4" fill="#FFFFFF" />
  382. <rect y="8" width="18" height="4" fill="#74ACDF" />
  383. <circle cx="9" cy="6" r="1.5" fill="#FCBF49" />
  384. </svg>
  385. </div>
  386. </div>
  387. `;
  388. document.body.appendChild(modal);
  389.  
  390. const toggleOptions = document.querySelectorAll('.toggle-option');
  391. toggleOptions.forEach(option => {
  392. option.addEventListener('click', function() {
  393. toggleOptions.forEach(opt => opt.classList.remove('active'));
  394. this.classList.add('active');
  395.  
  396. const targetId = this.getAttribute('data-target');
  397. document.querySelectorAll('.input-container').forEach(container => {
  398. container.classList.remove('active');
  399. });
  400. document.getElementById(targetId).classList.add('active');
  401. });
  402. });
  403.  
  404. document.getElementById('sendMessagesBtn').addEventListener('click', function(e) {
  405. e.preventDefault();
  406. const modalEl = document.getElementById('messageModal');
  407. modalEl.style.display = 'block';
  408. setTimeout(() => {
  409. modalEl.classList.add('show');
  410. }, 10);
  411. });
  412.  
  413. document.querySelector('.close').addEventListener('click', function() {
  414. const modalEl = document.getElementById('messageModal');
  415. modalEl.classList.remove('show');
  416. setTimeout(() => {
  417. modalEl.style.display = 'none';
  418. }, 300);
  419. });
  420.  
  421. window.addEventListener('click', function(event) {
  422. const modalEl = document.getElementById('messageModal');
  423. if (event.target === modalEl) {
  424. modalEl.classList.remove('show');
  425. setTimeout(() => {
  426. modalEl.style.display = 'none';
  427. }, 300);
  428. }
  429. });
  430.  
  431. document.getElementById('sendButton').addEventListener('click', async function() {
  432. const message = document.getElementById('messageText').value.trim();
  433. if (!message) {
  434. alert('Please enter a message');
  435. return;
  436. }
  437.  
  438. const statusArea = document.getElementById('statusArea');
  439. const statusMessages = document.getElementById('statusMessages');
  440. const progressBar = document.getElementById('progressBar');
  441. statusArea.classList.add('show');
  442. statusMessages.innerHTML = '';
  443. updateProgressBar(progressBar, 0);
  444.  
  445. const activeMode = document.querySelector('.toggle-option.active').getAttribute('data-target');
  446.  
  447. if (activeMode === 'users-input') {
  448. const usersText = document.getElementById('usersInput').value.trim();
  449. if (!usersText) {
  450. alert('Please enter at least one username');
  451. return;
  452. }
  453.  
  454. const users = usersText.split(',').map(u => u.trim()).filter(u => u);
  455. if (users.length === 0) {
  456. alert('Please enter valid usernames');
  457. return;
  458. }
  459.  
  460. const totalUsers = users.length;
  461. let processedUsers = 0;
  462.  
  463. addStatus('Starting to send messages...', 'status-info');
  464.  
  465. for (const username of users) {
  466. try {
  467. const userId = await getUserId(username);
  468. if (!userId) {
  469. addStatus(`Could not find user ID for ${username}`, 'status-error');
  470. continue;
  471. }
  472.  
  473. addStatus(`Found user ID: ${userId}`, 'status-info');
  474.  
  475. await sendMessage(userId, message);
  476. addStatus(`Message sent to ${username}`, 'status-success');
  477.  
  478. processedUsers++;
  479. updateProgressBar(progressBar, (processedUsers / totalUsers) * 100);
  480.  
  481. if (processedUsers < totalUsers) {
  482. addStatus(`Waiting 5 seconds before sending the next message...`, 'status-info');
  483. await new Promise(resolve => setTimeout(resolve, 5000));
  484. }
  485. } catch (error) {
  486. addStatus(`Error: ${error.message}`, 'status-error');
  487. }
  488. }
  489.  
  490. updateProgressBar(progressBar, 100);
  491. addStatus('All messages have been sent!', 'status-success');
  492. } else {
  493. const federationId = document.getElementById('federationId').value.trim();
  494. if (!federationId) {
  495. alert('Please enter a federation ID');
  496. return;
  497. }
  498.  
  499. if (!/^\d+$/.test(federationId)) {
  500. alert('Federation ID must be a number');
  501. return;
  502. }
  503.  
  504. addStatus(`Starting to fetch federation members for federation ID: ${federationId}...`, 'status-info');
  505.  
  506. try {
  507. const users = await getFederationMembers(federationId);
  508.  
  509. if (users.length === 0) {
  510. addStatus('No users found in federation', 'status-error');
  511. return;
  512. }
  513.  
  514. addStatus(`Found ${users.length} federation members`, 'status-info');
  515.  
  516. if (!confirm(`You are about to send messages to ${users.length} users. Do you want to continue?`)) {
  517. addStatus('Operation cancelled by user', 'status-info');
  518. return;
  519. }
  520.  
  521. const totalUsers = users.length;
  522. let processedUsers = 0;
  523.  
  524. addStatus('Starting to send messages...', 'status-info');
  525.  
  526. for (const username of users) {
  527. try {
  528. const userId = await getUserId(username);
  529. if (!userId) {
  530. addStatus(`Could not find user ID for ${username}`, 'status-error');
  531. continue;
  532. }
  533.  
  534. addStatus(`Found user ID: ${userId}`, 'status-info');
  535.  
  536. await sendMessage(userId, message);
  537. addStatus(`Message sent to ${username}`, 'status-success');
  538.  
  539. processedUsers++;
  540. updateProgressBar(progressBar, (processedUsers / totalUsers) * 100);
  541.  
  542. if (processedUsers < totalUsers) {
  543. addStatus(`Waiting 5 seconds before sending the next message...`, 'status-info');
  544. await new Promise(resolve => setTimeout(resolve, 5000));
  545. }
  546. } catch (error) {
  547. addStatus(`Error: ${error.message}`, 'status-error');
  548. }
  549. }
  550.  
  551. updateProgressBar(progressBar, 100);
  552. addStatus('All messages have been sent!', 'status-success');
  553. } catch (error) {
  554. addStatus(`Error fetching federation members: ${error.message}`, 'status-error');
  555. }
  556. }
  557. });
  558.  
  559. async function getFederationMembers(federationId) {
  560. const users = [];
  561. let offset = 0;
  562. let hasMoreMembers = true;
  563.  
  564. while (hasMoreMembers) {
  565. addStatus(`Fetching federation members (offset: ${offset})...`, 'status-info');
  566.  
  567. try {
  568. const url = `https://www.managerzone.com/ajax.php?p=federations&sub=federation_members&fid=${federationId}&offset=${offset}&sport=soccer`;
  569. const response = await fetch(url);
  570. const data = await response.json();
  571.  
  572. if (!data || !data[0] || !data[0].length) {
  573. hasMoreMembers = false;
  574. break;
  575. }
  576.  
  577. const newUsers = extractUsernames(data[0]);
  578.  
  579. if (newUsers.length === 0) {
  580. hasMoreMembers = false;
  581. break;
  582. }
  583.  
  584. users.push(...newUsers);
  585. addStatus(`Found ${newUsers.length} members at offset ${offset}`, 'status-info');
  586.  
  587. hasMoreMembers = data[1] && data[1].includes(`offset=${offset + 10}`);
  588. offset += 10;
  589.  
  590. await new Promise(resolve => setTimeout(resolve, 1000));
  591. } catch (error) {
  592. addStatus(`Error fetching members at offset ${offset}: ${error.message}`, 'status-error');
  593. hasMoreMembers = false;
  594. }
  595. }
  596.  
  597. return [...new Set(users)];
  598. }
  599.  
  600. function extractUsernames(html) {
  601. const regex = /\"(?:Epic|Legendary|Elite|Gold|Senior|\"|\s)*\s*([a-zA-Z0-9_-]+)<\/a>/g;
  602. const matches = [];
  603. let match;
  604.  
  605. while ((match = regex.exec(html)) !== null) {
  606. if (match[1] && match[1].trim()) {
  607. matches.push(match[1].trim());
  608. }
  609. }
  610.  
  611. const simpleRegex = /([a-zA-Z0-9_-]+)<\/a><\/span><\/td>/g;
  612. while ((match = simpleRegex.exec(html)) !== null) {
  613. if (match[1] && match[1].trim() && !matches.includes(match[1].trim())) {
  614. matches.push(match[1].trim());
  615. }
  616. }
  617.  
  618. return matches;
  619. }
  620.  
  621. function addStatus(message, className) {
  622. const statusMessages = document.getElementById('statusMessages');
  623. const messageElement = document.createElement('div');
  624. messageElement.className = `status-message ${className}`;
  625. messageElement.textContent = message;
  626. statusMessages.appendChild(messageElement);
  627. statusMessages.scrollTop = statusMessages.scrollHeight;
  628. }
  629.  
  630. function updateProgressBar(progressBar, percentage) {
  631. progressBar.style.width = `${percentage}%`;
  632. }
  633.  
  634. async function getUserId(username) {
  635. const response = await fetch(`https://www.managerzone.com/xml/manager_data.php?sport_id=1&username=${encodeURIComponent(username)}`);
  636. const text = await response.text();
  637. const parser = new DOMParser();
  638. const xmlDoc = parser.parseFromString(text, "text/xml");
  639. const userDataElement = xmlDoc.querySelector('UserData');
  640.  
  641. if (userDataElement && userDataElement.hasAttribute('userId')) {
  642. return userDataElement.getAttribute('userId');
  643. }
  644.  
  645. return null;
  646. }
  647.  
  648. async function sendMessage(userId, message) {
  649. await fetch(`https://www.managerzone.com/ajax.php?p=messageBoard&sub=write&template=1&ident_id=${userId}&sport=soccer`, {
  650. method: 'POST',
  651. body: new URLSearchParams({
  652. 'msg': message
  653. }),
  654. headers: {
  655. 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
  656. 'X-Requested-With': 'XMLHttpRequest'
  657. }
  658. });
  659.  
  660. return true;
  661. }
  662. });
  663. })();