dfde-user-filter

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

Dovrai installare un'estensione come Tampermonkey, Greasemonkey o Violentmonkey per installare questo script.

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

Dovrai installare un'estensione come Tampermonkey o Violentmonkey per installare questo script.

Dovrai installare un'estensione come Tampermonkey o Userscripts per installare questo script.

Dovrai installare un'estensione come ad esempio Tampermonkey per installare questo script.

Dovrai installare un gestore di script utente per installare questo script.

(Ho già un gestore di script utente, lasciamelo installare!)

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

(Ho già un gestore di stile utente, lasciamelo installare!)

// ==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();
}