MZ - Multiple League Standings in a Single View

Displays leagues and world leagues, grouped by div and/or region, in a single view

  1. // ==UserScript==
  2. // @name MZ - Multiple League Standings in a Single View
  3. // @namespace douglaskampl
  4. // @version 3.9
  5. // @description Displays leagues and world leagues, grouped by div and/or region, in a single view
  6. // @author Douglas
  7. // @match https://www.managerzone.com/?p=team
  8. // @icon https://www.google.com/s2/favicons?sz=64&domain=managerzone.com
  9. // @grant GM_addStyle
  10. // @grant GM_getResourceText
  11. // @resource leagueStandingsStyles https://br18.org/mz/userscript/other/sLeagueStandings.css
  12. // @run-at document-idle
  13. // @license MIT
  14. // ==/UserScript==
  15.  
  16. (function () {
  17. 'use strict';
  18.  
  19. GM_addStyle(GM_getResourceText('leagueStandingsStyles'));
  20.  
  21. const CONSTANTS = {
  22. REGIONS: {
  23. SOCCER: {
  24. COUNTRIES: [
  25. { name: 'Argentina', start: 16096 },
  26. { name: 'Brazil', start: 26187 },
  27. { name: 'China', start: 70847 },
  28. { name: 'Germany', start: 12086 },
  29. { name: 'Italy', start: 10625 },
  30. { name: 'Netherlands', start: 15004 },
  31. { name: 'Portugal', start: 17566 },
  32. { name: 'Spain', start: 10746 },
  33. { name: 'Poland', start: 13181 },
  34. { name: 'Romania', start: 17929 },
  35. { name: 'Sweden', start: 43 },
  36. { name: 'Turkey', start: 20356 }
  37. ],
  38. UXX_REGIONS: [
  39. { name: 'Argentina', start: 1 },
  40. { name: 'Brazil', start: 122 },
  41. { name: 'Latin America, USA and Canada', start: 727 },
  42. { name: 'Central Europe', start: 848 },
  43. { name: 'Iberia', start: 969 },
  44. { name: 'Mediterranean', start: 1090 },
  45. { name: 'Northern Europe', start: 1211 },
  46. { name: 'Poland', start: 243 },
  47. { name: 'Romania', start: 364 },
  48. { name: 'Sweden', start: 485 },
  49. { name: 'Turkey', start: 606 },
  50. { name: 'World (Asia, Oceania and Africa)', start: 1332 }
  51. ]
  52. },
  53. HOCKEY: {
  54. COUNTRIES: [
  55. { name: 'Brazil', start: 7900 },
  56. { name: 'Sweden', start: 1 },
  57. { name: 'Argentina', start: 2 },
  58. { name: 'Poland', start: 23 },
  59. { name: 'Portugal', start: 24 },
  60. { name: 'Spain', start: 12 },
  61. { name: 'Romania', start: 25 },
  62. { name: 'Turkey', start: 27 },
  63. { name: 'China', start: 13727 }
  64. ],
  65. UXX_REGIONS: [
  66. { name: 'Northern Europe', start: 1 },
  67. { name: 'Southern Europe', start: 122 },
  68. { name: 'Rest of the World', start: 243 }
  69. ]
  70. }
  71. },
  72. LEAGUE_TYPES: {
  73. SENIOR: 'senior',
  74. U18: 'u18',
  75. U21: 'u21',
  76. U23: 'u23',
  77. WORLD: 'world',
  78. U18_WORLD: 'u18_world',
  79. U21_WORLD: 'u21_world',
  80. U23_WORLD: 'u23_world'
  81. },
  82. DIVISION: {
  83. NAMES: {
  84. TOP: 'Top Division',
  85. TOP_SERIES: 'Top Series'
  86. },
  87. STRUCTURE: {
  88. BASE_DIVISIONS: 3,
  89. MAX_LEVEL: 4
  90. }
  91. },
  92. SELECTORS: {
  93. TEAM_INFO: '#infoAboutTeam',
  94. STADIUM_WRAPPER: '#team-stadium-wrapper',
  95. SHORTCUT_LINK: '#shortcut_link_thezone'
  96. },
  97. TEXTS: {
  98. TOGGLE_UP: 'UP 上',
  99. TOGGLE_DOWN: 'DOWN 下',
  100. LOADING: 'Loading ロード中…'
  101. },
  102. BUTTON_NAMES: [
  103. 'Senior Leagues',
  104. 'U18 Leagues',
  105. 'U21 Leagues',
  106. 'U23 Leagues',
  107. 'Senior World Leagues',
  108. 'U18 World Leagues',
  109. 'U21 World Leagues',
  110. 'U23 World Leagues'
  111. ]
  112. };
  113.  
  114. class LeagueManager {
  115. constructor() {
  116. const shortcutLink = document.querySelector(CONSTANTS.SELECTORS.SHORTCUT_LINK);
  117. this.sport = new URL(shortcutLink.href).searchParams.get('sport');
  118. const teamInfo = document.querySelector(CONSTANTS.SELECTORS.TEAM_INFO);
  119. this.teamId = RegExp(/\((\d+)\)/).exec(teamInfo.querySelector('dd').textContent)[1];
  120. this.seniorLeagues = this.getSeniorLeagues();
  121. this.worldLeagues = { World: this.getWorldLeaguesObj() };
  122. this.uxxLeagues = this.getUxxLeagues();
  123. }
  124.  
  125. getSeniorLeagues() {
  126. const countries = this.sport === 'soccer' ? CONSTANTS.REGIONS.SOCCER.COUNTRIES : CONSTANTS.REGIONS.HOCKEY.COUNTRIES;
  127. return countries.reduce((acc, { name, start }) => {
  128. acc[name] = { [CONSTANTS.DIVISION.NAMES.TOP]: [start] };
  129. return acc;
  130. }, {});
  131. }
  132.  
  133. getWorldLeaguesObj() {
  134. const leagues = {};
  135. let start = 1;
  136. for (let i = 0; i <= CONSTANTS.DIVISION.STRUCTURE.MAX_LEVEL; i++) {
  137. const divisionName = i === 0 ? CONSTANTS.DIVISION.NAMES.TOP_SERIES : `Division ${i}`;
  138. leagues[divisionName] = [];
  139. const numLeagues = Math.pow(CONSTANTS.DIVISION.STRUCTURE.BASE_DIVISIONS, i);
  140. for (let j = 0; j < numLeagues; j++) {
  141. leagues[divisionName].push(start++);
  142. }
  143. }
  144. return leagues;
  145. }
  146.  
  147. getUxxLeagues() {
  148. const regions = this.sport === 'soccer' ? CONSTANTS.REGIONS.SOCCER.UXX_REGIONS : CONSTANTS.REGIONS.HOCKEY.UXX_REGIONS;
  149. const obj = {};
  150. regions.forEach(region => {
  151. obj[region.name] = {
  152. [CONSTANTS.DIVISION.NAMES.TOP]: [region.start],
  153. 'Division 1': Array.from({ length: CONSTANTS.DIVISION.STRUCTURE.BASE_DIVISIONS }, (_, i) => region.start + i + 1),
  154. 'Division 2': Array.from({ length: Math.pow(CONSTANTS.DIVISION.STRUCTURE.BASE_DIVISIONS, 2) }, (_, i) => region.start + i + 4)
  155. };
  156. });
  157. return obj;
  158. }
  159.  
  160. getAllLeagues(leaguesObj) {
  161. const allLeagues = {};
  162. Object.entries(leaguesObj).forEach(([country, leagues]) => {
  163. Object.entries(leagues).forEach(([leagueName, ids]) => {
  164. if (!allLeagues[leagueName]) allLeagues[leagueName] = [];
  165. ids.forEach((id) => {
  166. allLeagues[leagueName].push({ sid: id, region: country });
  167. });
  168. });
  169. });
  170. return allLeagues;
  171. }
  172.  
  173. getLeagueTypeFromButtonId(id) {
  174. if (id.includes('senior leagues')) return CONSTANTS.LEAGUE_TYPES.SENIOR;
  175. if (id.includes('u18 world leagues')) return CONSTANTS.LEAGUE_TYPES.U18_WORLD;
  176. if (id.includes('u21 world leagues')) return CONSTANTS.LEAGUE_TYPES.U21_WORLD;
  177. if (id.includes('u23 world leagues')) return CONSTANTS.LEAGUE_TYPES.U23_WORLD;
  178. if (id.includes('u18 leagues')) return CONSTANTS.LEAGUE_TYPES.U18;
  179. if (id.includes('u21 leagues')) return CONSTANTS.LEAGUE_TYPES.U21;
  180. if (id.includes('u23 leagues')) return CONSTANTS.LEAGUE_TYPES.U23;
  181. return CONSTANTS.LEAGUE_TYPES.WORLD;
  182. }
  183.  
  184. getLeaguesObjFromLeagueType(leagueType, country) {
  185. switch (leagueType) {
  186. case CONSTANTS.LEAGUE_TYPES.SENIOR:
  187. return country === 'All' ? this.getAllLeagues(this.seniorLeagues) : this.seniorLeagues[country];
  188. case CONSTANTS.LEAGUE_TYPES.WORLD:
  189. case CONSTANTS.LEAGUE_TYPES.U18_WORLD:
  190. case CONSTANTS.LEAGUE_TYPES.U21_WORLD:
  191. case CONSTANTS.LEAGUE_TYPES.U23_WORLD:
  192. return this.worldLeagues.World;
  193. case CONSTANTS.LEAGUE_TYPES.U18:
  194. case CONSTANTS.LEAGUE_TYPES.U21:
  195. case CONSTANTS.LEAGUE_TYPES.U23:
  196. return country === 'All' ? this.getAllLeagues(this.uxxLeagues) : this.uxxLeagues[country];
  197. default:
  198. return {};
  199. }
  200. }
  201.  
  202. getCountries(leagueType) {
  203. if (
  204. leagueType === CONSTANTS.LEAGUE_TYPES.WORLD ||
  205. leagueType === CONSTANTS.LEAGUE_TYPES.U18_WORLD ||
  206. leagueType === CONSTANTS.LEAGUE_TYPES.U21_WORLD ||
  207. leagueType === CONSTANTS.LEAGUE_TYPES.U23_WORLD
  208. ) {
  209. return ['World'];
  210. }
  211. if (leagueType === CONSTANTS.LEAGUE_TYPES.SENIOR) {
  212. return Object.keys(this.seniorLeagues);
  213. }
  214. return Object.keys(this.uxxLeagues);
  215. }
  216. }
  217.  
  218. class UIManager {
  219. constructor(leagueManager) {
  220. this.leagueManager = leagueManager;
  221. }
  222.  
  223. initializeInterface() {
  224. const mainContainer = document.createElement('div');
  225. mainContainer.id = 'league-buttons-container';
  226. const toggleBtn = this.createToggleButton();
  227. toggleBtn.onclick = this.toggleLeagueButtons;
  228.  
  229. CONSTANTS.BUTTON_NAMES.forEach(name => {
  230. const btn = document.createElement('button');
  231. btn.className = 'league-button';
  232. btn.id = `league-button-${name.toLowerCase()}`;
  233. btn.textContent = name;
  234. btn.style.display = 'none';
  235. btn.onclick = () => {
  236. const leaguesModal = this.createLeaguesModal('leagues-modal', btn);
  237. document.body.appendChild(leaguesModal);
  238. };
  239. mainContainer.appendChild(btn);
  240. });
  241.  
  242. mainContainer.appendChild(toggleBtn);
  243. document.querySelector(CONSTANTS.SELECTORS.STADIUM_WRAPPER).appendChild(mainContainer);
  244. }
  245.  
  246. createToggleButton() {
  247. const btn = document.createElement('button');
  248. btn.id = 'league-toggle-button';
  249. btn.textContent = CONSTANTS.TEXTS.TOGGLE_DOWN;
  250. return btn;
  251. }
  252.  
  253. toggleLeagueButtons() {
  254. const container = document.getElementById('league-buttons-container');
  255. const buttons = container.querySelectorAll('.league-button');
  256. const toggleBtn = document.getElementById('league-toggle-button');
  257.  
  258. buttons.forEach(btn => {
  259. if (btn.style.display === 'none') {
  260. btn.style.display = 'block';
  261. btn.classList.remove('fade-out');
  262. btn.classList.add('fade-in');
  263. toggleBtn.textContent = CONSTANTS.TEXTS.TOGGLE_UP;
  264. } else {
  265. btn.classList.remove('fade-in');
  266. btn.classList.add('fade-out');
  267. setTimeout(() => {
  268. btn.style.display = 'none';
  269. }, 200);
  270. toggleBtn.textContent = CONSTANTS.TEXTS.TOGGLE_DOWN;
  271. }
  272. });
  273. }
  274.  
  275. createLeaguesModal(modalId, button) {
  276. const leagueType = this.leagueManager.getLeagueTypeFromButtonId(button.id);
  277. const modal = document.createElement('div');
  278. modal.id = modalId;
  279.  
  280. const content = document.createElement('div');
  281. content.id = 'leagues-modal-content';
  282.  
  283. const title = document.createElement('h2');
  284. title.id = 'leagues-modal-title';
  285. title.textContent = button.textContent;
  286. content.appendChild(title);
  287.  
  288. const closeButton = document.createElement('button');
  289. closeButton.id = 'leagues-modal-close-button';
  290. closeButton.textContent = '×';
  291. closeButton.onclick = () => modal.remove();
  292. content.appendChild(closeButton);
  293.  
  294. const tablesContainer = document.createElement('div');
  295. tablesContainer.id = 'league-tables-container';
  296.  
  297. const countryDropdown = this.createCountryDropdown(leagueType);
  298. content.appendChild(countryDropdown);
  299.  
  300. const tabContainer = this.createTabContainer(leagueType, countryDropdown.value, tablesContainer);
  301. content.appendChild(tabContainer);
  302. content.appendChild(tablesContainer);
  303.  
  304. modal.appendChild(content);
  305. modal.onclick = (e) => {
  306. if (e.target === modal) {
  307. modal.remove();
  308. }
  309. };
  310.  
  311. return modal;
  312. }
  313.  
  314. createCountryDropdown(leagueType) {
  315. const dropdown = document.createElement('select');
  316. dropdown.id = 'country-dropdown';
  317.  
  318. if (
  319. leagueType !== CONSTANTS.LEAGUE_TYPES.WORLD &&
  320. leagueType !== CONSTANTS.LEAGUE_TYPES.U18_WORLD &&
  321. leagueType !== CONSTANTS.LEAGUE_TYPES.U21_WORLD &&
  322. leagueType !== CONSTANTS.LEAGUE_TYPES.U23_WORLD
  323. ) {
  324. const allOption = document.createElement('option');
  325. allOption.value = 'All';
  326. allOption.text = 'All';
  327. dropdown.appendChild(allOption);
  328. }
  329.  
  330. const countries = this.leagueManager.getCountries(leagueType);
  331. countries.forEach(country => {
  332. const option = document.createElement('option');
  333. option.value = country;
  334. option.text = country;
  335. dropdown.appendChild(option);
  336. });
  337.  
  338. dropdown.onchange = () => {
  339. const tablesContainer = document.getElementById('league-tables-container');
  340. const tabs = document.getElementById('league-tabs');
  341. while (tabs.firstChild) {
  342. tabs.removeChild(tabs.firstChild);
  343. }
  344. while (tablesContainer.firstChild) {
  345. tablesContainer.removeChild(tablesContainer.firstChild);
  346. }
  347. const leagueTypeFromTitle = this.leagueManager.getLeagueTypeFromButtonId('league-button-' + document.getElementById('leagues-modal-title').textContent.toLowerCase());
  348. const newTabs = this.createTabContainer(leagueTypeFromTitle, dropdown.value, tablesContainer);
  349. tabs.parentNode.replaceChild(newTabs, tabs);
  350. if (newTabs.firstChild) {
  351. newTabs.firstChild.click();
  352. }
  353. };
  354.  
  355. return dropdown;
  356. }
  357.  
  358. createTabContainer(leagueType, country, tablesContainer) {
  359. const container = document.createElement('div');
  360. container.id = 'league-tabs';
  361.  
  362. const leagues = this.leagueManager.getLeaguesObjFromLeagueType(leagueType, country);
  363. Object.keys(leagues).forEach(league => {
  364. const tab = document.createElement('button');
  365. tab.textContent = league;
  366. tab.onclick = async () => {
  367. while (tablesContainer.firstChild) {
  368. tablesContainer.removeChild(tablesContainer.firstChild);
  369. }
  370.  
  371. const modalContent = document.getElementById('leagues-modal-content');
  372. const loadingOverlay = document.createElement('div');
  373. loadingOverlay.className = 'loading-overlay';
  374. const spinner = document.createElement('div');
  375. spinner.className = 'loader';
  376. const loadingText = document.createElement('div');
  377. loadingText.className = 'loading-text';
  378. loadingText.textContent = CONSTANTS.TEXTS.LOADING;
  379. loadingOverlay.appendChild(spinner);
  380. loadingOverlay.appendChild(loadingText);
  381. modalContent.appendChild(loadingOverlay);
  382. loadingOverlay.style.display = 'flex';
  383.  
  384. const leagueEntries = leagues[league];
  385. const tables = await Promise.all(
  386. leagueEntries.map((entry, index) => {
  387. let sid, region;
  388. if (typeof entry === 'object') {
  389. sid = entry.sid;
  390. region = entry.region;
  391. } else {
  392. sid = entry;
  393. region = country;
  394. }
  395.  
  396. let divisionCount = index + 1;
  397. if (league === 'Division 1') {
  398. divisionCount = (index % 3) + 1;
  399. } else if (league === 'Division 2') {
  400. divisionCount = (index % 9) + 1;
  401. }
  402.  
  403. return this.fetchLeagueTable(sid, leagueType, divisionCount, league, region);
  404. })
  405. );
  406.  
  407. tables.forEach(data => {
  408. if (data) {
  409. const hr = document.createElement('hr');
  410. const title = this.createLeagueTitle(league, data.divisionCount, leagueType, data.region, data.sid);
  411. tablesContainer.appendChild(hr);
  412. tablesContainer.appendChild(title);
  413. tablesContainer.appendChild(data.table);
  414. }
  415. });
  416.  
  417. loadingOverlay.style.display = 'none';
  418. loadingOverlay.remove();
  419. };
  420. container.appendChild(tab);
  421. });
  422.  
  423. return container;
  424. }
  425.  
  426. async fetchLeagueTable(sid, leagueType, divisionCount, tabName, region) {
  427. try {
  428. const response = await fetch(
  429. `https://www.managerzone.com/ajax.php?p=league&type=${leagueType}&sid=${sid}&tid=${this.leagueManager.teamId}&sport=${this.leagueManager.sport}&sub=table`
  430. );
  431. const html = await response.text();
  432. const parser = new DOMParser();
  433. const doc = parser.parseFromString(html, 'text/html');
  434. const table = doc.querySelector('.nice_table');
  435.  
  436. if (!table) return null;
  437.  
  438. this.setUpTableLinks(table);
  439. this.setUpHelpButton(table, sid, leagueType);
  440.  
  441. return {
  442. table,
  443. divisionCount,
  444. region,
  445. sid
  446. };
  447. } catch (error) {
  448. return null;
  449. }
  450. }
  451.  
  452. setUpTableLinks(table) {
  453. const rows = table.querySelectorAll('tbody tr');
  454. rows.forEach(row => {
  455. const link = row.querySelector('a[href^="/?p=league&type="]');
  456. if (link) {
  457. const tid = link.href.match(/tid=(\d+)/);
  458. if (tid) {
  459. link.href = `/?p=team&tid=${tid[1]}`;
  460. }
  461. }
  462.  
  463. const helpButton = row.querySelector('.help_button');
  464. if (helpButton) {
  465. helpButton.style.pointerEvents = 'none';
  466. helpButton.style.opacity = '0.5';
  467. helpButton.style.cursor = 'default';
  468. helpButton.onclick = (e) => {
  469. e.preventDefault();
  470. return false;
  471. };
  472. helpButton.href = 'javascript:void(0);';
  473. }
  474. });
  475. }
  476.  
  477. setUpHelpButton(table, sid, leagueType) {
  478. const secondRow = table.querySelector('tbody tr:nth-child(2)');
  479. if (!secondRow) return;
  480.  
  481. const helpButton = secondRow.querySelector('.help_button');
  482. const teamLink = secondRow.querySelector('a[onclick*="purchaseChallenge"]');
  483.  
  484. if (helpButton && teamLink) {
  485. const tid = teamLink
  486. .getAttribute('onclick')
  487. .split(',')[2].replace(/[ ';)]/g, '');
  488. helpButton.removeAttribute('onclick');
  489. helpButton.onclick = () => this.handleExtraLeagueData(sid, leagueType, tid);
  490. }
  491. }
  492.  
  493. async handleExtraLeagueData(sid, leagueType, tid) {
  494. const response = await fetch(
  495. `https://www.managerzone.com/ajax.php?p=extraLeague&sub=division_runner_ups&type=${leagueType}&sid=${sid}&tid=${tid}&sport=${this.leagueManager.sport}`
  496. );
  497. const html = await response.text();
  498.  
  499. const modal = document.createElement('div');
  500. modal.id = 'extra-league-data-modal';
  501. modal.className = 'leagues-modal';
  502.  
  503. const content = document.createElement('div');
  504. content.className = 'leagues-modal-content';
  505. content.innerHTML = html;
  506.  
  507. modal.appendChild(content);
  508. document.body.appendChild(modal);
  509.  
  510. modal.onclick = (event) => {
  511. if (event.target === modal) modal.remove();
  512. };
  513. }
  514.  
  515. createLeagueTitle(selectedLeague, divisionCount, leagueType, region, sid) {
  516. const p = document.createElement('p');
  517. p.classList.add('league-table-title');
  518.  
  519. let leagueName;
  520. if (!selectedLeague.startsWith('Division')) {
  521. leagueName = selectedLeague;
  522. } else {
  523. const theDivision = selectedLeague + '.' + divisionCount;
  524. leagueName = theDivision.replace('Division', 'div');
  525. }
  526.  
  527. let typeName = leagueType.charAt(0).toUpperCase() + leagueType.slice(1);
  528. typeName = typeName.replace('_', ' ');
  529. let finalTitle = leagueName + ' ' + typeName;
  530.  
  531. if (region && region !== 'World') {
  532. finalTitle += ' ' + region;
  533. }
  534.  
  535. const a = document.createElement('a');
  536. a.textContent = finalTitle;
  537. a.href = `https://www.managerzone.com/?p=league&type=${leagueType}&sid=${sid}`;
  538. a.target = '_blank';
  539. p.appendChild(a);
  540.  
  541. return p;
  542. }
  543. }
  544.  
  545. const infoAboutTeam = document.querySelector(CONSTANTS.SELECTORS.TEAM_INFO);
  546. const teamStadiumWrapper = document.querySelector(CONSTANTS.SELECTORS.STADIUM_WRAPPER);
  547.  
  548. if (infoAboutTeam && teamStadiumWrapper) {
  549. const leagueManager = new LeagueManager();
  550. const uiManager = new UIManager(leagueManager);
  551. uiManager.initializeInterface();
  552. }
  553. })();