MZ - Multiple League Standings in a Single View

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

目前為 2024-12-26 提交的版本,檢視 最新版本

// ==UserScript==
// @name          MZ - Multiple League Standings in a Single View
// @namespace     douglaskampl
// @version       3.6
// @description   Displays leagues and world leagues, grouped by div and/or region, in a single view
// @author        Douglas
// @match         https://www.managerzone.com/?p=team
// @icon          https://www.google.com/s2/favicons?sz=64&domain=managerzone.com
// @grant         GM_addStyle
// @grant         GM_getResourceText
// @resource      sLeagueStandingsStyles https://u18mz.vercel.app/mz/userscript/other/sLeagueStandings.css
// @run-at        document-idle
// @license       MIT
// ==/UserScript==

(function () {
  "use strict";

  GM_addStyle(GM_getResourceText("sLeagueStandingsStyles"));

  const UI = {
    TEXTS: {
      TOGGLE_UP: "UP 上",
      TOGGLE_DOWN: "DOWN 下",
      LOADING: "Loading ロード中…"
    },
    SELECTORS: {
      TEAM_INFO: "#infoAboutTeam",
      STADIUM_WRAPPER: "#team-stadium-wrapper",
      SHORTCUT_LINK: "#shortcut_link_thezone"
    }
  };

  const LEAGUE_TYPES = {
    SENIOR: "senior",
    U18: "u18",
    U21: "u21",
    U23: "u23",
    WORLD: "world",
    U18_WORLD: "u18_world",
    U21_WORLD: "u21_world",
    U23_WORLD: "u23_world"
  };

  const DIVISION = {
    NAMES: {
      TOP: "Top Division",
      TOP_SERIES: "Top Series"
    },
    STRUCTURE: {
      BASE_DIVISIONS: 3,
      MAX_LEVEL: 4
    }
  };

  const REGIONS = {
    SOCCER: {
      COUNTRIES: [
        { name: "Argentina", start: 16096 },
        { name: "Brazil", start: 26187 },
        { name: "China", start: 70847 },
        { name: "Germany", start: 12086 },
        { name: "Italy", start: 10625 },
        { name: "Netherlands", start: 15004 },
        { name: "Portugal", start: 17566 },
        { name: "Spain", start: 10746 },
        { name: "Poland", start: 13181 },
        { name: "Romania", start: 17929 },
        { name: "Sweden", start: 43 },
        { name: "Turkey", start: 20356 }
      ],
      UXX_REGIONS: [
        { name: "Argentina", start: 1 },
        { name: "Brazil", start: 122 },
        { name: "Latin America, USA and Canada", start: 727 },
        { name: "Central Europe", start: 848 },
        { name: "Iberia", start: 969 },
        { name: "Mediterranean", start: 1090 },
        { name: "Northern Europe", start: 1211 },
        { name: "Poland", start: 243 },
        { name: "Romania", start: 364 },
        { name: "Sweden", start: 485 },
        { name: "Turkey", start: 606 },
        { name: "China/Asia/Africa", start: 1332 }
      ]
    },
    HOCKEY: {
      COUNTRIES: [
        { name: "Brazil", start: 7900 },
        { name: "Sweden", start: 1 },
        { name: "Argentina", start: 2 },
        { name: "Poland", start: 23 },
        { name: "Portugal", start: 24 },
        { name: "Spain", start: 12 },
        { name: "Romania", start: 25 },
        { name: "Turkey", start: 27 },
        { name: "China", start: 13727 }
      ],
      UXX_REGIONS: [
        { name: "Northern Europe", start: 1 },
        { name: "Southern Europe", start: 122 },
        { name: "Rest of the World", start: 243 }
      ]
    }
  };

  const CONSTANTS = {
    BUTTON_NAMES: [
      "Senior Leagues",
      "U18 Leagues",
      "U21 Leagues",
      "U23 Leagues",
      "Senior World Leagues",
      "U18 World Leagues",
      "U21 World Leagues",
      "U23 World Leagues"
    ],
    TOGGLE_TEXT_DOWN: UI.TEXTS.TOGGLE_DOWN,
    TOGGLE_TEXT_UP: UI.TEXTS.TOGGLE_UP,
    LOADING_TEXT: UI.TEXTS.LOADING
  };

  class LeagueManager {
    constructor() {
      const shortcutLink = document.querySelector(UI.SELECTORS.SHORTCUT_LINK);
      this.sport = new URL(shortcutLink.href).searchParams.get("sport");
      const teamInfo = document.querySelector(UI.SELECTORS.TEAM_INFO);
      this.teamId = RegExp(/\((\d+)\)/).exec(teamInfo.querySelector("dd").textContent)[1];
      this.seniorLeagues = this.getSeniorLeagues();
      this.worldLeagues = { World: this.getWorldLeaguesObj() };
      this.uxxLeagues = this.getUxxLeagues();
    }

    getSeniorLeagues() {
      const countries = this.sport === "soccer" ? REGIONS.SOCCER.COUNTRIES : REGIONS.HOCKEY.COUNTRIES;
      return countries.reduce((acc, { name, start }) => {
        acc[name] = { [DIVISION.NAMES.TOP]: [start] };
        return acc;
      }, {});
    }

    getWorldLeaguesObj() {
      const leagues = {};
      let start = 1;
      for (let i = 0; i <= DIVISION.STRUCTURE.MAX_LEVEL; i++) {
        const divisionName = i === 0 ? DIVISION.NAMES.TOP_SERIES : `Division ${i}`;
        leagues[divisionName] = [];
        const numLeagues = Math.pow(DIVISION.STRUCTURE.BASE_DIVISIONS, i);
        for (let j = 0; j < numLeagues; j++) {
          leagues[divisionName].push(start++);
        }
      }
      return leagues;
    }

    getUxxLeagues() {
      const regions = this.sport === "soccer" ? REGIONS.SOCCER.UXX_REGIONS : REGIONS.HOCKEY.UXX_REGIONS;
      const obj = {};
      regions.forEach(region => {
        obj[region.name] = {
          [DIVISION.NAMES.TOP]: [region.start],
          "Division 1": Array.from({ length: DIVISION.STRUCTURE.BASE_DIVISIONS }, (_, i) => region.start + i + 1),
          "Division 2": Array.from(
            { length: Math.pow(DIVISION.STRUCTURE.BASE_DIVISIONS, 2) },
            (_, i) => region.start + i + 4
          )
        };
      });
      return obj;
    }

    getAllLeagues(leaguesObj) {
      const allLeagues = {};
      Object.entries(leaguesObj).forEach(([country, leagues]) => {
        Object.entries(leagues).forEach(([leagueName, ids]) => {
          if (!allLeagues[leagueName]) allLeagues[leagueName] = [];
          ids.forEach((id) => {
            allLeagues[leagueName].push({ sid: id, region: country });
          });
        });
      });
      return allLeagues;
    }

    getLeagueTypeFromButtonId(id) {
      if (id.includes("senior leagues")) return LEAGUE_TYPES.SENIOR;
      if (id.includes("u18 world leagues")) return LEAGUE_TYPES.U18_WORLD;
      if (id.includes("u21 world leagues")) return LEAGUE_TYPES.U21_WORLD;
      if (id.includes("u23 world leagues")) return LEAGUE_TYPES.U23_WORLD;
      if (id.includes("u18 leagues")) return LEAGUE_TYPES.U18;
      if (id.includes("u21 leagues")) return LEAGUE_TYPES.U21;
      if (id.includes("u23 leagues")) return LEAGUE_TYPES.U23;
      return LEAGUE_TYPES.WORLD;
    }

    getLeaguesObjFromLeagueType(leagueType, country) {
      switch (leagueType) {
        case LEAGUE_TYPES.SENIOR:
          return country === "All" ? this.getAllLeagues(this.seniorLeagues) : this.seniorLeagues[country];
        case LEAGUE_TYPES.WORLD:
        case LEAGUE_TYPES.U18_WORLD:
        case LEAGUE_TYPES.U21_WORLD:
        case LEAGUE_TYPES.U23_WORLD:
          return this.worldLeagues.World;
        case LEAGUE_TYPES.U18:
        case LEAGUE_TYPES.U21:
        case LEAGUE_TYPES.U23:
          return country === "All" ? this.getAllLeagues(this.uxxLeagues) : this.uxxLeagues[country];
        default:
          return {};
      }
    }

    getCountries(leagueType) {
      if (
        leagueType === LEAGUE_TYPES.WORLD ||
        leagueType === LEAGUE_TYPES.U18_WORLD ||
        leagueType === LEAGUE_TYPES.U21_WORLD ||
        leagueType === LEAGUE_TYPES.U23_WORLD
      ) {
        return ["World"];
      }
      if (leagueType === LEAGUE_TYPES.SENIOR) {
        return Object.keys(this.seniorLeagues);
      }
      return Object.keys(this.uxxLeagues);
    }
  }

  class UIManager {
    constructor(leagueManager) {
      this.leagueManager = leagueManager;
    }

    initializeInterface() {
      const mainContainer = document.createElement("div");
      mainContainer.id = "league-buttons-container";

      const toggleBtn = this.createToggleButton();
      toggleBtn.onclick = this.toggleLeagueButtons;

      CONSTANTS.BUTTON_NAMES.forEach(name => {
        const btn = document.createElement("button");
        btn.className = "league-button";
        btn.id = `league-button-${name.toLowerCase()}`;
        btn.textContent = name;
        btn.style.display = "none";
        btn.onclick = () => {
          const leaguesModal = this.createLeaguesModal("leagues-modal", btn);
          document.body.appendChild(leaguesModal);
        };
        mainContainer.appendChild(btn);
      });

      mainContainer.appendChild(toggleBtn);
      document.querySelector(UI.SELECTORS.STADIUM_WRAPPER).appendChild(mainContainer);
    }

    createToggleButton() {
      const btn = document.createElement("button");
      btn.id = "league-toggle-button";
      btn.textContent = CONSTANTS.TOGGLE_TEXT_DOWN;
      return btn;
    }

    toggleLeagueButtons() {
      const container = document.getElementById("league-buttons-container");
      const buttons = container.querySelectorAll(".league-button");
      const toggleBtn = document.getElementById("league-toggle-button");

      buttons.forEach(btn => {
        if (btn.style.display === "none") {
          btn.style.display = "block";
          btn.classList.remove("fade-out");
          btn.classList.add("fade-in");
          toggleBtn.textContent = CONSTANTS.TOGGLE_TEXT_UP;
        } else {
          btn.classList.remove("fade-in");
          btn.classList.add("fade-out");
          setTimeout(() => {
            btn.style.display = "none";
          }, 200);
          toggleBtn.textContent = CONSTANTS.TOGGLE_TEXT_DOWN;
        }
      });
    }

    createLeaguesModal(modalId, button) {
      const leagueType = this.leagueManager.getLeagueTypeFromButtonId(button.id);
      const modal = document.createElement("div");
      modal.id = modalId;

      const content = document.createElement("div");
      content.id = "leagues-modal-content";

      const title = document.createElement("h2");
      title.id = "leagues-modal-title";
      title.textContent = button.textContent;
      content.appendChild(title);

      const closeButton = document.createElement("button");
      closeButton.id = "leagues-modal-close-button";
      closeButton.textContent = "×";
      closeButton.onclick = () => modal.remove();
      content.appendChild(closeButton);

      const tablesContainer = document.createElement("div");
      tablesContainer.id = "league-tables-container";

      const countryDropdown = this.createCountryDropdown(leagueType);
      content.appendChild(countryDropdown);

      const tabContainer = this.createTabContainer(leagueType, countryDropdown.value, tablesContainer);
      content.appendChild(tabContainer);
      content.appendChild(tablesContainer);

      modal.appendChild(content);

      modal.onclick = (e) => {
        if (e.target === modal) {
          modal.remove();
        }
      };

      return modal;
    }

    createCountryDropdown(leagueType) {
      const dropdown = document.createElement("select");
      dropdown.id = "country-dropdown";

      if (
        leagueType !== LEAGUE_TYPES.WORLD &&
        leagueType !== LEAGUE_TYPES.U18_WORLD &&
        leagueType !== LEAGUE_TYPES.U21_WORLD &&
        leagueType !== LEAGUE_TYPES.U23_WORLD
      ) {
        const allOption = document.createElement("option");
        allOption.value = "All";
        allOption.text = "All";
        dropdown.appendChild(allOption);
      }

      const countries = this.leagueManager.getCountries(leagueType);
      countries.forEach(country => {
        const option = document.createElement("option");
        option.value = country;
        option.text = country;
        dropdown.appendChild(option);
      });

      dropdown.onchange = () => {
        const tablesContainer = document.getElementById("league-tables-container");
        const tabs = document.getElementById("league-tabs");
        while (tabs.firstChild) {
          tabs.removeChild(tabs.firstChild);
        }
        while (tablesContainer.firstChild) {
          tablesContainer.removeChild(tablesContainer.firstChild);
        }
        const newTabs = this.createTabContainer(leagueType, dropdown.value, tablesContainer);
        tabs.parentNode.replaceChild(newTabs, tabs);
        if (newTabs.firstChild) {
          newTabs.firstChild.click();
        }
      };
      return dropdown;
    }

    createTabContainer(leagueType, country, tablesContainer) {
      const container = document.createElement("div");
      container.id = "league-tabs";

      const leagues = this.leagueManager.getLeaguesObjFromLeagueType(leagueType, country);

      Object.keys(leagues).forEach(league => {
        const tab = document.createElement("button");
        tab.textContent = league;
        tab.onclick = async () => {
          while (tablesContainer.firstChild) {
            tablesContainer.removeChild(tablesContainer.firstChild);
          }
          const modalContent = document.getElementById("leagues-modal-content");
          const loadingOverlay = document.createElement("div");
          loadingOverlay.className = "loading-overlay";

          const spinner = document.createElement("div");
          spinner.className = "loader";

          const loadingText = document.createElement("div");
          loadingText.className = "loading-text";
          loadingText.textContent = CONSTANTS.LOADING_TEXT;

          loadingOverlay.appendChild(spinner);
          loadingOverlay.appendChild(loadingText);
          modalContent.appendChild(loadingOverlay);
          loadingOverlay.style.display = "flex";

          const leagueEntries = leagues[league];
          const tables = await Promise.all(
            leagueEntries.map((entry, index) => {
              let sid, region;
              if (typeof entry === "object") {
                sid = entry.sid;
                region = entry.region;
              } else {
                sid = entry;
                region = country;
              }
              return this.fetchLeagueTable(sid, leagueType, index + 1, league, region);
            })
          );

          tables.sort((a, b) => a.divisionCount - b.divisionCount);

          tables.forEach(data => {
            if (data) {
              const hr = document.createElement("hr");
              const title = this.createLeagueTitle(league, data.divisionCount, leagueType, data.region, league);
              tablesContainer.appendChild(hr);
              tablesContainer.appendChild(title);
              tablesContainer.appendChild(data.table);
            }
          });

          loadingOverlay.style.display = "none";
          loadingOverlay.remove();
        };
        container.appendChild(tab);
      });
      return container;
    }

    async fetchLeagueTable(sid, leagueType, divisionCount, tabName, region) {
      try {
        const response = await fetch(
          `https://www.managerzone.com/ajax.php?p=league&type=${leagueType}&sid=${sid}&tid=${this.leagueManager.teamId}&sport=${this.leagueManager.sport}&sub=table`
        );
        const html = await response.text();
        const parser = new DOMParser();
        const doc = parser.parseFromString(html, "text/html");
        const table = doc.querySelector(".nice_table");
        if (!table) return null;
        this.setUpTableLinks(table);
        this.setUpHelpButton(table, sid, leagueType);
        return {
          table,
          divisionCount,
          region
        };
      } catch (error) {
        console.error("Error fetching league table:", error);
        return null;
      }
    }

    setUpTableLinks(table) {
      const rows = table.querySelectorAll("tbody tr");
      rows.forEach(row => {
        const link = row.querySelector('a[href^="/?p=league&type="]');
        if (link) {
          const tid = link.href.match(/tid=(\d+)/);
          if (tid) {
            link.href = `/?p=team&tid=${tid[1]}`;
          }
        }
        const helpButton = row.querySelector(".help_button");
        if (helpButton) {
          helpButton.style.pointerEvents = "none";
          helpButton.style.opacity = "0.5";
          helpButton.style.cursor = "default";
          helpButton.onclick = (e) => {
            e.preventDefault();
            return false;
          };
          helpButton.href = "javascript:void(0);";
        }
      });
    }

    setUpHelpButton(table, sid, leagueType) {
      const secondRow = table.querySelector("tbody tr:nth-child(2)");
      if (!secondRow) return;
      const helpButton = secondRow.querySelector(".help_button");
      const teamLink = secondRow.querySelector("a[onclick*='purchaseChallenge']");

      if (helpButton && teamLink) {
        const tid = teamLink
          .getAttribute("onclick")
          .split(",")[2].replace(/[ ';)]/g, "");
        helpButton.removeAttribute("onclick");
        helpButton.onclick = () => this.handleExtraLeagueData(sid, leagueType, tid);
      }
    }

    async handleExtraLeagueData(sid, leagueType, tid) {
      const response = await fetch(
        `https://www.managerzone.com/ajax.php?p=extraLeague&sub=division_runner_ups&type=${leagueType}&sid=${sid}&tid=${tid}&sport=${this.leagueManager.sport}`
      );
      const html = await response.text();
      const modal = document.createElement("div");
      modal.id = "extra-league-data-modal";
      modal.className = "leagues-modal";

      const content = document.createElement("div");
      content.className = "leagues-modal-content";
      content.innerHTML = html;
      modal.appendChild(content);

      document.body.appendChild(modal);

      modal.onclick = (event) => {
        if (event.target === modal) modal.remove();
      };
    }

    createLeagueTitle(selectedLeague, divisionCount, leagueType, region) {
      const p = document.createElement("p");
      p.classList.add("league-table-title");
      if (!selectedLeague.startsWith("Division")) {
        p.textContent = selectedLeague;
      } else {
        const theDivision = selectedLeague + "." + divisionCount;
        p.textContent = theDivision.replace("Division", "div");
      }
      p.textContent += " " + leagueType.charAt(0).toUpperCase() + leagueType.slice(1);
      if (region && region !== "World") {
        p.textContent += " " + region;
      }
      if (p.textContent.includes("_")) {
        p.textContent = p.textContent.replace("_", " ");
      }
      return p;
    }
  }

  const infoAboutTeam = document.querySelector(UI.SELECTORS.TEAM_INFO);
  const teamStadiumWrapper = document.querySelector(UI.SELECTORS.STADIUM_WRAPPER);

  if (infoAboutTeam && teamStadiumWrapper) {
    const leagueManager = new LeagueManager();
    const uiManager = new UIManager(leagueManager);
    uiManager.initializeInterface();
  }
})();