atcoder-standings-lang

AtCoder の順位表に最多提出言語を追加します.uesugi6111 さん作のスクリプトが元ネタです.

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         atcoder-standings-lang
// @namespace    iilj
// @version      2020.11.12.0
// @description  AtCoder の順位表に最多提出言語を追加します.uesugi6111 さん作のスクリプトが元ネタです.
// @author       iilj
// @supportURL   https://github.com/iilj/atcoder-standings-lang/issues
// @match        https://atcoder.jp/contests/*/standings*
// ==/UserScript==

/* globals $ */

/**
 * ユーザID/言語ごとの提出数
 * @typedef {Object} UserLangEntry
 * @property {string} user_id ユーザ ID
 * @property {string} language 言語名
 * @property {number} count 提出数
 */

(() => {
    'use strict';

    /** @type {Map<string, UserLangEntry[]>} */
    const userLangEntryMap = new Map();

    const fetchLangJson = async () => {
        console.log('@kenkooooさんありがとう');
        console.log('うえすぎさんありがとう');
        // 2e5 個くらい要素があるのでキャッシュする
        const res = await fetch("https://kenkoooo.com/atcoder/resources/lang.json", { cache: 'force-cache' });
        /** @type {UserLangEntry[]} */
        const userLangEntries = await res.json();

        // prepare map
        userLangEntries.forEach(userLangEntry => {
            if (userLangEntryMap.has(userLangEntry.user_id)) {
                userLangEntryMap.get(userLangEntry.user_id).push(userLangEntry);
            } else {
                userLangEntryMap.set(userLangEntry.user_id, [userLangEntry]);
            }
        });

        // sort arrays
        userLangEntryMap.forEach(userLangArray => {
            userLangArray.sort((a, b) => b.count - a.count); // in place
        });
    };

    /** @type {(anchor: HTMLAnchorElement) => void} */
    const updateAnchor = (anchor) => {
        if (!anchor.href.includes('/users/')) return;

        const user_id = anchor.text.trim();
        if (!userLangEntryMap.has(user_id)) return;

        const userLangArray = userLangEntryMap.get(user_id);

        const tooltipHtml = userLangArray.map((userLangEntry) =>
            `${userLangEntry.language} : ${userLangEntry.count}`
        ).join('<br>');

        let langHtml = userLangArray[0].language;
        if (userLangArray.length >= 2) {
            langHtml += '<div style="font-size:10px;display:inline;">'
                + `/${userLangArray[1].language}`
                + (userLangArray.length >= 3 ? `/${userLangArray[2].language}` : '')
                + ' </div>';
        }

        anchor.insertAdjacentHTML('beforeend',
            '/'
            + '<div data-toggle="tooltip" data-html="true" data-placement="right" style="font-size:12px;display:inline;" title="'
            + tooltipHtml + '">'
            + langHtml
            + '</div>');
    };

    /** @type {(tbody: HTMLTableSectionElement) => void} */
    const updateTable = (tbody) => {
        tbody.querySelectorAll('.username').forEach(anchor => {
            updateAnchor(anchor);
        });
        $('[data-toggle="tooltip"]').tooltip();
    };

    /** @type {HTMLTableSectionElement} */
    let tbody = null;

    const tableObserver = new MutationObserver(() => {
        updateTable(tbody);
    });
    const parentObserver = new MutationObserver(async () => {
        tbody = document.getElementById('standings-tbody');
        if (tbody) {
            parentObserver.disconnect();
            await fetchLangJson();
            updateTable(tbody);
            tableObserver.observe(
                tbody,
                { childList: true }
            )
        }
    });
    parentObserver.observe(
        document.getElementById('vue-standings'),
        { childList: true, subtree: true }
    );
})();