Simple GreasyFork Script Filter

Block unwanted scripts on GreasyFork. Non-Latin filter, custom keywords, and per-script hiding. Compatible with most Greasyfork enhancement scripts.

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

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

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.

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

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         Simple GreasyFork Script Filter
// @name:zh-CN 简单 GreasyFork 脚本过滤器
// @name:zh-TW 簡單 GreasyFork 腳本過濾器
// @name:ja      シンプル GreasyFork スクリプトフィルター
// @description  Block unwanted scripts on GreasyFork. Non-Latin filter, custom keywords, and per-script hiding. Compatible with most Greasyfork enhancement scripts.
// @description:zh-CN 在 GreasyFork 上屏蔽不需要的脚本。非拉丁字符过滤、自定义关键词、按脚本隐藏。兼容大部分 Greasyfork 美化与增强脚本。
// @description:zh-TW 在 GreasyFork 上封鎖不需要的腳本。非拉丁字元過濾、自訂關鍵詞、按腳本隱藏。兼容大部分 Greasyfork 美化与增强脚本。
// @description:ja      GreasyFork で不要なスクリプトをブロック。非ラテン文字フィルター、カスタムキーワード、スクリプトごとの非表示。大部分 Greasyfork ページをサポート。
// @namespace    simple_greasyfork_script_filter
// @version      1.1.0
// @license      Apache-2.0
// @author       Ckrvxr
// @homepage     https://github.com/Ckrvxr/simple_greasyfork_script_filter_js
// @match        https://greatest.deepsurf.us/*
// @run-at       document-end
// @grant        none
// ==/UserScript==

(function () {
    'use strict';

    /* ---- i18n ---- */
    var LOCALES = {
        'en': {
            groupTitle: 'Script Filter:',
            blacklisted: 'Filter by Regex',
            hidden: 'Filter by Script ID',
            dateFiltered: 'Filter by Update Date',
            filterSettings: 'Script Filter Settings',
            nonLatin: 'Non-Latin Filter',
            nonLatinNote: 'Block scripts with non-Latin characters in title or description',
            dateFilter: 'Filter by Update Date',
            dateFilterNote: 'Hide scripts updated before the specified date',
            datePlaceholder: 'YYYY-MM-DD',
            regexMatch: 'Filter by Regex',
            regexMatchPlace: 're/^test/i\nLearning Tool\nOnline Course',
            regexMatchNote: 'Each line is a regex or keyword. Matching scripts will be filtered.',
            hideScripts: 'Filter by Script ID',
            hideAddPlace: 'Add Script ID',
            hideSearchPlace: 'Search script name or ID ...',
            cancel: 'Cancel',
            save: 'Save',
            hide: 'Filter',
            unhide: 'Unfilter'
        },
        'zh-Hans': {
            groupTitle: '脚本过滤器:',
            blacklisted: '按正则表达式过滤',
            hidden: '按脚本ID过滤',
            dateFiltered: '按更新日期过滤',
            filterSettings: '脚本过滤器设置',
            nonLatin: '过滤非拉丁字符脚本',
            nonLatinNote: '屏蔽标题或描述含非拉丁字符的脚本',
            dateFilter: '按更新日期过滤',
            dateFilterNote: '隐藏指定日期之前最后更新的脚本',
            datePlaceholder: '年-月-日',
            regexMatch: '按正则表达式匹配过滤',
            regexMatchPlace: 're/^test/i\n学习通\n网课',
            regexMatchNote: '每行一个正则或关键词。匹配中脚本名称或描述的会被过滤。',
            hideScripts: '按脚本ID过滤',
            hideAddPlace: '添加脚本ID',
            hideSearchPlace: '搜索脚本名称或 ID ...',
            cancel: '取消',
            save: '保存',
            hide: '过滤',
            unhide: '取消过滤'
        },
        'zh-Hant': {
            groupTitle: '腳本過濾器:',
            blacklisted: '按正則表達式過濾',
            hidden: '按腳本ID過濾',
            dateFiltered: '按更新日期過濾',
            filterSettings: '腳本過濾器設定',
            nonLatin: '過濾非拉丁字元腳本',
            nonLatinNote: '封鎖標題或描述含非拉丁字元的腳本',
            dateFilter: '按更新日期過濾',
            dateFilterNote: '隱藏指定日期之前最後更新的腳本',
            datePlaceholder: '年-月-日',
            regexMatch: '按正則表達式匹配過濾',
            regexMatchPlace: 're/^test/i\n學習通\n網課',
            regexMatchNote: '每行一個正則或關鍵詞。匹配腳本名稱或描述的將被封鎖。',
            hideScripts: '按腳本ID過濾',
            hideAddPlace: '添加腳本ID',
            hideSearchPlace: '搜索腳本名稱或 ID ...',
            cancel: '取消',
            save: '儲存',
            hide: '過濾',
            unhide: '取消過濾'
        },
        'ja': {
            groupTitle: 'スクリプトフィルター:',
            blacklisted: '正規表現でフィルター',
            hidden: 'スクリプトIDでフィルター',
            dateFiltered: '更新日でフィルター',
            filterSettings: 'スクリプトフィルター設定',
            nonLatin: '非ラテン文字をフィルター',
            nonLatinNote: 'タイトルまたは説明に非ラテン文字を含むスクリプトをブロック',
            dateFilter: '更新日でフィルター',
            dateFilterNote: '指定日以前に更新されたスクリプトを非表示',
            datePlaceholder: 'YYYY-MM-DD',
            regexMatch: '正規表現でフィルター',
            regexMatchPlace: 're/^test/i\n学習通\nネット授業',
            regexMatchNote: '各行に正規表現またはキーワード。一致するスクリプトはフィルターされます。',
            hideScripts: 'スクリプトIDでフィルター',
            hideAddPlace: 'スクリプトIDを追加',
            hideSearchPlace: 'スクリプト名またはIDを検索...',
            cancel: 'キャンセル',
            save: '保存',
            hide: 'フィルター',
            unhide: 'フィルター解除'
        }
    };
    var langAlias = { 
        'zh-CN': 'zh-Hans',
        'zh-MO': 'zh-Hans',
        'zh-SG': 'zh-Hans', 
        'zh-MY': 'zh-Hans',
        'zh-TW': 'zh-Hant', 
        'zh-HK': 'zh-Hant'
    };
    var lang = (document.documentElement.lang || 'en').replace(/_/g, '-');
    lang = langAlias[lang] || lang;
    if (!LOCALES[lang]) { var l2 = lang.substring(0, 2); lang = LOCALES[l2] ? l2 : 'en'; }
    function T(k) { return (LOCALES[lang] || LOCALES.en)[k] || k; }

    /* ---- filter definitions ---- */
    var FILTERS = {
        nonLatin: /[^\p{Script=Latin}\p{Script=Common}\p{Script=Inherited}]/gu,
    };

    /* ---- persistence ---- */
    var CFG = {
        nonLatins: false,
        customBlacklist: '',
        hiddenList: [],
        cutoffDate: ''
    };

    function hiddenIndex(id) {
        for (var i = 0; i < CFG.hiddenList.length; i++) {
            if (CFG.hiddenList[i].id === id) return i;
        }
        return -1;
    }

    function loadCfg() {
        try {
            CFG.nonLatins = JSON.parse(localStorage.getItem('gf-blocker-nonLatins') || 'false');
            CFG.customBlacklist = localStorage.getItem('gf-blocker-customBlacklist') || 're/VIP视频|全网VIP|视频解析|vip视频|会员视频解析|学习通|网课助手|[刷网]课|自动[答刷]题|超星|知到|智慧树|自动刷课/i';
            CFG.cutoffDate = localStorage.getItem('gf-blocker-cutoffDate') || '';
            var hl = JSON.parse(localStorage.getItem('gf-blocker-hiddenList') || '[]');
            if (Array.isArray(hl)) {
                CFG.hiddenList = hl.map(function (e) {
                    if (typeof e === 'number' && !isNaN(e)) return { id: e, name: '' };
                    if (e && typeof e.id === 'number' && !isNaN(e.id)) return { id: e.id, name: e.name || '' };
                    return null;
                }).filter(function (e) { return e; });
            } else {
                CFG.hiddenList = [];
            }
        } catch (e) { CFG.hiddenList = []; }
    }
    function saveCfg() {
        localStorage.setItem('gf-blocker-nonLatins', JSON.stringify(CFG.nonLatins));
        localStorage.setItem('gf-blocker-customBlacklist', CFG.customBlacklist);
        localStorage.setItem('gf-blocker-cutoffDate', CFG.cutoffDate);
        localStorage.setItem('gf-blocker-hiddenList', JSON.stringify(CFG.hiddenList));
    }
    loadCfg();

    /* ---- custom blacklist parser ---- */
    var customBlacklistFn = null;

    function buildCustomBlacklist(txt) {
        if (!txt) { customBlacklistFn = null; return; }
        var reArr = [];
        txt = txt.replace(/\uE084/g, '\uE084x');
        var c = 800;
        while (c--) {
            var i = txt.search(/\bre\//);
            if (i < 0) break;
            var s = txt.substring(i + 3);
            var j = -1;
            var g = /(.?)\//g, m;
            while (m = g.exec(s)) { if (m[1] !== '\\') { j = g.lastIndex + i + 3; break; } }
            if (j < 0) break;
            var o = (/^[a-z]+/.exec(txt.substring(j)) || [''])[0];
            var rc = txt.substring(i + 3, j - 1);
            txt = txt.substring(0, i) + '\uE084' + reArr.length + 'r' + txt.substring(j + o.length);
            reArr.push(new RegExp(rc, o));
        }
        var rules = txt.replace(/[,、]/g, ',').split(/,|\n/).map(function (e) { return e.trim(); }).filter(function (e) { return e; });
        customBlacklistFn = function (text) {
            var t0 = text.replace(/\uE084/g, '\uE084x');
            for (var k = 0; k < rules.length; k++) {
                var r = rules[k];
                var match = false;
                if (!r.includes('\uE084')) {
                    match = text.toLowerCase('en-US').includes(r.toLowerCase('en-US'));
                } else {
                    var parts = r.split(/\uE084(\d+)r/);
                    match = parts.every(function (p, idx) {
                        if (!p || !p.length) return true;
                        if (idx % 2) return reArr[+p].test(t0);
                        return t0.includes(p.trim());
                    });
                }
                if (match) return k;
            }
        };
    }
    buildCustomBlacklist(CFG.customBlacklist);

    /* ---- CSS ---- */
    var CSS = '\
        .script-list li.bl-blacklisted,\
        .script-list li.bl-hidden { display:none }\
        .script-list li.bl-blacklisted { background:rgba(180,40,40,0.06); border-left:3px solid rgba(180,40,40,0.35) }\
        .script-list li.bl-hidden { background:rgba(70,70,200,0.06); border-left:3px solid rgba(70,70,200,0.35) }\
        .script-list li.bl-dt { background:rgba(40,180,40,0.06); border-left:3px solid rgba(40,180,40,0.35) }\
        .bl-btn {\
            cursor:pointer; font-size:0.75rem; white-space:nowrap; display:inline-flex; align-items:center;\
            padding:0.2em 0.65em; border-radius:4px; border:1px solid #ccc;\
            background:linear-gradient(180deg,#f0f0ff,#e0e0f0); color:#222;\
            margin-left:0.6em; user-select:none\
        }\
        .bl-btn:hover { border-color:#66f; background:linear-gradient(180deg,#e0e0ff,#d0d0f0) }\
        html { --bl-bl-display:none; --bl-hl-display:none; --bl-dt-display:none }\
        [bl-bl-shown] { --bl-bl-display:list-item }\
        [bl-hl-shown] { --bl-hl-display:list-item }\
        [bl-dt-shown] { --bl-dt-display:list-item }\
        .script-list li.bl-blacklisted { display:var(--bl-bl-display,none) !important }\
        .script-list li.bl-hidden { display:var(--bl-hl-display,none) !important }\
        .script-list li.bl-dt { display:var(--bl-dt-display,none) !important }\
        #bl-bl-opt, #bl-hl-opt, #bl-dt-opt { border:none!important; outline:none!important; box-shadow:none!important; background:none!important; color:inherit!important; opacity:0.5 }\
        [bl-bl-shown] #bl-bl-opt { opacity:1 }\
        [bl-hl-shown] #bl-hl-opt { opacity:1 }\
        [bl-dt-shown] #bl-dt-opt { opacity:1 }\
    ';

    function injectCSS() {
        var s = document.createElement('style');
        s.textContent = CSS;
        (document.head || document.documentElement).appendChild(s);
    }

    /* ---- filtering ---- */
    function hideBL(element, list) {
        if (!element) return;
        var name = (element.querySelector('.script-link') || {}).textContent || '';
        var desc = (element.querySelector('.script-description') || {}).textContent || '';
        if (!name) return;
        switch (list) {
            case 'nl':
                if (FILTERS.nonLatin.test(name) || FILTERS.nonLatin.test(desc))
                    element.classList.add('bl-blacklisted', 'bl-nl');
                break;
            case 'cbl':
                if (customBlacklistFn && (customBlacklistFn(name) >= 0 || customBlacklistFn(desc) >= 0))
                    element.classList.add('bl-blacklisted', 'bl-cbl');
                break;
            case 'dt':
                var updated = element.getAttribute('data-script-updated-date');
                if (updated && CFG.cutoffDate && updated < CFG.cutoffDate)
                    element.classList.add('bl-dt');
                break;
        }
    }
    function hideOne(element, id) {
        id = +id;
        if (!(id >= 0)) return;
        var idx = hiddenIndex(id);
        var hidden = idx !== -1;
        if (hidden) element.classList.add('bl-hidden');
        var name = (element.querySelector('.script-link') || {}).textContent || '';
        var btn = document.createElement('span');
        btn.className = 'bl-btn';
        btn.textContent = hidden ? T('unhide') : T('hide');
        btn.addEventListener('click', function (e) {
            e.stopPropagation();
            e.stopImmediatePropagation();
            var i = hiddenIndex(id);
            if (i === -1) { CFG.hiddenList.push({ id: id, name: name }); element.classList.add('bl-hidden'); btn.textContent = T('unhide'); }
            else { CFG.hiddenList.splice(i, 1); element.classList.remove('bl-hidden'); btn.textContent = T('hide'); }
            saveCfg();
        });
        var badge = element.querySelector('.badge-js, .badge-css');
        if (badge) badge.parentNode.insertBefore(btn, badge.nextSibling);
        else { var h2 = element.querySelector('h2'); if (h2) h2.appendChild(btn); }
    }
    function processList(list) {
        if (!list || !list.isConnected) return;
        var items = list.querySelectorAll('li[data-script-id]:not([bl-done])');
        for (var i = 0; i < items.length; i++) {
            var el = items[i];
            el.setAttribute('bl-done', '1');
            var sid = +el.getAttribute('data-script-id');
            if (!(sid > 0)) continue;
            if (CFG.nonLatins) hideBL(el, 'nl');
            if (customBlacklistFn) hideBL(el, 'cbl');
            if (CFG.cutoffDate) hideBL(el, 'dt');
            hideOne(el, sid);
        }
        updateSidebar();
    }
    function processDiscussions(list) {
        if (!list || !list.isConnected) return;
        var items = list.querySelectorAll('.discussion-list-item:not([bl-done])');
        for (var i = 0; i < items.length; i++) {
            var item = items[i];
            item.setAttribute('bl-done', '1');
            var link = item.querySelector('a.script-link');
            if (!link) continue;
            var m = /\/scripts\/(\d+)/.exec(link.getAttribute('href') || '');
            var id = m ? +m[1] : 0;
            if (id > 0 && hiddenIndex(id) !== -1) {
                (item.closest('.discussion-list-container') || item).classList.add('bl-hidden');
            }
        }
    }

    /* ---- sidebar ---- */
    function addSidebar() {
        var g = document.querySelector('.list-option-groups');
        if (!g || document.getElementById('bl-group')) return;
        var d = document.createElement('div');
        d.className = 'list-option-group';
        d.id = 'bl-group';
        d.innerHTML = T('groupTitle') + '<ul>\
<li class="list-option"><a href="#" id="bl-bl-opt">' + T('blacklisted') + ' (0)</a></li>\
<li class="list-option"><a href="#" id="bl-dt-opt">' + T('dateFiltered') + ' (0)</a></li>\
<li class="list-option"><a href="#" id="bl-hl-opt">' + T('hidden') + ' (0)</a></li>\
<li class="list-option"><a href="#" id="bl-set">' + T('filterSettings') + '</a></li></ul>';
        var f = g.querySelector('div');
        (f || g).parentNode.insertBefore(d, f || null);
        var toggle = function (t) {
            var a = t === 'bl' ? 'bl-bl-shown' : t === 'hl' ? 'bl-hl-shown' : 'bl-dt-shown';
            if (document.documentElement.hasAttribute(a)) document.documentElement.removeAttribute(a);
            else document.documentElement.setAttribute(a, '');
        };
        (document.getElementById('bl-bl-opt') || {}).addEventListener('click', function (e) { e.preventDefault(); toggle('bl'); });
        (document.getElementById('bl-hl-opt') || {}).addEventListener('click', function (e) { e.preventDefault(); toggle('hl'); });
        (document.getElementById('bl-dt-opt') || {}).addEventListener('click', function (e) { e.preventDefault(); toggle('dt'); });
        (document.getElementById('bl-set') || {}).addEventListener('click', function (e) { e.preventDefault(); openSettings(); });
    }
    function updateSidebar() {
        var bl = document.getElementById('bl-bl-opt');
        var hl = document.getElementById('bl-hl-opt');
        var dt = document.getElementById('bl-dt-opt');
        if (bl) bl.textContent = T('blacklisted') + ' (' + document.querySelectorAll('.script-list li.bl-blacklisted').length + ')';
        if (hl) hl.textContent = T('hidden') + ' (' + document.querySelectorAll('.script-list li.bl-hidden').length + ')';
        if (dt) dt.textContent = T('dateFiltered') + ' (' + document.querySelectorAll('.script-list li.bl-dt').length + ')';
    }

    /* ---- settings modal ---- */
    function openSettings() {
        if (document.getElementById('bl-modal')) return;
        var c = document.createElement('div');
        c.id = 'bl-modal';
        c.innerHTML = '\
<div style="position:fixed;inset:0;background:rgba(0,0,0,0.5);z-index:9999;display:flex;align-items:center;justify-content:center">\
<div style="background:#fff;max-width:460px;width:90%;border-radius:8px;padding:24px;box-shadow:0 20px 40px rgba(0,0,0,0.3);max-height:90vh;overflow-y:auto;color:#222;font-family:sans-serif;font-size:14px;line-height:1.5">\
<h2 style="margin:0 0 16px;font-size:18px">' + T('filterSettings') + '</h2>\
<div style="margin-bottom:12px"><label style="display:flex;align-items:center;gap:6px"><input type="checkbox" id="bl-f-nl"' + (CFG.nonLatins ? ' checked' : '') + '> ' + T('nonLatin') + '</label><span style="font-size:12px;color:#888">' + T('nonLatinNote') + '</span></div>\
<div style="margin-bottom:12px"><label style="display:block;margin-bottom:4px;font-weight:600">' + T('dateFilter') + '</label><input type="date" id="bl-f-dt" value="' + (CFG.cutoffDate || '') + '" style="width:100%;box-sizing:border-box;padding:10px 12px;border:1px solid #ccc;border-radius:4px;font:inherit;font-size:14px"><span style="font-size:12px;color:#888">' + T('dateFilterNote') + '</span></div>\
<div style="margin-bottom:12px"><label style="display:block;margin-bottom:4px;font-weight:600">' + T('regexMatch') + '</label><textarea id="bl-f-cbl" rows="3" placeholder="' + T('regexMatchPlace') + '" style="width:100%;box-sizing:border-box;padding:10px 12px;border:1px solid #ccc;border-radius:4px;font:inherit;font-size:14px;font-family:monospace">' + (CFG.customBlacklist || '') + '</textarea><span style="font-size:12px;color:#888">' + T('regexMatchNote') + '</span></div>\
<div style="margin-bottom:12px"><label style="display:block;margin-bottom:4px;font-weight:600">' + T('hideScripts') + '</label>\
<div style="display:flex;gap:4px;margin-bottom:6px">\
<input type="text" id="bl-hl-add" placeholder="' + T('hideAddPlace') + '" style="flex:1;padding:6px 8px;border:1px solid #ccc;border-radius:4px;font-family:monospace;font-size:13px">\
<button id="bl-hl-add-btn" style="padding:6px 12px;border:1px solid #ccc;background:#f5f5f5;border-radius:4px;cursor:pointer;font-size:13px">+</button></div>\
<input type="text" id="bl-hl-search" placeholder="' + T('hideSearchPlace') + '" style="width:100%;box-sizing:border-box;padding:6px 8px;border:1px solid #ccc;border-radius:4px;font-size:13px;margin-bottom:6px">\
<div id="bl-hl-items" style="max-height:300px;overflow-y:auto;border:1px solid #eee;border-radius:4px;font-family:monospace;font-size:13px"></div></div>\
<div style="text-align:right;margin-top:16px">\
<button id="bl-cancel" style="padding:8px 16px;border:1px solid #ccc;background:#f5f5f5;border-radius:4px;cursor:pointer;margin-right:8px">' + T('cancel') + '</button>\
<button id="bl-save" style="padding:8px 16px;border:none;background:#4f46e5;color:#fff;border-radius:4px;cursor:pointer;font-weight:600">' + T('save') + '</button></div></div></div>';
        document.body.appendChild(c);

        var _hl = CFG.hiddenList.map(function (e) { return { id: e.id, name: e.name }; });
        var _search = '';

        function renderHL() {
            var container = document.getElementById('bl-hl-items');
            if (!container) return;
            var q = _search.toLowerCase();
            var filtered = _hl.filter(function (e) {
                return !q || ('' + e.id).indexOf(q) !== -1 || (e.name || '').toLowerCase().indexOf(q) !== -1;
            });
            container.innerHTML = filtered.map(function (e) {
                var text = e.id + (e.name ? ' ' + e.name : '');
                return '<div style="display:flex;align-items:center;padding:4px 8px;border-bottom:1px solid #f0f0f0">\
<span style="flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">' + text + '</span>\
<span class="bl-hl-del" data-id="' + e.id + '" style="cursor:pointer;color:#999;padding:0 4px;font-family:sans-serif">✕</span></div>';
            }).join('');
            Array.prototype.forEach.call(container.querySelectorAll('.bl-hl-del'), function (el) {
                el.addEventListener('click', function () {
                    var id = +this.getAttribute('data-id');
                    for (var k = 0; k < _hl.length; k++) {
                        if (_hl[k].id === id) { _hl.splice(k, 1); break; }
                    }
                    renderHL();
                });
            });
        }

        renderHL();

        document.getElementById('bl-hl-search').addEventListener('input', function () {
            _search = this.value;
            renderHL();
        });

        document.getElementById('bl-hl-add-btn').addEventListener('click', function () {
            var input = document.getElementById('bl-hl-add');
            var val = input.value.trim();
            if (!val) return;
            var n = parseInt(val);
            if (!isNaN(n)) {
                if (_hl.some(function (e) { return e.id === n; })) { input.value = ''; return; }
                _hl.push({ id: n, name: '' });
                input.value = '';
                renderHL();
            }
        });
        document.getElementById('bl-hl-add').addEventListener('keydown', function (e) {
            if (e.key === 'Enter') document.getElementById('bl-hl-add-btn').click();
        });

        var close = function () { c.remove(); };
        c.querySelector('div').addEventListener('click', function (e) { if (e.target === e.currentTarget) close(); });
        document.getElementById('bl-cancel').addEventListener('click', close);

        document.getElementById('bl-save').addEventListener('click', function () {
            CFG.nonLatins = document.getElementById('bl-f-nl').checked;
            CFG.customBlacklist = document.getElementById('bl-f-cbl').value || '';
            CFG.cutoffDate = document.getElementById('bl-f-dt').value || '';
            CFG.hiddenList = _hl;
            saveCfg();
            buildCustomBlacklist(CFG.customBlacklist);
            close();
            window.location.reload();
        });
    }

    /* ---- init ---- */
    function init() {
        injectCSS();
        var sl = document.querySelector('.script-list');
        if (sl) {
            addSidebar();
            processList(sl);
            new MutationObserver(function () { processList(sl); }).observe(sl, { childList: true, subtree: true });
        }
        var dl = document.querySelector('.discussion-list');
        if (dl) {
            processDiscussions(dl);
            new MutationObserver(function () { processDiscussions(dl); }).observe(dl, { childList: true, subtree: true });
        }
    }
    if (document.readyState === 'loading') window.addEventListener('DOMContentLoaded', init, { once: true });
    else init();
})();