MZ - Ongoing Matches

Fetches results of ongoing matches and updates standings

  1. // ==UserScript==
  2. // @name MZ - Ongoing Matches
  3. // @namespace douglaskampl
  4. // @version 3.7
  5. // @description Fetches results of ongoing matches and updates standings
  6. // @author Douglas
  7. // @match https://www.managerzone.com/?p=league*
  8. // @match https://www.managerzone.com/?p=friendlyseries*
  9. // @match https://www.managerzone.com/?p=private_cup*
  10. // @match https://www.managerzone.com/?p=cup*
  11. // @icon https://www.google.com/s2/favicons?sz=64&domain=managerzone.com
  12. // @grant GM_addStyle
  13. // @grant GM_getResourceText
  14. // @require https://cdnjs.cloudflare.com/ajax/libs/nprogress/0.2.0/nprogress.min.js
  15. // @resource NPROGRESS_CSS https://unpkg.com/nprogress@0.2.0/nprogress.css
  16. // @resource ongoingMatchesStyles https://br18.org/mz/userscript/other/ongoingMatches.css
  17. // @run-at document-idle
  18. // @license MIT
  19. // ==/UserScript==
  20.  
  21. (function () {
  22. 'use strict';
  23.  
  24. GM_addStyle(GM_getResourceText('NPROGRESS_CSS'));
  25. GM_addStyle(GM_getResourceText('ongoingMatchesStyles'));
  26.  
  27. const UI = {
  28. BUTTON_STATES: {
  29. READY: 'Get match results',
  30. FETCHING: 'Processing…',
  31. DONE: 'Results updated'
  32. },
  33. PROGRESS_MESSAGES: {
  34. FETCHING_MATCHES: 'Fetching matches…',
  35. PROCESSING_RESULTS: 'Processing results…',
  36. UPDATING_STANDINGS: 'Updating standings…',
  37. ALL_COMPLETE: 'All updates complete'
  38. },
  39. MODAL: {
  40. NO_MATCHES_FOUND: 'Matches have not started yet. Please wait a few minutes.',
  41. STANDINGS_UPDATED: 'League standings have been updated with the results above.',
  42. NO_UPDATES_NEEDED: 'No ongoing matches. League standings were not updated.'
  43. },
  44. MATCH_STATUS: {
  45. WIN: 'green',
  46. DRAW: 'yellow',
  47. LOSS: 'red'
  48. }
  49. };
  50.  
  51. const SELECTORS = {
  52. TRACK_BUTTONS: [
  53. '[id^="trackButton_series_"]',
  54. '[id^="trackButton_u18_series_"]',
  55. '[id^="trackButton_u21_series_"]',
  56. '[id^="trackButton_u23_series_"]',
  57. '[id^="trackButton_world_series_"]',
  58. '[id^="trackButton_u18_world_series_"]',
  59. '[id^="trackButton_u21_world_series_"]',
  60. '[id^="trackButton_u23_world_series_"]',
  61. '[id^="trackButton_friendlyseries_"]',
  62. '[id^="trackButton_cup_"]',
  63. '[id^="trackButton_privatecup_"]'
  64. ],
  65. MATCHES_TABLE: 'table.hitlist',
  66. STANDINGS_TABLE: 'table.nice_table',
  67. LAST_SIX_CELL: 'td.responsive-hide'
  68. };
  69.  
  70. const ENDPOINTS = {
  71. MATCH_INFO: 'https://www.managerzone.com/xml/match_info.php?sport_id=1&match_id=',
  72. FRIENDLY_LEAGUE_SCHEDULE: 'https://www.managerzone.com/ajax.php?p=friendlySeries&sub=matches&sport=soccer&fsid='
  73. };
  74.  
  75. class OngoingMatchesTracker {
  76. constructor() {
  77. this.matchResults = new Map();
  78. this.isFriendlyLeague = window.location.href.includes('friendlyseries');
  79. this.isPrivateCup = window.location.href.includes('private_cup');
  80. this.isCup = window.location.href.includes('p=cup');
  81. this.hasRun = false;
  82. this.observe();
  83. }
  84.  
  85. async init() {
  86. const matches = await this.getMatches();
  87. if (!matches || !matches.length) return;
  88. this.setUpUI(matches);
  89. }
  90.  
  91. async getMatches() {
  92. if (this.isFriendlyLeague) return await this.getFriendlyLeagueMatches();
  93. if (this.isPrivateCup || this.isCup) return this.getCupMatches();
  94. return this.getLeagueMatches();
  95. }
  96.  
  97. getLeagueMatches() {
  98. const matchesTable = document.querySelector(SELECTORS.MATCHES_TABLE);
  99. if (!matchesTable) return [];
  100. return Array.from(matchesTable.querySelectorAll('tr'))
  101. .filter(row => {
  102. const link = row.querySelector('a[href*="mid="]');
  103. if (!link) return false;
  104. const score = link.textContent.trim();
  105. return !/^\d+\s*-\s*\d+$/.test(score) && !/^X\s*-\s*X$/.test(score);
  106. })
  107. .map(row => {
  108. const link = row.querySelector('a[href*="mid="]');
  109. const homeTeam = row.querySelector('td:first-child').textContent.trim();
  110. const awayTeam = row.querySelector('td:last-child').textContent.trim();
  111. const params = new URLSearchParams(link.href);
  112. return {
  113. mid: params.get('mid'),
  114. homeTeam,
  115. awayTeam
  116. };
  117. });
  118. }
  119.  
  120. async getFriendlyLeagueMatches() {
  121. const fsidMatch = window.location.href.match(/fsid=(\d+)/);
  122. if (!fsidMatch) return [];
  123. try {
  124. const response = await fetch(ENDPOINTS.FRIENDLY_LEAGUE_SCHEDULE + fsidMatch[1]);
  125. const text = await response.text();
  126. const doc = new DOMParser().parseFromString(text, 'text/html');
  127. const now = new Date();
  128. const ongoingMatches = [];
  129. const rounds = doc.querySelectorAll('h2.subheader.clearfix');
  130. rounds.forEach(round => {
  131. const headerText = round.textContent;
  132. const dateTimeMatch = headerText.match(/(\d{2}\/\d{2}\/\d{4})\s+(\d{1,2}:\d{2}(?:am|pm))/i);
  133. if (dateTimeMatch) {
  134. const dateStr = dateTimeMatch[1];
  135. const timeStr = dateTimeMatch[2];
  136. const matchTime = this.parseDateTime(dateStr, timeStr);
  137. const matchEndTime = new Date(matchTime.getTime() + 2 * 60 * 60 * 1000);
  138. const matchesDiv = round.nextElementSibling;
  139. if (matchesDiv && matchesDiv.classList.contains('mainContent')) {
  140. const matchRows = matchesDiv.querySelectorAll('tr');
  141. matchRows.forEach(m => {
  142. const link = m.querySelector('a[href*="mid="]');
  143. if (link) {
  144. const score = link.textContent.trim();
  145. if (!/^\d+-\d+$/.test(score)) {
  146. if (score === 'X - X') {
  147. if (now >= matchTime && now <= matchEndTime) {
  148. const homeTeam = m.querySelector('td:first-child').textContent.trim();
  149. const awayTeam = m.querySelector('td:last-child').textContent.trim();
  150. const params = new URLSearchParams(link.href);
  151. ongoingMatches.push({
  152. mid: params.get('mid'),
  153. homeTeam,
  154. awayTeam
  155. });
  156. }
  157. } else {
  158. const homeTeam = m.querySelector('td:first-child').textContent.trim();
  159. const awayTeam = m.querySelector('td:last-child').textContent.trim();
  160. const params = new URLSearchParams(link.href);
  161. ongoingMatches.push({
  162. mid: params.get('mid'),
  163. homeTeam,
  164. awayTeam
  165. });
  166. }
  167. }
  168. }
  169. });
  170. }
  171. }
  172. });
  173. return ongoingMatches;
  174. } catch (error) {
  175. return [];
  176. }
  177. }
  178.  
  179. getCupMatches() {
  180. const groupStages = document.querySelector('#group-stages');
  181. if (!groupStages) return [];
  182. return Array.from(groupStages.querySelectorAll('table.hitlist tr'))
  183. .filter(row => {
  184. const link = row.querySelector('a[href*="mid="]');
  185. if (!link) return false;
  186. const score = link.textContent.trim();
  187. return !/^\d+\s*-\s*\d+$/.test(score) && !/^X\s*-\s*X$/.test(score);
  188. })
  189. .map(row => {
  190. const link = row.querySelector('a[href*="mid="]');
  191. const homeTeam = row.querySelector('td:first-child').textContent.trim();
  192. const awayTeam = row.querySelector('td:last-child').textContent.trim();
  193. const params = new URLSearchParams(link.href);
  194. return {
  195. mid: params.get('mid'),
  196. homeTeam,
  197. awayTeam
  198. };
  199. });
  200. }
  201.  
  202. setUpUI(matches) {
  203. const trackButton = this.findTrackButton();
  204. if (!trackButton) return;
  205. if (trackButton.parentNode.classList.contains('mz-fetch-added')) return;
  206. trackButton.parentNode.classList.add('mz-fetch-added');
  207. const fetchButton = this.createFetchButton();
  208. trackButton.parentNode.insertBefore(fetchButton, trackButton.nextSibling);
  209. fetchButton.addEventListener('click', () => {
  210. if (!this.hasRun) this.handleFetchClick(fetchButton, matches);
  211. });
  212. }
  213.  
  214. findTrackButton() {
  215. return SELECTORS.TRACK_BUTTONS.reduce(
  216. (found, selector) => found || document.querySelector(selector),
  217. null
  218. );
  219. }
  220.  
  221. createFetchButton() {
  222. const button = document.createElement('button');
  223. button.className = 'mz-fetch-button';
  224. button.textContent = UI.BUTTON_STATES.READY;
  225. return button;
  226. }
  227.  
  228. showResultsModal(results) {
  229. const modal = document.createElement('div');
  230. modal.className = 'mz-modal';
  231. const roundNum = this.getCurrentRound();
  232. const headerTitle = roundNum
  233. ? `Match Results for Round ${roundNum}`
  234. : 'Match Results for Current Round';
  235.  
  236. const header = document.createElement('div');
  237. header.className = 'mz-modal-header';
  238. header.innerHTML = `
  239. <h3>${headerTitle}</h3>
  240. <button class="mz-modal-close">Close</button>
  241. `;
  242.  
  243. const content = document.createElement('div');
  244. content.className = 'mz-modal-content';
  245.  
  246. if (results && results.length > 0) {
  247. results.forEach(r => {
  248. const matchDiv = document.createElement('div');
  249. matchDiv.className = 'mz-match-result';
  250. const matchLink = document.createElement('a');
  251. matchLink.href = `https://www.managerzone.com/?p=match&mid=${r.mid}`;
  252. matchLink.className = 'mz-match-link';
  253. matchLink.textContent = `${r.homeTeam} ${r.score} ${r.awayTeam}`;
  254. matchDiv.appendChild(matchLink);
  255. content.appendChild(matchDiv);
  256. });
  257. } else {
  258. const noMatchesDiv = document.createElement('div');
  259. noMatchesDiv.className = 'mz-no-matches';
  260. noMatchesDiv.textContent = UI.MODAL.NO_MATCHES_FOUND;
  261. content.appendChild(noMatchesDiv);
  262. }
  263.  
  264. const footer = document.createElement('div');
  265. footer.className = 'mz-modal-footer';
  266. footer.innerHTML = `
  267. <div class="mz-update-info">
  268. ${results && results.length > 0
  269. ? UI.MODAL.STANDINGS_UPDATED
  270. : UI.MODAL.NO_UPDATES_NEEDED}
  271. </div>
  272. `;
  273.  
  274. modal.appendChild(header);
  275. modal.appendChild(content);
  276. modal.appendChild(footer);
  277. document.body.appendChild(modal);
  278. setTimeout(() => modal.classList.add('show'), 10);
  279.  
  280. modal.querySelector('.mz-modal-close').addEventListener('click', () => {
  281. modal.classList.remove('show');
  282. setTimeout(() => modal.remove(), 300);
  283. });
  284. }
  285.  
  286. async clickStandingsTab() {
  287. if (window.location.href === 'https://www.managerzone.com/?p=league&type=senior') {
  288. const ul = document.querySelectorAll('.ui-tabs-nav')[0];
  289. if (ul) {
  290. const tabs = ul.querySelectorAll('li[role="tab"]');
  291. if (tabs.length >= 1) {
  292. const firstTab = tabs[0];
  293. if (
  294. !firstTab.classList.contains('ui-tabs-active') &&
  295. !firstTab.classList.contains('ui-state-active')
  296. ) {
  297. firstTab.querySelector('a').click();
  298. await new Promise(r => setTimeout(r, 1000));
  299. }
  300. }
  301. }
  302. } else if (window.location.href.includes('p=league')) {
  303. const ul = document.querySelectorAll('.ui-tabs-nav')[0];
  304. if (ul) {
  305. const tabs = ul.querySelectorAll('li[role="tab"]');
  306. if (tabs.length >= 2) {
  307. const secondTab = tabs[1];
  308. if (
  309. !secondTab.classList.contains('ui-tabs-active') &&
  310. !secondTab.classList.contains('ui-state-active')
  311. ) {
  312. secondTab.querySelector('a').click();
  313. await new Promise(r => setTimeout(r, 1000));
  314. }
  315. }
  316. }
  317. } else if (this.isFriendlyLeague) {
  318. const allUls = document.querySelectorAll('.ui-tabs-nav');
  319. if (allUls.length >= 2) {
  320. const secondUl = allUls[1];
  321. const tabs = secondUl.querySelectorAll('li[role="tab"]');
  322. if (tabs.length >= 2) {
  323. const secondTab = tabs[1];
  324. if (
  325. !secondTab.classList.contains('ui-tabs-active') &&
  326. !secondTab.classList.contains('ui-state-active')
  327. ) {
  328. secondTab.querySelector('a').click();
  329. await new Promise(r => setTimeout(r, 1000));
  330. }
  331. }
  332. }
  333. }
  334. }
  335.  
  336. async handleFetchClick(button, matches) {
  337. if (this.hasRun) return;
  338. this.hasRun = true;
  339.  
  340. await this.clickStandingsTab();
  341.  
  342. NProgress.configure({ showSpinner: false });
  343. NProgress.start();
  344. this.showStatusMessage(UI.PROGRESS_MESSAGES.FETCHING_MATCHES);
  345.  
  346. if (!matches.length) {
  347. if (this.isFriendlyLeague || this.isPrivateCup || this.isCup) {
  348. this.showResultsModal([]);
  349. }
  350. NProgress.done();
  351. return;
  352. }
  353.  
  354. button.classList.add('disabled');
  355. button.textContent = UI.BUTTON_STATES.FETCHING;
  356.  
  357. this.showStatusMessage(UI.PROGRESS_MESSAGES.PROCESSING_RESULTS);
  358. const results = await this.processMatches(matches);
  359.  
  360. if (this.isFriendlyLeague || this.isPrivateCup || this.isCup) {
  361. this.showResultsModal(results);
  362. }
  363.  
  364. this.showStatusMessage(UI.PROGRESS_MESSAGES.UPDATING_STANDINGS);
  365. this.updateAllTeamStats();
  366.  
  367. button.classList.remove('disabled');
  368. button.classList.add('done');
  369. button.textContent = UI.BUTTON_STATES.DONE;
  370.  
  371. NProgress.done();
  372. this.showStatusMessage(UI.PROGRESS_MESSAGES.ALL_COMPLETE);
  373. }
  374.  
  375. parseDateTime(dateStr, timeStr) {
  376. const [day, month, year] = dateStr.split('/');
  377. const date = `${month}/${day}/${year}`;
  378. let [time, period] = timeStr.toLowerCase().split(/(?=[ap]m)/);
  379. let [hours, minutes] = time.split(':');
  380. hours = parseInt(hours);
  381. if (period === 'pm' && hours !== 12) hours += 12;
  382. else if (period === 'am' && hours === 12) hours = 0;
  383. return new Date(`${date} ${hours}:${minutes}`);
  384. }
  385.  
  386. getCurrentRound() {
  387. const table = document.querySelector(SELECTORS.STANDINGS_TABLE);
  388. if (!table) return null;
  389. const firstDataRow = table.querySelector('tbody tr');
  390. if (!firstDataRow) return null;
  391. const matchesCell = firstDataRow.querySelector('td:nth-child(3)');
  392. if (!matchesCell) return null;
  393. return parseInt(matchesCell.textContent.trim(), 10) + 1;
  394. }
  395.  
  396. async processMatches(matches) {
  397. const results = [];
  398. const total = matches.length;
  399. for (let i = 0; i < total; i++) {
  400. const match = matches[i];
  401. try {
  402. const response = await fetch(`${ENDPOINTS.MATCH_INFO}${match.mid}`);
  403. const text = await response.text();
  404. const matchData = this.parseMatchResponse({ responseText: text });
  405. if (matchData) {
  406. matchData.homeTeam = match.homeTeam;
  407. matchData.awayTeam = match.awayTeam;
  408. this.matchResults.set(match.mid, matchData);
  409. this.updateMatchDisplay(match.mid, matchData);
  410. results.push({
  411. ...match,
  412. score: `${matchData.homeGoals}-${matchData.awayGoals}`
  413. });
  414. }
  415. } catch (error) { }
  416. NProgress.set((i + 1) / total);
  417. }
  418. return results;
  419. }
  420.  
  421. parseMatchResponse(response) {
  422. const parser = new DOMParser();
  423. const xmlDoc = parser.parseFromString(response.responseText, 'application/xml');
  424. const matchNode = xmlDoc.querySelector('Match');
  425. if (!matchNode) return null;
  426. const homeTeam = matchNode.querySelector('Team[field="home"]');
  427. const awayTeam = matchNode.querySelector('Team[field="away"]');
  428. if (!homeTeam || !awayTeam) return null;
  429. return {
  430. homeTid: homeTeam.getAttribute('id'),
  431. awayTid: awayTeam.getAttribute('id'),
  432. homeGoals: parseInt(homeTeam.getAttribute('goals'), 10) || 0,
  433. awayGoals: parseInt(awayTeam.getAttribute('goals'), 10) || 0
  434. };
  435. }
  436.  
  437. updateMatchDisplay(mid, matchData) {
  438. const link = Array.from(document.links).find(l => l.href.includes(`mid=${mid}`));
  439. if (link) {
  440. link.textContent = `${matchData.homeGoals}-${matchData.awayGoals}`;
  441. }
  442. }
  443.  
  444. calculateMatchResult(matchData) {
  445. if (matchData.homeGoals > matchData.awayGoals) {
  446. return {
  447. home: { points: 3, goalsFor: matchData.homeGoals, goalsAgainst: matchData.awayGoals },
  448. away: { points: 0, goalsFor: matchData.awayGoals, goalsAgainst: matchData.homeGoals }
  449. };
  450. } else if (matchData.homeGoals < matchData.awayGoals) {
  451. return {
  452. home: { points: 0, goalsFor: matchData.homeGoals, goalsAgainst: matchData.awayGoals },
  453. away: { points: 3, goalsFor: matchData.awayGoals, goalsAgainst: matchData.homeGoals }
  454. };
  455. } else {
  456. return {
  457. home: { points: 1, goalsFor: matchData.homeGoals, goalsAgainst: matchData.awayGoals },
  458. away: { points: 1, goalsFor: matchData.awayGoals, goalsAgainst: matchData.homeGoals }
  459. };
  460. }
  461. }
  462.  
  463. sortTableByPoints() {
  464. const table = document.querySelector(SELECTORS.STANDINGS_TABLE);
  465. if (!table) return;
  466. const tbody = table.querySelector('tbody');
  467. if (!tbody) return;
  468. const dataRows = Array.from(tbody.querySelectorAll('tr')).filter(r => {
  469. const cells = r.querySelectorAll('td');
  470. return cells.length >= 10 && cells[2] && !isNaN(parseInt(cells[2].textContent));
  471. });
  472. dataRows.sort((a, b) => {
  473. const getVal = (rw, i) => parseInt(rw.querySelectorAll('td')[i].textContent.trim(), 10) || 0;
  474. const pointsA = getVal(a, 9);
  475. const pointsB = getVal(b, 9);
  476. if (pointsB !== pointsA) return pointsB - pointsA;
  477. const goalDiffA = getVal(a, 6) - getVal(a, 7);
  478. const goalDiffB = getVal(b, 6) - getVal(b, 7);
  479. if (goalDiffB !== goalDiffA) return goalDiffB - goalDiffA;
  480. return getVal(b, 6) - getVal(a, 6);
  481. });
  482. const nonDataRows = Array.from(tbody.children).filter(r => !dataRows.includes(r));
  483. const tempContainer = document.createDocumentFragment();
  484. dataRows.forEach((row, index) => {
  485. const originalRow = row.cloneNode(true);
  486. const positionCell = originalRow.querySelector('td:first-child span');
  487. if (positionCell) {
  488. const prevHelpButton = row.querySelector('td:first-child span .help_button');
  489. positionCell.innerHTML = `${index + 1}`;
  490. if (prevHelpButton) positionCell.appendChild(prevHelpButton);
  491. }
  492. originalRow.className = index % 2 === 0 ? '' : 'highlight_row';
  493. if (index === 0) originalRow.classList.add('mz-table-row-champion');
  494. else if (index === 1) originalRow.classList.add('mz-table-row-promotion');
  495. else if (index === 7) originalRow.classList.add('mz-table-row-relegation');
  496. tempContainer.appendChild(originalRow);
  497. });
  498. nonDataRows.forEach(r => {
  499. tempContainer.appendChild(r.cloneNode(true));
  500. });
  501. tbody.innerHTML = '';
  502. tbody.appendChild(tempContainer);
  503. table.classList.add('standings-updated');
  504. setTimeout(() => table.classList.remove('standings-updated'), 2000);
  505. }
  506.  
  507. findTeamRows(tid) {
  508. const teamLinks = Array.from(document.querySelectorAll(`a[href*="tid=${tid}"]`));
  509. const rowsSet = new Set();
  510. teamLinks.forEach(link => {
  511. const row = link.closest('tr');
  512. if (row) rowsSet.add(row);
  513. });
  514. const highlightedRows = Array.from(document.querySelectorAll('.highlight_row'))
  515. .filter(r => r.querySelector(`a[href*="tid=${tid}"]`));
  516. highlightedRows.forEach(r => rowsSet.add(r));
  517. return Array.from(rowsSet);
  518. }
  519.  
  520. updateAllTeamStats() {
  521. this.matchResults.forEach(m => {
  522. const result = this.calculateMatchResult(m);
  523. this.updateTeamRow(m.homeTid, result.home);
  524. this.updateTeamRow(m.awayTid, result.away);
  525. });
  526. this.sortTableByPoints();
  527. }
  528.  
  529. updateTeamRow(tid, result) {
  530. const teamRows = this.findTeamRows(tid);
  531. if (!teamRows.length) return;
  532. teamRows.forEach(row => {
  533. const cells = row.querySelectorAll('td');
  534. if (cells.length < 10) return;
  535. const parseCell = c => parseInt(c.textContent, 10) || 0;
  536. const updateCell = (c, val) => {
  537. c.classList.add('updated-cell');
  538. c.textContent = val;
  539. setTimeout(() => c.classList.remove('updated-cell'), 1500);
  540. };
  541. updateCell(cells[2], parseCell(cells[2]) + 1);
  542. if (result.points === 3) updateCell(cells[3], parseCell(cells[3]) + 1);
  543. else if (result.points === 1) updateCell(cells[4], parseCell(cells[4]) + 1);
  544. else updateCell(cells[5], parseCell(cells[5]) + 1);
  545. updateCell(cells[6], parseCell(cells[6]) + result.goalsFor);
  546. updateCell(cells[7], parseCell(cells[7]) + result.goalsAgainst);
  547. const goalDiff = parseCell(cells[6]) - parseCell(cells[7]);
  548. const goalDiffElem = cells[8].querySelector('nobr');
  549. if (goalDiffElem) updateCell(goalDiffElem, goalDiff);
  550. updateCell(cells[9], parseCell(cells[9]) + result.points);
  551. this.updateLastSixDisplay(row, result, tid);
  552. });
  553. }
  554.  
  555. updateLastSixDisplay(row, result, tid) {
  556. const lastSixCell = row.querySelector(SELECTORS.LAST_SIX_CELL);
  557. if (!lastSixCell) return;
  558. const nobrElement = lastSixCell.querySelector('nobr');
  559. if (!nobrElement) return;
  560. const links = nobrElement.querySelectorAll('a');
  561. const allTeamMatches = Array.from(this.matchResults.entries()).filter(([_, data]) => data.homeTid === tid || data.awayTid === tid);
  562. if (!allTeamMatches.length) return;
  563. const [matchId, data] = allTeamMatches[allTeamMatches.length - 1];
  564. const matchStatus = this.getMatchStatus(result);
  565. if (links.length >= 6) links[0].remove();
  566. const newStatusLink = this.createStatusLink(matchStatus, matchId, data, tid);
  567. nobrElement.appendChild(newStatusLink);
  568. }
  569.  
  570. getMatchStatus(result) {
  571. if (result.points === 3) return UI.MATCH_STATUS.WIN;
  572. if (result.points === 1) return UI.MATCH_STATUS.DRAW;
  573. return UI.MATCH_STATUS.LOSS;
  574. }
  575.  
  576. createStatusLink(status, matchId, matchData, tid) {
  577. const link = document.createElement('a');
  578. link.href = `/?p=match&sub=result&mid=${matchId}`;
  579. const isHome = matchData.homeTid === tid;
  580. const teamScore = isHome ? matchData.homeGoals : matchData.awayGoals;
  581. const oppScore = isHome ? matchData.awayGoals : matchData.homeGoals;
  582. link.title = `${matchData.homeTeam} ${matchData.homeGoals} - ${matchData.awayGoals} ${matchData.awayTeam}`;
  583. const img = document.createElement('img');
  584. img.style.border = '0';
  585. img.src = `img/status_${status}.gif`;
  586. img.alt = '';
  587. img.width = '13';
  588. img.height = '12';
  589. link.appendChild(img);
  590. return link;
  591. }
  592.  
  593. showStatusMessage(message) {
  594. const existingMessage = document.querySelector('.status-message');
  595. existingMessage?.remove();
  596. const messageElement = document.createElement('div');
  597. messageElement.className = 'status-message';
  598. messageElement.textContent = message;
  599. document.body.appendChild(messageElement);
  600. setTimeout(() => {
  601. messageElement.style.opacity = '0';
  602. setTimeout(() => messageElement.remove(), 300);
  603. }, 2000);
  604. }
  605.  
  606. observe() {
  607. const observer = new MutationObserver((mutations) => {
  608. for (const mutation of mutations) {
  609. if (mutation.addedNodes.length) {
  610. const trackButton = this.findTrackButton();
  611. if (trackButton && !document.querySelector('.mz-fetch-button')) {
  612. this.init();
  613. break;
  614. }
  615. }
  616. }
  617. });
  618.  
  619. observer.observe(document.body, {
  620. childList: true,
  621. subtree: true
  622. });
  623. }
  624. }
  625.  
  626. const tracker = new OngoingMatchesTracker();
  627. tracker.init();
  628. })();