atcoder-standings-lang

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

Устаревшая версия за 12.11.2020. Перейдите к последней версии.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey, Greasemonkey или Violentmonkey.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey или Violentmonkey.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey или Violentmonkey.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey или Userscripts.

Чтобы установить этот скрипт, сначала вы должны установить расширение браузера, например 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 }
    );
})();