dfde-user-filter

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

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램을 설치해야 합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

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