dfde-user-filter

Blendet Themen und Beiträge von bestimmten Nutzern auf debianforum.de aus

คุณจะต้องติดตั้งส่วนขยาย เช่น Tampermonkey, Greasemonkey หรือ Violentmonkey เพื่อติดตั้งสคริปต์นี้

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

คุณจะต้องติดตั้งส่วนขยาย เช่น Tampermonkey หรือ Violentmonkey เพื่อติดตั้งสคริปต์นี้

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         dfde-user-filter
// @namespace    http://tampermonkey.net/
// @version      1.17
// @description  Blendet Themen und Beiträge von bestimmten Nutzern auf debianforum.de aus
// @author       megabert
// @match        https://debianforum.de/forum/*
// @license      MIT
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_registerMenuCommand
// ==/UserScript==

const DEFAULTS = {
    geblockte_nutzer:      '',
    filter_nach_ersteller: true,
    filter_beitraege:      true,
    geblockte_titel:       '',
    filter_nach_titel:     true,
    geblockte_beitraege:   '',
    filter_nach_beitragsid: true,
};

let cfg = {
    geblockte_nutzer:      GM_getValue('geblockte_nutzer',       DEFAULTS.geblockte_nutzer),
    filter_nach_ersteller: GM_getValue('filter_nach_ersteller',  DEFAULTS.filter_nach_ersteller),
    filter_beitraege:      GM_getValue('filter_beitraege',       DEFAULTS.filter_beitraege),
    geblockte_titel:       GM_getValue('geblockte_titel',        DEFAULTS.geblockte_titel),
    filter_nach_titel:     GM_getValue('filter_nach_titel',      DEFAULTS.filter_nach_titel),
    geblockte_beitraege:   GM_getValue('geblockte_beitraege',    DEFAULTS.geblockte_beitraege),
    filter_nach_beitragsid: GM_getValue('filter_nach_beitragsid', DEFAULTS.filter_nach_beitragsid),
};

GM_registerMenuCommand('Einstellungen', zeigeEinstellungen);
einmal_filter();
setInterval(filtere_inhalt, 2000);

function getGeblockteNutzerListe() {
    return cfg.geblockte_nutzer
        .split('|')
        .map(s => s.split('::')[0].trim().toLowerCase())
        .filter(s => s.length > 0);
}

function filtere_inhalt() {
    filtere_themen_nach_nutzer();
    filtere_themen_nach_titel();
    filtere_beitraege_nach_nutzer();
    filtere_beitraege_nach_id();
    aktualisiereZeilenfarben();
}

function aktualisiereZeilenfarben() {
    let i = 0;
    document.querySelectorAll('ul.topiclist.topics li.row').forEach(row => {
        if (row.style.display === 'none') return;
        row.classList.toggle('bg1', i % 2 === 0);
        row.classList.toggle('bg2', i % 2 !== 0);
        i++;
    });
}

function einmal_filter() {
    fuegeAusblendenButtonsHinzu();
    fuegeAusblendenButtonsBeitraegeHinzu();
    filtere_inhalt();
}

function mylog(message) { console.log('dfde-user-filter: ' + message); }

function verstecke(container, label) {
    if (!container || container.style.display === 'none') return;
    container.style.display = 'none';
    mylog(label);
}

function filtere_themen_nach_nutzer() {
    const nutzer = getGeblockteNutzerListe();
    if (nutzer.length === 0 || !cfg.filter_nach_ersteller) return;

    document.querySelectorAll('ul.topiclist.topics li.row').forEach(row => {
        if (row.style.display === 'none') return;
        const el = row.querySelector('div.responsive-hide .username');
        if (el && nutzer.includes(el.textContent.trim().toLowerCase())) {
            verstecke(row, 'Thema ausgeblendet (Ersteller: ' + el.textContent.trim() + ')');
        }
    });
}

function filtere_beitraege_nach_nutzer() {
    const nutzer = getGeblockteNutzerListe();
    if (nutzer.length === 0 || !cfg.filter_beitraege) return;

    const startParam = parseInt(new URLSearchParams(window.location.search).get('start') || '0', 10);
    const posts = document.querySelectorAll('div.post');
    posts.forEach(post => {
        if (startParam === 0 && post.querySelector('h3.first')) return;
        if (post.style.display === 'none') return;
        const el = post.querySelector('dl.postprofile dt .username, dl.postprofile dt .username-coloured');
        mylog("beitrag gefunden von benutzer: " + (el && el.textContent.trim().toLowerCase()));
        if (el && nutzer.includes(el.textContent.trim().toLowerCase())) {
            verstecke(post, 'Beitrag ausgeblendet (Autor: ' + el.textContent.trim() + ')');
            const next = post.nextElementSibling;
            if (next && next.tagName === 'HR' && next.classList.contains('divider')) {
                next.style.display = 'none';
            }
        }
    });
}

function getTitelRegexListe() {
    return cfg.geblockte_titel
        .split('|')
        .map(s => s.trim())
        .filter(s => s.length > 0)
        .map(s => { try { return new RegExp(s, 'i'); } catch (e) { mylog('Ungültiger Titel-Regex: ' + e.message); return null; } })
        .filter(r => r !== null);
}

function filtere_themen_nach_titel() {
    if (!cfg.filter_nach_titel) return;
    const muster = getTitelRegexListe();
    if (muster.length === 0) return;

    document.querySelectorAll('ul.topiclist.topics li.row').forEach(row => {
        if (row.style.display === 'none') return;
        const titelEl = row.querySelector('a.topictitle');
        if (!titelEl) return;
        const titel = titelEl.textContent.trim();
        if (muster.some(re => re.test(titel))) {
            verstecke(row, 'Thema ausgeblendet (Titel: ' + titel + ')');
        }
    });
}

function fuegeAusblendenButtonsHinzu() {
    if (!document.querySelector('ul.topiclist.topics')) return;

    if (!document.getElementById('cf-hide-btn-style')) {
        const style = document.createElement('style');
        style.id = 'cf-hide-btn-style';
        style.textContent = `
            .cf-hide-btn {
                background: none; border: none; cursor: pointer;
                color: #aaa; padding: 0 0 0 7px; font-size: 11px;
                vertical-align: middle; line-height: 1;
            }
            .cf-hide-btn:hover { color: #c00; }
        `;
        document.head.appendChild(style);
    }

    document.querySelectorAll('ul.topiclist.topics li.row').forEach(row => {
        if (row.querySelector('.cf-hide-btn')) return;
        const titelEl = row.querySelector('a.topictitle');
        const userEl  = row.querySelector('div.responsive-hide .username');
        if (!titelEl || !userEl) return;

        const btn = document.createElement('button');
        btn.className = 'cf-hide-btn';
        btn.title = 'Thread ausblenden';
        btn.innerHTML = '<i class="icon fa-eye-slash fa-fw" aria-hidden="true"></i>';
        btn.addEventListener('click', e => {
            e.preventDefault();
            e.stopPropagation();
            const titel   = titelEl.textContent.trim();
            const escaped = titel.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
            const liste   = nutzerZuListe(cfg.geblockte_titel);
            if (!liste.includes(escaped)) {
                liste.push(escaped);
                cfg.geblockte_titel = listeZuNutzer(liste);
                GM_setValue('geblockte_titel', cfg.geblockte_titel);
            }
            if (!cfg.filter_nach_titel) {
                cfg.filter_nach_titel = true;
                GM_setValue('filter_nach_titel', true);
            }
            verstecke(row, 'Thema ausgeblendet (Titel-Button: ' + titel + ')');
        });

        titelEl.after(btn);
    });
}

function nutzerZuListe(nutzer) {
    return nutzer.split('|').map(s => s.trim()).filter(s => s.length > 0);
}

function listeZuNutzer(liste) {
    return liste.filter(s => s.trim().length > 0).join('|');
}

// ---- Beitrags-ID-Filter ----

function kuerzeZeitstempel(zeit) {
    const m = zeit.match(/(\d{2})\.(\d{2})\.\d{4}\s+(\d{2}):(\d{2})/);
    if (!m) return zeit;
    return m[3] + ':' + m[4] + ' ' + m[1] + '.' + m[2];
}

function getGeblockteBeitraegeListe() {
    return cfg.geblockte_beitraege
        .split('|')
        .map(s => s.trim())
        .filter(s => s.length > 0)
        .map(s => { const p = s.split('::'); return { id: p[0] || '', username: p[1] || '', zeit: p[2] || '', titel: p[3] || '' }; });
}

function filtere_beitraege_nach_id() {
    if (!cfg.filter_nach_beitragsid) return;
    const liste = getGeblockteBeitraegeListe();
    if (liste.length === 0) return;
    const ids = new Set(liste.map(e => e.id));
    document.querySelectorAll('div.post').forEach(post => {
        if (post.style.display === 'none') return;
        const postId = post.id.replace(/^p/, '');
        if (ids.has(postId)) {
            verstecke(post, 'Beitrag ausgeblendet (ID: ' + postId + ')');
            const next = post.nextElementSibling;
            if (next && next.tagName === 'HR' && next.classList.contains('divider')) {
                next.style.display = 'none';
            }
        }
    });
}

function fuegeAusblendenButtonsBeitraegeHinzu() {
    if (!document.querySelector('div.post')) return;

    if (!document.getElementById('cf-hide-btn-style')) {
        const style = document.createElement('style');
        style.id = 'cf-hide-btn-style';
        style.textContent = `
            .cf-hide-btn {
                background: none; border: none; cursor: pointer;
                color: #aaa; padding: 0 0 0 7px; font-size: 11px;
                vertical-align: middle; line-height: 1;
            }
            .cf-hide-btn:hover { color: #c00; }
        `;
        document.head.appendChild(style);
    }

    const startParam = parseInt(new URLSearchParams(window.location.search).get('start') || '0', 10);
    document.querySelectorAll('div.post').forEach(post => {
        if (post.querySelector('.cf-post-hide-btn')) return;
        if (startParam === 0 && post.querySelector('h3.first')) return;
        const h3 = post.querySelector('h3');
        if (!h3) return;
        const userEl = post.querySelector('dl.postprofile dt .username, dl.postprofile dt .username-coloured');
        if (!userEl) return;

        const authorEl  = post.querySelector('p.author');
        const zeitMatch = authorEl ? authorEl.textContent.match(/(\d{2}\.\d{2}\.\d{4}\s+\d{2}:\d{2}:\d{2})/) : null;
        const zeitKurz  = kuerzeZeitstempel(zeitMatch ? zeitMatch[1] : '');
        const postId    = post.id.replace(/^p/, '');
        const username  = userEl.textContent.trim();
        const titelEl   = h3.querySelector('a');
        const titel     = titelEl ? titelEl.textContent.trim() : '';

        const btn = document.createElement('button');
        btn.className = 'cf-hide-btn cf-post-hide-btn';
        btn.title = 'Beitrag ausblenden';
        btn.innerHTML = '<i class="icon fa-eye-slash fa-fw" aria-hidden="true"></i>';
        btn.addEventListener('click', e => {
            e.preventDefault();
            e.stopPropagation();
            const eintrag = postId + '::' + username + '::' + zeitKurz + '::' + titel;
            const liste = nutzerZuListe(cfg.geblockte_beitraege);
            if (!liste.some(s => s.split('::')[0] === postId)) {
                liste.push(eintrag);
                cfg.geblockte_beitraege = listeZuNutzer(liste);
                GM_setValue('geblockte_beitraege', cfg.geblockte_beitraege);
            }
            if (!cfg.filter_nach_beitragsid) {
                cfg.filter_nach_beitragsid = true;
                GM_setValue('filter_nach_beitragsid', true);
            }
            verstecke(post, 'Beitrag ausgeblendet (ID-Button: ' + postId + ')');
            const next = post.nextElementSibling;
            if (next && next.tagName === 'HR' && next.classList.contains('divider')) {
                next.style.display = 'none';
            }
        });

        h3.appendChild(btn);
    });
}

// ---- Import/Export ----

function djb2Hash(str) {
    let hash = 5381;
    for (let i = 0; i < str.length; i++) {
        hash = ((hash << 5) + hash) + str.charCodeAt(i);
        hash |= 0;
    }
    return (hash >>> 0).toString(16).padStart(8, '0');
}

function cfgExportieren() {
    const json = JSON.stringify(cfg);
    return btoa('v1:' + djb2Hash(json) + ':' + json);
}

function cfgImportieren(str) {
    let decoded;
    try { decoded = atob(str); } catch (e) { return { ok: false, fehler: 'Ungültiger Base64-String.' }; }
    const sep1 = decoded.indexOf(':');
    const sep2 = decoded.indexOf(':', sep1 + 1);
    if (sep1 < 0 || sep2 < 0) return { ok: false, fehler: 'Ungültiges Format.' };
    const version = decoded.slice(0, sep1);
    const hash    = decoded.slice(sep1 + 1, sep2);
    const json    = decoded.slice(sep2 + 1);
    if (version !== 'v1') return { ok: false, fehler: 'Unbekannte Version: ' + version };
    if (djb2Hash(json) !== hash) return { ok: false, fehler: 'Prüfziffer ungültig — String beschädigt?' };
    let neu;
    try { neu = JSON.parse(json); } catch (e) { return { ok: false, fehler: 'JSON-Fehler: ' + e.message }; }
    for (const key of Object.keys(DEFAULTS)) {
        if (!(key in neu)) neu[key] = DEFAULTS[key];
    }
    return { ok: true, cfg: neu };
}

// ---- Dialog ----

const DIALOG_CSS = `
* { box-sizing: border-box; font-family: system-ui, sans-serif; margin: 0; padding: 0; }
#cf-overlay {
    position: absolute; inset: 0;
    background: rgba(0,0,0,0.55);
    display: flex; align-items: center; justify-content: center;
}
#cf-box {
    background: #fff; border-radius: 12px; padding: 28px 32px;
    max-width: 480px; width: 90%;
    box-shadow: 0 8px 32px rgba(0,0,0,0.25);
    max-height: 90vh; overflow-y: auto;
}
h2 { font-size: 18px; color: #222; font-weight: 700; margin-bottom: 20px; }
.cf-row { display: flex; align-items: center; gap: 10px; margin-bottom: 12px; cursor: pointer; }
.cf-row input[type="checkbox"] {
    width: 18px; height: 18px; flex-shrink: 0; cursor: pointer;
    accent-color: #2563eb; margin: 0;
}
.cf-row span { font-size: 14px; color: #333; line-height: 1.4; }
hr { border: none; border-top: 1px solid #eee; margin: 16px 0; }
.cf-label { display: block; font-size: 13px; color: #444; margin-bottom: 8px; font-weight: 600; }
#cf-nutzer-liste { display: flex; flex-direction: column; gap: 6px; margin-bottom: 8px; }
.cf-nutzer-zeile { display: flex; align-items: center; gap: 6px; }
.cf-nutzer-zeile input[type="text"] {
    flex: 1; padding: 7px 10px;
    border: 1px solid #ccc; border-radius: 6px;
    font-size: 13px; color: #222; background: #fff; min-width: 0;
}
.cf-nutzer-zeile input[type="text"]:focus { border-color: #2563eb; outline: none; }
.cf-nutzer-zeile input.cf-nutzer-kommentar { flex: 0.8; color: #888; font-style: italic; }
.cf-btn-remove {
    width: 28px; height: 28px; border: 1px solid #ccc; border-radius: 50%;
    background: #f5f5f5; color: #666; font-size: 16px; cursor: pointer;
    display: flex; align-items: center; justify-content: center; flex-shrink: 0;
}
.cf-btn-remove:hover { background: #fee2e2; border-color: #f87171; color: #c00; }
#cf-btn-add {
    display: flex; align-items: center; gap: 6px; padding: 6px 12px;
    border: 1px dashed #2563eb; border-radius: 6px;
    background: #eff6ff; color: #2563eb; font-size: 13px; cursor: pointer;
    width: 100%; justify-content: center; margin-bottom: 4px;
}
#cf-btn-add:hover { background: #dbeafe; }
#cf-hint { font-size: 12px; color: #888; margin-top: 4px; font-style: italic; }
#cf-buttons { display: flex; gap: 10px; margin-top: 24px; justify-content: flex-end; flex-wrap: wrap; }
#cf-buttons button {
    padding: 8px 16px; border-radius: 6px; font-size: 14px; cursor: pointer;
    border: 1px solid #ccc; background: #f5f5f5; color: #333; font-weight: 500;
}
#cf-save { background: #2563eb; color: #fff; border-color: #2563eb; font-weight: 700; padding: 8px 20px; }
#cf-titel-liste { display: flex; flex-direction: column; gap: 6px; margin-bottom: 8px; }
.cf-titel-zeile { display: flex; align-items: center; gap: 6px; }
.cf-titel-zeile input[type="text"] {
    flex: 1; padding: 7px 10px;
    border: 1px solid #ccc; border-radius: 6px;
    font-size: 13px; font-family: monospace; color: #222; background: #fff; min-width: 0;
}
.cf-titel-zeile input[type="text"]:focus { border-color: #2563eb; outline: none; }
#cf-btn-titel-add {
    display: flex; align-items: center; gap: 6px; padding: 6px 12px;
    border: 1px dashed #2563eb; border-radius: 6px;
    background: #eff6ff; color: #2563eb; font-size: 13px; cursor: pointer;
    width: 100%; justify-content: center; margin-bottom: 4px;
}
#cf-btn-titel-add:hover { background: #dbeafe; }
#cf-beitrag-liste { display: flex; flex-direction: column; gap: 4px; margin-bottom: 8px; }
.cf-beitrag-zeile { display: flex; align-items: center; gap: 6px; padding: 4px 0; }
.cf-beitrag-info { flex: 1; font-size: 12px; color: #555; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cf-beitrag-info a { color: #2563eb; text-decoration: none; }
.cf-beitrag-info a:hover { text-decoration: underline; }
#cf-ie-box {
    display: none;
    background: #fff; border-radius: 12px; padding: 28px 32px;
    max-width: 480px; width: 90%;
    box-shadow: 0 8px 32px rgba(0,0,0,0.25);
    max-height: 90vh; overflow-y: auto;
}
.cf-ie-textarea {
    width: 100%; height: 80px;
    border: 1px solid #ccc; border-radius: 6px;
    padding: 7px 10px; font-size: 12px; font-family: monospace;
    color: #444; background: #f9f9f9; resize: vertical;
    margin-bottom: 16px; display: block;
}
.cf-ie-input {
    width: 100%; height: 80px;
    border: 1px solid #ccc; border-radius: 6px;
    padding: 7px 10px; font-size: 12px; font-family: monospace;
    color: #222; background: #fff; resize: vertical;
    margin-bottom: 8px; display: block;
}
.cf-ie-input:focus { border-color: #2563eb; outline: none; }
#cf-ie-error { font-size: 12px; color: #c00; margin-bottom: 12px; min-height: 16px; }
#cf-ie-buttons { display: flex; gap: 10px; margin-top: 24px; justify-content: flex-end; flex-wrap: wrap; }
#cf-ie-buttons button {
    padding: 8px 16px; border-radius: 6px; font-size: 14px; cursor: pointer;
    border: 1px solid #ccc; background: #f5f5f5; color: #333; font-weight: 500;
}
#cf-ie-import-btn { background: #2563eb; color: #fff; border-color: #2563eb; font-weight: 700; padding: 8px 20px; }
`;

let _host = null;
let _shadow = null;

function vorbereiteDialog() {
    _host = document.createElement('div');
    _host.style.cssText = 'position:fixed;inset:0;z-index:999999;';

    _shadow = _host.attachShadow({ mode: 'open' });

    const styleEl = document.createElement('style');
    styleEl.textContent = DIALOG_CSS;
    _shadow.appendChild(styleEl);

    const overlay = document.createElement('div');
    overlay.id = 'cf-overlay';

    const box = document.createElement('div');
    box.id = 'cf-box';
    box.innerHTML = `
        <h2>⚙ Nutzer-Filter Einstellungen</h2>
        <label class="cf-row">
            <input type="checkbox" id="cf-filter-ersteller">
            <span>Themen nach Ersteller filtern</span>
        </label>
        <label class="cf-row">
            <input type="checkbox" id="cf-filter-beitraege">
            <span>Beiträge in der Threadansicht ausblenden</span>
        </label>
        <hr>
        <span class="cf-label">Geblockte Nutzernamen</span>
        <div id="cf-nutzer-liste"></div>
        <button id="cf-btn-add">+ Nutzer hinzufügen</button>
        <div id="cf-hint">Groß-/Kleinschreibung wird ignoriert.</div>
        <hr>
        <label class="cf-row">
            <input type="checkbox" id="cf-filter-titel">
            <span>Threads nach Titel ausblenden (RegEx)</span>
        </label>
        <div id="cf-titel-liste"></div>
        <button id="cf-btn-titel-add">+ Regex hinzufügen</button>
        <div id="cf-hint">Reguläre Ausdrücke, Groß-/Kleinschreibung wird ignoriert.</div>
        <hr>
        <label class="cf-row">
            <input type="checkbox" id="cf-filter-beitragsid">
            <span>Einzelne Beiträge ausblenden (Nur per Ausblendsymbol)</span>
        </label>
        <span class="cf-label">Ausgeblendete Beiträge</span>
        <div id="cf-beitrag-liste"></div>
        <div id="cf-buttons">
            <button id="cf-ie-btn">Import/Export</button>
            <button id="cf-reset">Zurücksetzen</button>
            <button id="cf-cancel">Abbrechen</button>
            <button id="cf-save">Speichern</button>
        </div>
    `;

    overlay.appendChild(box);

    const ieBox = document.createElement('div');
    ieBox.id = 'cf-ie-box';
    ieBox.innerHTML = `
        <h2>⇅ Import / Export</h2>
        <span class="cf-label">Aktuelle Konfiguration (Export)</span>
        <textarea class="cf-ie-textarea" id="cf-ie-export" readonly></textarea>
        <span class="cf-label">Konfiguration importieren</span>
        <textarea class="cf-ie-input" id="cf-ie-import" placeholder="Import-String hier einfügen …"></textarea>
        <div id="cf-ie-error"></div>
        <div id="cf-ie-buttons">
            <button id="cf-ie-cancel">Abbrechen</button>
            <button id="cf-ie-import-btn">Importieren</button>
        </div>
    `;
    overlay.appendChild(ieBox);
    _shadow.appendChild(overlay);

    _shadow.querySelector('#cf-btn-add').addEventListener('click', () => addNutzerZeile(''));
    _shadow.querySelector('#cf-btn-titel-add').addEventListener('click', () => addTitelZeile(''));

    _shadow.querySelector('#cf-save').addEventListener('click', () => {
        cfg.filter_nach_ersteller  = _shadow.querySelector('#cf-filter-ersteller').checked;
        cfg.filter_beitraege       = _shadow.querySelector('#cf-filter-beitraege').checked;
        cfg.geblockte_nutzer       = listeZuNutzer(getNutzerWerte());
        cfg.filter_nach_titel      = _shadow.querySelector('#cf-filter-titel').checked;
        cfg.geblockte_titel        = listeZuNutzer(getTitelWerte());
        cfg.filter_nach_beitragsid = _shadow.querySelector('#cf-filter-beitragsid').checked;
        cfg.geblockte_beitraege    = listeZuNutzer(
            Array.from(_shadow.querySelectorAll('.cf-beitrag-zeile')).map(z => z.dataset.eintrag)
        );

        GM_setValue('filter_nach_ersteller',  cfg.filter_nach_ersteller);
        GM_setValue('filter_beitraege',       cfg.filter_beitraege);
        GM_setValue('geblockte_nutzer',       cfg.geblockte_nutzer);
        GM_setValue('filter_nach_titel',      cfg.filter_nach_titel);
        GM_setValue('geblockte_titel',        cfg.geblockte_titel);
        GM_setValue('filter_nach_beitragsid', cfg.filter_nach_beitragsid);
        GM_setValue('geblockte_beitraege',    cfg.geblockte_beitraege);

        schliessen();
        filtere_inhalt();
        mylog('Einstellungen gespeichert');
    });

    _shadow.querySelector('#cf-reset').addEventListener('click', () => {
        _shadow.querySelector('#cf-filter-ersteller').checked  = DEFAULTS.filter_nach_ersteller;
        _shadow.querySelector('#cf-filter-beitraege').checked  = DEFAULTS.filter_beitraege;
        _shadow.querySelector('#cf-nutzer-liste').innerHTML    = '';
        addNutzerZeile('');
        _shadow.querySelector('#cf-filter-titel').checked     = DEFAULTS.filter_nach_titel;
        _shadow.querySelector('#cf-titel-liste').innerHTML    = '';
        addTitelZeile('');
        _shadow.querySelector('#cf-filter-beitragsid').checked = DEFAULTS.filter_nach_beitragsid;
        _shadow.querySelector('#cf-beitrag-liste').innerHTML   = '';
    });

    _shadow.querySelector('#cf-cancel').addEventListener('click', schliessen);
    overlay.addEventListener('click', e => { if (e.target === overlay) schliessen(); });

    _shadow.querySelector('#cf-ie-btn').addEventListener('click', () => {
        _shadow.querySelector('#cf-ie-export').value = cfgExportieren();
        _shadow.querySelector('#cf-ie-import').value = '';
        _shadow.querySelector('#cf-ie-error').textContent = '';
        _shadow.querySelector('#cf-ie-error').style.color = '';
        _shadow.querySelector('#cf-box').style.display = 'none';
        _shadow.querySelector('#cf-ie-box').style.display = 'block';
    });

    _shadow.querySelector('#cf-ie-cancel').addEventListener('click', () => {
        _shadow.querySelector('#cf-ie-box').style.display = 'none';
        befuelleHauptDialog();
        _shadow.querySelector('#cf-box').style.display = '';
    });

    _shadow.querySelector('#cf-ie-import-btn').addEventListener('click', () => {
        const errEl = _shadow.querySelector('#cf-ie-error');
        const str = _shadow.querySelector('#cf-ie-import').value.trim();
        if (!str) {
            errEl.style.color = '';
            errEl.textContent = 'Bitte einen Import-String eingeben.';
            return;
        }
        const result = cfgImportieren(str);
        if (!result.ok) {
            errEl.style.color = '';
            errEl.textContent = result.fehler;
            return;
        }
        const neu = result.cfg;
        cfg.geblockte_nutzer       = neu.geblockte_nutzer;
        cfg.filter_nach_ersteller  = neu.filter_nach_ersteller;
        cfg.filter_beitraege       = neu.filter_beitraege;
        cfg.geblockte_titel        = neu.geblockte_titel;
        cfg.filter_nach_titel      = neu.filter_nach_titel;
        cfg.geblockte_beitraege    = neu.geblockte_beitraege;
        cfg.filter_nach_beitragsid = neu.filter_nach_beitragsid;
        GM_setValue('geblockte_nutzer',       cfg.geblockte_nutzer);
        GM_setValue('filter_nach_ersteller',  cfg.filter_nach_ersteller);
        GM_setValue('filter_beitraege',       cfg.filter_beitraege);
        GM_setValue('geblockte_titel',        cfg.geblockte_titel);
        GM_setValue('filter_nach_titel',      cfg.filter_nach_titel);
        GM_setValue('geblockte_beitraege',    cfg.geblockte_beitraege);
        GM_setValue('filter_nach_beitragsid', cfg.filter_nach_beitragsid);
        errEl.style.color = '#16a34a';
        errEl.textContent = 'Konfiguration erfolgreich importiert.';
        filtere_inhalt();
        mylog('Konfiguration importiert');
    });

    mylog('Dialog vorbereitet');
}

function befuelleHauptDialog() {
    _shadow.querySelector('#cf-filter-ersteller').checked = cfg.filter_nach_ersteller;
    _shadow.querySelector('#cf-filter-beitraege').checked = cfg.filter_beitraege;

    _shadow.querySelector('#cf-nutzer-liste').innerHTML = '';
    const liste = nutzerZuListe(cfg.geblockte_nutzer);
    (liste.length > 0 ? liste : ['']).forEach(n => addNutzerZeile(n));

    _shadow.querySelector('#cf-filter-titel').checked = cfg.filter_nach_titel;
    _shadow.querySelector('#cf-titel-liste').innerHTML = '';
    const titelListe = nutzerZuListe(cfg.geblockte_titel);
    (titelListe.length > 0 ? titelListe : ['']).forEach(t => addTitelZeile(t));

    _shadow.querySelector('#cf-filter-beitragsid').checked = cfg.filter_nach_beitragsid;
    _shadow.querySelector('#cf-beitrag-liste').innerHTML = '';
    getGeblockteBeitraegeListe().forEach(e => addBeitragZeile(e.id + '::' + e.username + '::' + e.zeit + '::' + e.titel));
}

function zeigeEinstellungen() {
    if (!_host) vorbereiteDialog();
    befuelleHauptDialog();
    document.body.appendChild(_host);
}

function schliessen() {
    _host?.remove();
}

function addNutzerZeile(eintrag) {
    const liste = _shadow.querySelector('#cf-nutzer-liste');
    const zeile = document.createElement('div');
    zeile.className = 'cf-nutzer-zeile';

    const parts = eintrag.split('::');
    const inputName = document.createElement('input');
    inputName.type = 'text';
    inputName.value = parts[0] || '';
    inputName.placeholder = 'z.B. DEBIANUNDANDREAS';

    const inputKommentar = document.createElement('input');
    inputKommentar.type = 'text';
    inputKommentar.className = 'cf-nutzer-kommentar';
    inputKommentar.value = parts[1] || '';
    inputKommentar.placeholder = 'Kommentar (optional)';

    const btnRemove = document.createElement('button');
    btnRemove.className = 'cf-btn-remove';
    btnRemove.title = 'Entfernen';
    btnRemove.textContent = '×';
    btnRemove.addEventListener('click', () => {
        if (_shadow.querySelectorAll('.cf-nutzer-zeile').length > 1) {
            zeile.remove();
        } else {
            inputName.value = '';
            inputKommentar.value = '';
        }
    });

    zeile.appendChild(inputName);
    zeile.appendChild(inputKommentar);
    zeile.appendChild(btnRemove);
    liste.appendChild(zeile);
    inputName.focus();
}

function getNutzerWerte() {
    return Array.from(_shadow.querySelectorAll('.cf-nutzer-zeile')).map(zeile => {
        const inputs = zeile.querySelectorAll('input[type="text"]');
        const name = inputs[0].value.trim();
        const kommentar = inputs[1].value.trim();
        return kommentar ? name + '::' + kommentar : name;
    });
}

function addTitelZeile(wert) {
    const liste = _shadow.querySelector('#cf-titel-liste');
    const zeile = document.createElement('div');
    zeile.className = 'cf-titel-zeile';

    const input = document.createElement('input');
    input.type = 'text';
    input.value = wert;
    input.placeholder = 'z.B. Debian Quatscher';

    const btnRemove = document.createElement('button');
    btnRemove.className = 'cf-btn-remove';
    btnRemove.title = 'Entfernen';
    btnRemove.textContent = '×';
    btnRemove.addEventListener('click', () => {
        if (_shadow.querySelectorAll('.cf-titel-zeile').length > 1) {
            zeile.remove();
        } else {
            input.value = '';
        }
    });

    zeile.appendChild(input);
    zeile.appendChild(btnRemove);
    liste.appendChild(zeile);
}

function getTitelWerte() {
    return Array.from(_shadow.querySelectorAll('.cf-titel-zeile input[type="text"]'))
        .map(el => el.value.trim());
}

function addBeitragZeile(eintrag) {
    const liste = _shadow.querySelector('#cf-beitrag-liste');
    const zeile = document.createElement('div');
    zeile.className = 'cf-beitrag-zeile';
    zeile.dataset.eintrag = eintrag;

    const parts = eintrag.split('::');
    const postId   = parts[0] || '';
    const username = parts[1] || '';
    const zeit     = parts[2] || '';
    const titel    = parts[3] || '';
    const titelKurz = titel.length > 20 ? titel.slice(0, 20) + '…' : (titel || ('#' + postId));

    const info = document.createElement('span');
    info.className = 'cf-beitrag-info';
    const link = document.createElement('a');
    link.href = 'https://debianforum.de/forum/viewtopic.php?p=' + postId + '#p' + postId;
    link.target = '_blank';
    link.textContent = titelKurz;
    info.appendChild(link);
    info.appendChild(document.createTextNode(' · ' + username + ' · ' + zeit));

    const btnRemove = document.createElement('button');
    btnRemove.className = 'cf-btn-remove';
    btnRemove.title = 'Entfernen';
    btnRemove.textContent = '×';
    btnRemove.addEventListener('click', () => zeile.remove());

    zeile.appendChild(info);
    zeile.appendChild(btnRemove);
    liste.appendChild(zeile);
}

if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', vorbereiteDialog);
} else {
    vorbereiteDialog();
}