CLIST Contest Filter

You can choose and filter any favorite contests at homepage on CLIST

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

Bạn sẽ cần cài đặt một tiện ích mở rộng như Tampermonkey hoặc Violentmonkey để cài đặt kịch bản này.

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

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

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

You will need to install a user script manager extension to install this script.

(Tôi đã có Trình quản lý tập lệnh người dùng, hãy cài đặt nó!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name            CLIST Contest Filter
// @name:ja         CLIST Contest Filter
// @namespace       https://github.com/Coki628/clist-contest-filter
// @version         1.0.5
// @description     You can choose and filter any favorite contests at homepage on CLIST
// @description:ja  CLISTのTOPページで自分の好きなコンテストサイトだけ表示できます。
// @author          Coki628
// @license         MIT
// @match           https://clist.by/
// @grant           GM_getValue
// @grant           GM_setValue
// ==/UserScript==

(function() {
    'use strict';

    // 設定保存用オブジェクト
    const config = {
        favorites: new Set(),

        load() {
            const config_json = GM_getValue("ClistContestFilterConfig", null);
            if (config_json !== null) {
                try {
                    const data = JSON.parse(config_json);
                    this.favorites = new Set(data.favorites);
                } catch (e) {
                    console.error("ClistContestFilter: Invalid JSON", config_json);
                }
            }
        },

        save() {
            const data = { favorites: [...this.favorites], };
            const config_json = JSON.stringify(data);
            GM_setValue("ClistContestFilterConfig", config_json);
            // console.info("ClistContestFilter: saved");
        },

        has(site) {
            return this.favorites.has(site);
        },

        set(site, checked) {
            if (checked) {
                this.favorites.add(site);
            } else {
                this.favorites.delete(site);
            }
        },
    };
    config.load();

    // 存在するコンテストサイトの情報を取得
    let resources = new Set();
    $('.contest.row').each(function() {
        resources.add($(this).find('a.resource_search').text().trim());
    });
    // 領域とサイト毎のチェックボックス生成
    $('#contests').prepend(`
        <div id="custom-filter-box" style="padding: 10px 10px 5px;">
            <div></div>
            <div></div>
        </div>
    `);
    // フィルターボタン
    let checked = config.has('site-filter-on') ? ' checked' : '';
    $('#custom-filter-box>div:first').append(`
        <input id="site-filter-button" type="checkbox" value="site-filter-on"${checked}>
    `);
    $('#site-filter-button').bootstrapToggle({
        on: 'filtered',
        off: 'all',
        size: 'mini',
        onstyle: 'default active',
        offstyle: 'default active',
    });
    // サブコンテスト表示チェック
    checked = config.has('show-subcontest') ? ' checked' : '';
    $('#custom-filter-box>div:first').append(`
        <div style="margin-top: 5px;">
            <input type="checkbox" id="checkbox-show-subcontest" value="show-subcontest"${checked}>
            <label for="checkbox-show-subcontest" style="margin-right: 10px;">Show Subcontests</label>
        </div>
    `);
    // 各コンテストサイトのチェック
    for (const resource of resources.keys()) {
        checked = config.has(resource) ? ' checked' : '';
        $('#custom-filter-box>div:nth-child(2)').append(`
            <span style="white-space: nowrap;">
                <input type="checkbox" id="checkbox-${resource}" value="${resource}"${checked}>
                <label for="checkbox-${resource}" style="margin-right: 10px;">${resource}</label>
            </span>
        `);
    }

    // チェックボックスの変化に合わせて設定にも反映
    $('#site-filter-button,input[id^="checkbox-"]').on('change', function() {
        config.set($(this).val(), $(this).prop('checked'));
        config.save();
    });

    // 各行に今の設定を適用
    const updateRows = () => {
        // filtered
        if ($('#site-filter-button').prop('checked')) {
            $('.contest.row').each(function() {
                const site = $(this).find('a.resource_search').text().trim();
                // 未選択 or サブコンテスト(かつサブコンテスト未チェック)だけ非表示
                if (!config.has(site) || ($(this).hasClass('subcontest') && !$('#checkbox-show-subcontest').prop('checked'))) {
                    $(this).css('display', 'none');
                } else {
                    $(this).css('display', 'block');
                }
            });
        // all
        } else {

            $('.contest.row').each(function() {
                // サブコンテスト(かつサブコンテスト未チェック)だけ非表示
                if ($(this).hasClass('subcontest') && !$('#checkbox-show-subcontest').prop('checked')) {
                    $(this).css('display', 'none');
                } else {
                    $(this).css('display', 'block');
                }
            });
        }
    };
    updateRows();

    $('#site-filter-button,input[id^="checkbox-"]').on('change', function() {
        updateRows();
    });
})();