TMVN Cup HOF

Trophymanager: hall of fame of tournament

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         TMVN Cup HOF
// @namespace    https://trophymanager.com
// @version      5
// @description  Trophymanager: hall of fame of tournament
// @match        https://trophymanager.com/history/cup/*
// @grant        none
// ==/UserScript==

(function () {
    'use strict';

    /* --------------------------- CONFIG --------------------------- */
    const CONFIG = {
        DEFAULT_SEASON_COUNT: 0,
        SEASON_COUNT_LOCAL_STORAGE_KEY: "TMVN_CUP_HOF_SEASON_COUNT"
    };

    const CONTROL_ID = {
        INPUT_SEASON_COUNT: 'tmvn_cup_hof_input_season_count',
        BUTTON_SEASON_COUNT: 'tmvn_cup_hof_button_season_count_set'
    };

    /* --------------------------- STORE --------------------------- */
    const championsBySeason = new Map(); // season -> {id, nth}
    const championIdOfSeason = new Map(); // season -> clubId (raw, before nth calc)
    const titleCounter = new Map(); // clubId -> running nth counter
    const clubNameById = new Map(); // clubId -> latest name

    const cupChampTotal = new Map(); // clubId -> #champion
    const cupRunnerTotal = new Map(); // clubId -> #runner‑up
    const cupThirdTotal = new Map(); // clubId -> #third place
    const lastSeasonAppear = new Map(); // clubId -> most recent season they reached final/semi

    /* --------------------------- HELPERS ------------------------- */
    const inc = (map, key) => map.set(key, (map.get(key) || 0) + 1);

    function ensureRightColumn() {
        let column = document.querySelector('div.column3_a');
        if (!column) {
            const old = document.querySelector('div.column3');
            if (old)
                old.parentNode.removeChild(old);
            column = document.createElement('div');
            column.className = 'column3_a';
            const centers = document.querySelectorAll('div.main_center');
            const target = centers[centers.length === 4 ? 3 : 2];
            target.appendChild(column);
        }
        return column;
    }

    function renderConfigPanel() {
        const col = ensureRightColumn();

        // Load season count from localStorage
        let savedSeasonCount = localStorage.getItem(CONFIG.SEASON_COUNT_LOCAL_STORAGE_KEY);
        if (savedSeasonCount == null || savedSeasonCount == "") {
            savedSeasonCount = CONFIG.DEFAULT_SEASON_COUNT;
        }

        const configHTML = `
            <div class='box' id='tmvn_cup_hof_config'>
                <div class='box_head'>
                    <h2 class='std'>CONFIG</h2>
                </div>
                <div class='box_body'>
                    <div class='box_shadow'></div>
                    <table>
                        <tr>
                            <td>
                                <span style="display: inline-block;">
                                    <input id="${CONTROL_ID.INPUT_SEASON_COUNT}"
                                           type="text"
                                           class="embossed"
                                           style="width: 150px; line-height: 100%; padding: 3px 3px 4px 3px;"
                                           placeholder="Season count"
                                           value="${savedSeasonCount}">
                                </span>
                            </td>
                            <td>
                                <span id="${CONTROL_ID.BUTTON_SEASON_COUNT}" class="button" style="margin-left: 3px;">
                                    <span class="button_border">Season count</span>
                                </span>
                            </td>
                        </tr>
                    </table>
                </div>
                <div class='box_footer'><div></div></div>
            </div>
        `;

        col.insertAdjacentHTML('beforeend', configHTML);

        // Add event listener for button
        document.getElementById(CONTROL_ID.BUTTON_SEASON_COUNT).addEventListener('click', setSeasonCount);
    }

    function setSeasonCount() {
        let seasonCount = document.getElementById(CONTROL_ID.INPUT_SEASON_COUNT).value;

        if (seasonCount == '') {
            localStorage.removeItem(CONFIG.SEASON_COUNT_LOCAL_STORAGE_KEY);
            alert('Season count cleared. Please refresh to show all seasons.');
        } else if (isNaN(seasonCount) || seasonCount < 0) {
            alert('Season count must be a non-negative integer. 0 means all seasons.');
        } else {
            localStorage.setItem(CONFIG.SEASON_COUNT_LOCAL_STORAGE_KEY, seasonCount);
            alert('Set successful, please refresh');
        }
    }

    function renderTables() {
        /* Champions by season table (desc) */
        const seasonsDesc = Array.from(championsBySeason.keys()).sort((a, b) => b - a);
        let championsHTML = "<table><tr><th>Season</th><th>Club</th><th align='right'>Nth</th></tr>";
        seasonsDesc.forEach((season, idx) => {
            const {
                id,
                nth
            } = championsBySeason.get(season);
            const name = clubNameById.get(id);
            const odd = (idx & 1) ? " class='odd'" : "";
            championsHTML += `<tr${odd}><td>${season}</td><td><span onclick=\"window.open('https://trophymanager.com/club/${id}')\">${name}</span></td><td align='right'>${nth}</td></tr>`;
        });
        championsHTML += "</table>";

        /* Hall of Fame */
        const allIds = new Set([...cupChampTotal.keys(), ...cupRunnerTotal.keys(), ...cupThirdTotal.keys()]);
        const hofList = Array.from(allIds).sort((a, b) => {
            const scoreB = (cupChampTotal.get(b) || 0) * 10000 + (cupRunnerTotal.get(b) || 0) * 100 + (cupThirdTotal.get(b) || 0);
            const scoreA = (cupChampTotal.get(a) || 0) * 10000 + (cupRunnerTotal.get(a) || 0) * 100 + (cupThirdTotal.get(a) || 0);
            if (scoreB !== scoreA)
                return scoreB - scoreA;
            // tie‑break by most recent appearance (descending season)
            return (lastSeasonAppear.get(b) || 0) - (lastSeasonAppear.get(a) || 0);
        });
        let hofHTML = "<table><tr><th>#</th><th>Club</th><th align='right'>#1</th><th align='right'>#2</th><th align='right'>#3</th></tr>";
        hofList.forEach((id, index) => {
            const name = clubNameById.get(id);
            const c = cupChampTotal.get(id) || '';
            const r = cupRunnerTotal.get(id) || '';
            const t = cupThirdTotal.get(id) || '';
            const odd = (index & 1) ? " class='odd'" : "";
            hofHTML += `<tr${odd}><td>${index + 1}</td><td><span onclick=\"window.open('https://trophymanager.com/club/${id}')\">${name}</span></td><td align='right'>${c}</td><td align='right'>${r}</td><td align='right'>${t}</td></tr>`;
        });
        hofHTML += "</table>";

        /* inject */
        const col = ensureRightColumn();
        if (!document.getElementById('tmvn_cup_champions')) {
            col.insertAdjacentHTML('beforeend', `<div class='box' id='tmvn_cup_champions'><div class='box_head'><h2 class='std'>CHAMPION LIST</h2></div><div class='box_body'><div class='box_shadow'></div>${championsHTML}</div><div class='box_footer'><div></div></div></div>`);
        } else {
            document.querySelector('#tmvn_cup_champions .box_body').innerHTML = `<div class='box_shadow'></div>${championsHTML}`;
        }
        if (!document.getElementById('tmvn_cup_hof')) {
            col.insertAdjacentHTML('beforeend', `<div class='box' id='tmvn_cup_hof'><div class='box_head'><h2 class='std'>HALL OF FAME</h2></div><div class='box_body'><div class='box_shadow'></div>${hofHTML}</div><div class='box_footer'><div></div></div></div>`);
        } else {
            document.querySelector('#tmvn_cup_hof .box_body').innerHTML = `<div class='box_shadow'></div>${hofHTML}`;
        }

        // Render CONFIG panel at the end
        renderConfigPanel();
    }

    /* --------------------------- SCAN --------------------------- */
    const currentSeason = parseInt(document.querySelector('#top_menu a.none.white.small').innerText.split(/(\s+)/)[2], 10);
    const country = location.href.split('/')[5];

    // Load season count from localStorage
    let seasonCount = localStorage.getItem(CONFIG.SEASON_COUNT_LOCAL_STORAGE_KEY);
    if (seasonCount == null || seasonCount == "") {
        seasonCount = CONFIG.DEFAULT_SEASON_COUNT;
    } else {
        seasonCount = parseInt(seasonCount, 10);
    }

    // Calculate start season based on seasonCount
    let startSeason = 1;
    if (seasonCount > 0 && seasonCount < currentSeason) {
        startSeason = currentSeason - seasonCount;
    }

    let scanned = 0;
    const totalScans = currentSeason - startSeason;

    for (let season = startSeason; season < currentSeason; season++) {
        $.ajax(`https://trophymanager.com/history/cup/${country}/${season}`, {
            type: 'GET',
            dataType: 'html',
            crossDomain: true,
            success: response => {
                const matches = $('.match_list.border_bottom li', response);

                // Trận chung kết
                const final = matches[0];
                if (final) {
                    const a = final.querySelectorAll('a');
                    const id1 = a[0].getAttribute('club_link');
                    const id2 = a[2].getAttribute('club_link');
                    const name1 = a[0].innerText;
                    const name2 = a[2].innerText;
                    clubNameById.set(id1, name1);
                    clubNameById.set(id2, name2);
                    const [s1, s2] = a[1].innerText.split('-').map(n => parseInt(n, 10));
                    const champId = s1 > s2 ? id1 : id2;
                    const runId = s1 > s2 ? id2 : id1;

                    championIdOfSeason.set(season, champId);

                    inc(cupChampTotal, champId);
                    inc(cupRunnerTotal, runId);

                    lastSeasonAppear.set(champId, season);
                    lastSeasonAppear.set(runId, season);
                }

                // 2 trận bán kết
                const semi1 = matches[1];
                const semi2 = matches[2];

                // Xử lý bán kết 1
                if (semi1) {
                    const a = semi1.querySelectorAll('a');
                    const id1 = a[0].getAttribute('club_link');
                    const id2 = a[2].getAttribute('club_link');
                    const name1 = a[0].innerText;
                    const name2 = a[2].innerText;
                    clubNameById.set(id1, name1);
                    clubNameById.set(id2, name2);
                    const [s1, s2] = a[1].innerText.split('-').map(n => parseInt(n, 10));
                    const loserId = s1 > s2 ? id2 : id1;

                    inc(cupThirdTotal, loserId);
                    lastSeasonAppear.set(loserId, season);
                }

                // Xử lý bán kết 2
                if (semi2) {
                    const a = semi2.querySelectorAll('a');
                    const id1 = a[0].getAttribute('club_link');
                    const id2 = a[2].getAttribute('club_link');
                    const name1 = a[0].innerText;
                    const name2 = a[2].innerText;
                    clubNameById.set(id1, name1);
                    clubNameById.set(id2, name2);
                    const [s1, s2] = a[1].innerText.split('-').map(n => parseInt(n, 10));
                    const loserId = s1 > s2 ? id2 : id1;

                    inc(cupThirdTotal, loserId);
                    lastSeasonAppear.set(loserId, season);
                }

                if (++scanned === totalScans)
                    finalize();
            },
            error: () => {
                if (++scanned === totalScans)
                    finalize();
            }
        });
    }

    function finalize() {
        /* compute nth titles in chronological order to avoid async issues */
        const seasonsAsc = Array.from(championIdOfSeason.keys()).sort((a, b) => a - b);
        seasonsAsc.forEach(season => {
            const champId = championIdOfSeason.get(season);
            if (!champId)
                return;
            inc(titleCounter, champId);
            const nth = titleCounter.get(champId);
            championsBySeason.set(season, {
                id: champId,
                nth
            });
        });
        renderTables();
    }
})();