Ekşi Author Filter

Filters entries from a remote list (hide/collapse) and adds profile page warnings for Ekşi Sözlük.

// ==UserScript==
// @name         Ekşi Author Filter
// @namespace    http://tampermonkey.net/
// @version      1.2
// @description  Filters entries from a remote list (hide/collapse) and adds profile page warnings for Ekşi Sözlük.
// @author       @protono
// @match        *://eksisozluk.com/*
// @match        *://eksisozluk.com/
// @match        *://eksisozluk.com/*--*
// @match        *://eksisozluk.com/basliklar/gundem*
// @match        *://eksisozluk.com/basliklar/bugun*
// @match        *://eksisozluk.com/basliklar/populer*
// @match        *://eksisozluk.com/basliklar/debe*
// @match        *://eksisozluk.com/basliklar/kanal/*
// @match        *://eksisozluk.com/biri/*
// @exclude      *://eksisozluk.com/mesaj/*
// @exclude      *://eksisozluk.com/ayarlar/*
// @exclude      *://eksisozluk.com/hesap/*
// @exclude      *://eksisozluk.com/tercihler/*
// @icon         https://eksisozluk.com/favicon.ico
// @connect      raw.githubusercontent.com
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_xmlhttpRequest
// @grant        GM_registerMenuCommand
// @grant        GM_addStyle
// @grant        GM_deleteValue
// @run-at       document-idle
// @license      MIT
// ==/UserScript==

(async () => {
    'use strict';

    const SCRIPT_NAME = "Ekşi Sözlük Unified Filter";
    const PRIMARY_LIST_URL = "https://raw.githubusercontent.com/bat9254/troll-list/refs/heads/main/list.txt";
    const UPDATE_INTERVAL_MS = 24 * 60 * 60 * 1000;
    const NETWORK_TIMEOUT_MS = 20000;
    const LOG_PREFIX = `[${SCRIPT_NAME}]`;
    const DEBOUNCE_DELAY_MS = 300;
    const SAVE_COUNT_DEBOUNCE_MS = 2500;
    const TOPIC_WARNING_THRESHOLD = 3;
    const CSS_PREFIX = "eusf-";

    const KEY_PAUSED = "eusf_paused_v1";
    const KEY_MODE = "eusf_filterMode_v1";
    const KEY_SHOW_WARNING = "eusf_showTopicWarning_v1";
    const KEY_LIST_RAW = "eusf_authorListRaw_v1";
    const KEY_LAST_UPDATE = "eusf_lastUpdateTime_v1";
    const KEY_TOTAL_FILTERED = "eusf_totalFiltered_v1";

    GM_addStyle(`
        .${CSS_PREFIX}topic-warning { background-color:#fff0f0; border:1px solid #d9534f; border-left:3px solid #d9534f; border-radius:3px; padding:2px 6px; margin-left:8px; font-size:0.85em; color:#a94442; display:inline-block; vertical-align:middle; cursor:help; font-weight:bold; }
        .${CSS_PREFIX}hidden { display: none !important; }
        .${CSS_PREFIX}collapsed > .content,
        .${CSS_PREFIX}collapsed > footer > .feedback-container,
        .${CSS_PREFIX}collapsed > footer .entry-footer-bottom > .footer-info > div:not(#entry-nick-container):not(:has(.entry-date)) { display: none !important; }
        .${CSS_PREFIX}collapsed > footer, .${CSS_PREFIX}collapsed footer > .info, .${CSS_PREFIX}collapsed footer .entry-footer-bottom { min-height: 1px; }
        .${CSS_PREFIX}collapsed #entry-nick-container, .${CSS_PREFIX}collapsed .entry-date { display:inline-block !important; visibility:visible !important; opacity:1 !important; }
        .${CSS_PREFIX}collapsed { min-height:35px !important; padding-bottom:0 !important; margin-bottom:10px !important; border-left:3px solid #ffcccc !important; background-color:rgba(128,128,128,0.03); overflow:hidden; }
        .${CSS_PREFIX}collapse-placeholder { min-height:25px; background-color:transparent; border:none; padding:6px 10px 6px 12px; margin-bottom:0px; font-style:normal; color:#6c757d; position:relative; display:flex; align-items:center; flex-wrap:wrap; box-sizing:border-box; }
        .${CSS_PREFIX}collapse-placeholder .${CSS_PREFIX}collapse-icon { margin-right:6px; opacity:0.9; font-style:normal; display:inline-block; color:#dc3545; cursor:help; }
        .${CSS_PREFIX}collapse-placeholder .${CSS_PREFIX}collapse-text { margin-right:10px; flex-grow:1; display:inline-block; font-size:0.9em; font-weight:500; }
        .${CSS_PREFIX}collapse-placeholder .${CSS_PREFIX}collapse-text strong { color:#dc3545; font-weight:600; }
        .${CSS_PREFIX}collapse-placeholder .${CSS_PREFIX}show-link { font-style:normal; flex-shrink:0; margin-left:auto; }
        .${CSS_PREFIX}collapse-placeholder .${CSS_PREFIX}show-link a { cursor:pointer; text-decoration:none; color:#0d6efd; font-size:0.9em; padding:1px 4px; border-radius:3px; font-weight:bold; border:1px solid transparent; transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out; }
        .${CSS_PREFIX}collapse-placeholder .${CSS_PREFIX}show-link a::before { content:"» "; opacity:0.7; }
        .${CSS_PREFIX}collapse-placeholder .${CSS_PREFIX}show-link a:hover { color:#0a58ca; text-decoration:underline; background-color:rgba(13,110,253,0.1); border-color:rgba(13,110,253,0.2); }
        .${CSS_PREFIX}opened-warning { font-size:0.8em; color:#856404; background-color:#fff3cd; border:1px solid #ffeeba; border-radius:3px; padding:1px 4px; margin-left:8px; vertical-align:middle; cursor:help; display:inline-block; font-style:normal; font-weight:bold; }
        .${CSS_PREFIX}profile-warning { margin-left: 10px; padding: 2px 6px; font-size: 0.85em; font-weight: bold; color: #a94442; background-color: #f8d7da; border: 1px solid #f5c6cb; border-radius: 4px; vertical-align: middle; cursor: help; }
    `);

    const logger = {
        log: (...args) => console.log(LOG_PREFIX, ...args),
        warn: (...args) => console.warn(LOG_PREFIX, ...args),
        error: (...args) => console.error(LOG_PREFIX, ...args),
        debug: (...args) => console.debug(LOG_PREFIX, ...args),
    };

    const debounce = (func, wait) => {
        let timeout;
        return (...args) => {
            clearTimeout(timeout);
            timeout = setTimeout(() => func.apply(this, args), wait);
        };
    };

    const requiredGmFunctions = ['GM_getValue', 'GM_setValue', 'GM_xmlhttpRequest', 'GM_registerMenuCommand', 'GM_addStyle', 'GM_deleteValue'];
    if (requiredGmFunctions.some(fn => typeof window[fn] !== 'function')) {
        const missing = requiredGmFunctions.filter(fn => typeof window[fn] !== 'function');
        const errorMsg = `Hata: Gerekli Tampermonkey API fonksiyonları eksik: ${missing.join(', ')}! Script çalışmayacak. Lütfen Tampermonkey'in güncel olduğundan ve script'e @grant yetkilerinin verildiğinden emin olun.`;
        logger.error(errorMsg);
        if (typeof window.alert === 'function') {
            alert(`${SCRIPT_NAME} - Hata:\n${errorMsg}`);
        }
        return;
    }

    function showFeedback(title, text, options = {}) {
        const { isError = false, silent = false } = options;
        const prefix = isError ? "Hata" : "Bilgi";
        (isError ? logger.error : logger.log)(`${prefix}: ${title}`, text);
        if (!silent && typeof window.alert === 'function') {
            alert(`[${SCRIPT_NAME}] ${prefix}: ${title}\n\n${text}`);
        }
    }

    let config = {};
    let filteredAuthorsSet = new Set();
    let filteredListSize = 0;
    let filteredEntryCountOnPage = 0;
    let firstEntryAuthorFilteredOnPage = false;
    let topicWarningElement = null;
    let isFirstEntryOnPageProcessed = false;
    let entryListContainerEl = null;

    async function loadConfig() {
        logger.debug("Yapılandırma yükleniyor...");
        try {
            const results = await Promise.allSettled([
                GM_getValue(KEY_PAUSED, false),
                GM_getValue(KEY_MODE, "collapse"),
                GM_getValue(KEY_SHOW_WARNING, true),
                GM_getValue(KEY_LIST_RAW, ""),
                GM_getValue(KEY_LAST_UPDATE, 0),
                GM_getValue(KEY_TOTAL_FILTERED, 0)
            ]);

            const getValueFromResult = (result, defaultValue, keyName) => {
                if (result.status === 'fulfilled') {
                    return result.value;
                } else {
                    logger.warn(`'${keyName}' yüklenemedi, varsayılan (${defaultValue}) kullanılıyor. Hata:`, result.reason);
                    return defaultValue;
                }
            };

            config = {
                paused: getValueFromResult(results[0], false, KEY_PAUSED),
                filterMode: getValueFromResult(results[1], "collapse", KEY_MODE),
                showWarning: getValueFromResult(results[2], true, KEY_SHOW_WARNING),
                listRaw: getValueFromResult(results[3], "", KEY_LIST_RAW),
                lastUpdate: getValueFromResult(results[4], 0, KEY_LAST_UPDATE),
                totalFiltered: getValueFromResult(results[5], 0, KEY_TOTAL_FILTERED)
            };

            filteredAuthorsSet = parseAuthorList(config.listRaw);
            filteredListSize = filteredAuthorsSet.size;
            logger.log(`Yapılandırma: Durum: ${config.paused ? 'Duraklatıldı' : 'Aktif'}, Mod: ${config.filterMode}, Uyarılar: ${config.showWarning ? 'Açık' : 'Kapalı'}, Liste Boyutu: ${filteredListSize}, Toplam Filtrelenen: ${config.totalFiltered}`);

        } catch (err) {
            logger.error("Yapılandırma yüklenemedi:", err);
            config = { paused: false, filterMode: 'collapse', showWarning: true, listRaw: '', lastUpdate: 0, totalFiltered: 0 };
            filteredAuthorsSet = new Set();
            filteredListSize = 0;
            showFeedback("Yapılandırma Hatası", "Ayarlar yüklenemedi! Varsayılanlar kullanılıyor.", { isError: true });
        }
    }

    const debouncedSaveTotalFiltered = debounce(async (count) => {
        try {
            await GM_setValue(KEY_TOTAL_FILTERED, count);
            logger.debug(`Toplam filtreleme kaydedildi: ${count}`);
        } catch (err) {
            logger.warn("Toplam filtreleme kaydedilemedi:", err);
        }
    }, SAVE_COUNT_DEBOUNCE_MS);


    const fetchList = () => new Promise((resolve, reject) => {
        logger.debug(`Liste isteniyor: ${PRIMARY_LIST_URL}`);
        GM_xmlhttpRequest({
            method: "GET",
            url: PRIMARY_LIST_URL,
            timeout: NETWORK_TIMEOUT_MS,
            responseType: 'text',
            headers: { 'Cache-Control': 'no-cache, no-store, must-revalidate', 'Pragma': 'no-cache', 'Expires': '0' },
            onload: response => {
                if (response.status >= 200 && response.status < 300) {
                    logger.debug(`Liste alındı (HTTP ${response.status}). Boyut: ${response.responseText?.length ?? 0} bytes.`);
                    resolve(response.responseText ?? "");
                } else {
                    const errorMsg = `Liste alınamadı. Yanıt: HTTP ${response.status} ${response.statusText || ''}`;
                    logger.warn(errorMsg);
                    reject(new Error(errorMsg));
                }
            },
            onerror: response => {
                const errorMsg = `Liste çekme hatası: ${response.statusText || 'Ağ hatası'}`;
                logger.error(errorMsg, response);
                reject(new Error(errorMsg));
            },
            ontimeout: () => {
                const errorMsg = `Liste çekme zaman aşımı (${NETWORK_TIMEOUT_MS / 1000}s).`;
                logger.error(errorMsg);
                reject(new Error(errorMsg));
            }
        });
    });

    const parseAuthorList = (rawText) => {
        if (!rawText || typeof rawText !== 'string') {
            if (rawText) logger.warn("Liste metni geçersiz.");
            return new Set();
        }
        try {
            const authors = rawText.split(/\r?\n/)
                .map(line => line.replace(/#.*$/, '').trim().toLowerCase())
                .filter(line => line.length > 0);
            logger.debug(`Liste ayrıştırıldı, ${authors.length} yazar bulundu.`);
            return new Set(authors);
        } catch (err) {
            logger.error("Liste ayrıştırılamadı:", err);
            showFeedback("Liste Hatası", `Liste işlenemedi. Hata: ${err.message}`, { isError: true });
            return new Set();
        }
    };

    const syncList = async (force = false) => {
        logger.log(`Liste güncellemesi ${force ? 'zorlanıyor' : 'kontrol ediliyor'}...`);
        let newRawText;
        try {
            newRawText = await fetchList();
        } catch (err) {
            logger.error("Liste çekme hatası:", err.message);
            if (force || filteredListSize === 0) {
                showFeedback("Güncelleme Başarısız", `Liste alınamadı.\nHata: ${err.message}\nMevcut liste kullanılacak.`, { isError: true });
            }
            return false;
        }

        if (!force && config.listRaw === newRawText) {
            logger.log("Liste değişmemiş.");
            config.lastUpdate = Date.now();
            GM_setValue(KEY_LAST_UPDATE, config.lastUpdate).catch(e => logger.warn("Zaman damgası kaydı başarısız:", e));
            return false;
        }

        logger.log(force ? "Zorunlu güncelleme/liste değişmiş, işleniyor." : "Liste değişmiş, güncelleniyor.");
        let newListSet;
        try {
            newListSet = parseAuthorList(newRawText);
        } catch (err) {
            logger.error("Liste işleme hatası (syncList):", err);
            return false;
        }

        if (filteredListSize > 0 && newListSet.size === 0 && newRawText.trim().length > 0) {
            logger.warn("Yeni liste alındı ancak ayrıştırma boş! Eski liste korunuyor.");
            showFeedback("Güncelleme Uyarısı", "Yeni liste boş sonuç verdi (format hatası?). Eski liste kullanılıyor.", { isError: true });
            config.lastUpdate = Date.now();
            GM_setValue(KEY_LAST_UPDATE, config.lastUpdate).catch(e=>logger.warn("Zaman damgası kaydı (ayrıştırma hatası) başarısız:", e));
            return false;
        }

        const oldSize = filteredListSize;
        filteredAuthorsSet = newListSet;
        filteredListSize = filteredAuthorsSet.size;
        config.listRaw = newRawText;
        config.lastUpdate = Date.now();
        logger.log(`Liste güncellendi. Eski: ${oldSize}, Yeni: ${filteredListSize}`);

        try {
            await Promise.all([
                GM_setValue(KEY_LIST_RAW, config.listRaw),
                GM_setValue(KEY_LAST_UPDATE, config.lastUpdate)
            ]);
            logger.debug("Liste ve zaman damgası kaydedildi.");
        } catch (err) {
            logger.error("Liste verileri kaydedilemedi:", err);
            showFeedback("Depolama Hatası", "Liste güncellendi ancak kaydedilemedi.", { isError: true });
        }
        return true;
    };

    function applyFilterAction(entry, author) {
        const entryId = entry.dataset.id || 'ID Yok';
        const displayAuthor = entry.dataset.author || author;

        if (entry.querySelector(`.${CSS_PREFIX}opened-warning`)) {
            logger.debug(`Entry #${entryId} manuel açılmış, filtre uygulanmıyor.`);
            return false;
        }

        if (config.filterMode === "hide") {
            if (!entry.classList.contains(`${CSS_PREFIX}hidden`)) {
                entry.classList.add(`${CSS_PREFIX}hidden`);
                logger.debug(`Gizlendi: Entry #${entryId} (Yazar: ${displayAuthor})`);
                return true;
            }
            return false;
        }

        if (entry.classList.contains(`${CSS_PREFIX}collapsed`)) {
             logger.debug(`Entry #${entryId} zaten daraltılmış.`);
             return false;
        }

        const contentEl = entry.querySelector(".content");
        if (!contentEl) {
             logger.warn(`Daraltma: .content bulunamadı: Entry #${entryId}`);
             return false;
        }

        let placeholder = entry.querySelector(`.${CSS_PREFIX}collapse-placeholder`);
        if (!placeholder) {
            placeholder = document.createElement('div');
            placeholder.className = `${CSS_PREFIX}collapse-placeholder`;
            const strongAuthor = document.createElement('strong');
            strongAuthor.textContent = displayAuthor;

            const textSpan = document.createElement('span');
            textSpan.className = `${CSS_PREFIX}collapse-text`;
            textSpan.textContent = 'Listeden: ';
            textSpan.appendChild(strongAuthor);
            textSpan.insertAdjacentText('beforeend', '.');

            placeholder.innerHTML = `<span class="${CSS_PREFIX}collapse-icon" title="Yazar '${displayAuthor}' listede.">🚫</span>`;
            placeholder.appendChild(textSpan);
            placeholder.insertAdjacentHTML('beforeend', `<div class="${CSS_PREFIX}show-link"><a href="#" role="button">Göster</a></div>`);

            const showLink = placeholder.querySelector(`.${CSS_PREFIX}show-link a`);
            showLink?.addEventListener("click", (e) => {
                e.preventDefault();
                e.stopPropagation();
                const currentEntry = e.target.closest('li[data-author]');
                if (!currentEntry) {
                    logger.warn("Genişletme: Üst entry bulunamadı.");
                    return;
                }
                const currentContent = currentEntry.querySelector(".content");
                const currentPlaceholder = currentEntry.querySelector(`.${CSS_PREFIX}collapse-placeholder`);
                const currentAuthor = currentEntry.dataset.author || '?';
                const currentId = currentEntry.dataset.id || '?';

                logger.debug(`Genişletiliyor: Entry #${currentId} (Yazar: ${currentAuthor})`);

                if (currentContent) currentContent.style.display = '';
                if (currentPlaceholder) currentPlaceholder.style.display = 'none';
                currentEntry.classList.remove(`${CSS_PREFIX}collapsed`);

                const footer = currentEntry.querySelector('footer');
                if (footer && !footer.querySelector(`.${CSS_PREFIX}opened-warning`)) {
                    const warningSpan = document.createElement('span');
                    warningSpan.className = `${CSS_PREFIX}opened-warning`;
                    warningSpan.textContent = '⚠️ Filtre Açıldı';
                    warningSpan.title = `'${currentAuthor}' içeriği daraltılmıştı.`;
                    const footerInfo = footer.querySelector('.info .footer-info') || footer.querySelector('.entry-footer-bottom .footer-info') || footer.querySelector('.info') || footer;
                    footerInfo.appendChild(warningSpan);
                 } else if (!footer) {
                     logger.warn(`Genişletilen entry #${currentId} footeri bulunamadı.`);
                 }
            });

            const footerEl = entry.querySelector('footer');
            if (footerEl) {
                 entry.insertBefore(placeholder, footerEl);
            } else if (contentEl) {
                 contentEl.parentNode.insertBefore(placeholder, contentEl.nextSibling);
            } else {
                 entry.appendChild(placeholder);
                 logger.warn(`Entry #${entryId} footer/content bulunamadı, placeholder sona eklendi.`);
            }
        } else {
             placeholder.style.display = 'flex';
        }

        if(contentEl) contentEl.style.display = 'none';
        entry.classList.add(`${CSS_PREFIX}collapsed`);
        logger.debug(`Daraltıldı: Entry #${entryId} (Yazar: ${displayAuthor})`);
        return true;
    }


    function enhanceEntry(entry) {
        if (config.paused) return false;
        if (entry.dataset.eusfProcessed === 'true') return false;
        if (!entry || !entry.matches || !entry.matches('li[data-author]')) {
             if (entry && entry.dataset) entry.dataset.eusfProcessed = 'skipped_invalid_element';
             return false;
        }

        const authorOriginal = entry.dataset.author;
        const authorLower = authorOriginal?.toLowerCase().trim();
        const entryId = entry.dataset.id || 'ID Yok';

        if (!authorLower) {
            logger.warn(`Entry #${entryId} için yazar adı ('data-author') bulunamadı/boş.`);
            entry.dataset.eusfProcessed = 'skipped_empty_author';
            return false;
        }

        entry.dataset.eusfProcessed = 'true';
        let filteredByList = false;

        const firstEntryElement = entryListContainerEl?.firstElementChild;

        try {
            if (filteredAuthorsSet.has(authorLower)) {
                if (!isFirstEntryOnPageProcessed && entry === firstEntryElement) {
                    firstEntryAuthorFilteredOnPage = true;
                    logger.debug(`İlk entry listeye göre filtrelenecek: #${entryId} (Yazar: ${authorOriginal})`);
                }

                if (applyFilterAction(entry, authorLower)) {
                    filteredByList = true;
                    filteredEntryCountOnPage++;
                    config.totalFiltered = (config.totalFiltered || 0) + 1;
                    debouncedSaveTotalFiltered(config.totalFiltered);
                    entry.dataset.eusfAction = `filtered_${config.filterMode}`;
                } else {
                    entry.dataset.eusfAction = 'filter_skipped_or_failed';
                }
            } else {
                 entry.dataset.eusfAction = 'not_in_list';
            }

        } catch (err) {
            logger.error(`Entry #${entryId} işlenemedi (Yazar: ${authorOriginal}):`, err);
            entry.dataset.eusfAction = 'processing_error';
        } finally {
             if (!isFirstEntryOnPageProcessed && entry === firstEntryElement) {
                 isFirstEntryOnPageProcessed = true;
             }
        }
        return filteredByList;
    }

    const debouncedUpdateTopicWarning = debounce(() => {
        const currentEntryListContainer = document.getElementById('entry-item-list');
        const currentTitleH1 = document.getElementById("title");

        if (!currentEntryListContainer || !currentTitleH1) {
            logger.debug("Konu uyarısı: DOM elemanları bulunamadı.");
            return;
        }

        try {
            topicWarningElement?.remove();
            topicWarningElement = null;

            if (!config.showWarning || config.paused || filteredEntryCountOnPage === 0) {
                return;
            }

            const shouldShowWarning = firstEntryAuthorFilteredOnPage || filteredEntryCountOnPage >= TOPIC_WARNING_THRESHOLD;

            if (shouldShowWarning) {
                const targetElement = currentTitleH1.querySelector('a[href^="/entry/"]') || currentTitleH1;

                topicWarningElement = document.createElement("span");
                topicWarningElement.id = `${CSS_PREFIX}title-warning`;
                topicWarningElement.className = `${CSS_PREFIX}topic-warning`;
                topicWarningElement.textContent = "[Filtre Aktif]"; // Normalized text

                let titleText = `${filteredEntryCountOnPage} entry ${config.filterMode === 'hide' ? 'gizlendi' : 'daraltıldı'}.`;
                if (firstEntryAuthorFilteredOnPage) {
                    titleText += " İlk entry de filtrelendi.";
                }
                topicWarningElement.title = titleText;

                targetElement.insertAdjacentElement('beforeend', topicWarningElement);
                logger.debug(`Konu başlığı uyarısı eklendi (${filteredEntryCountOnPage} filtrelendi, ilk: ${firstEntryAuthorFilteredOnPage}).`);
            }
        } catch (err) {
            logger.error("Konu başlığı uyarısı hatası:", err);
            topicWarningElement?.remove();
            topicWarningElement = null;
        }
    }, DEBOUNCE_DELAY_MS);

    function addProfileWarning() {
        if (config.paused || !config.showWarning) {
            logger.debug("Profil uyarısı atlandı.");
            return;
        }

        const authorElement = document.querySelector('h1#user-profile-title');
        if (!authorElement) {
            return;
        }
        logger.debug("Profil sayfası uyarısı kontrol ediliyor...");

        const authorName = authorElement.textContent?.trim();
        if (!authorName) {
            logger.warn("Profil: Yazar adı alınamadı.");
            return;
        }

        const authorLower = authorName.toLowerCase();

        if (filteredAuthorsSet.has(authorLower)) {
            logger.log(`Profildeki yazar "${authorName}" listede.`);

            if (authorElement.querySelector(`.${CSS_PREFIX}profile-warning`)) {
                logger.debug("Profil uyarısı zaten mevcut.");
                return;
            }

            try {
                const warningSpan = document.createElement('span');
                warningSpan.className = `${CSS_PREFIX}profile-warning`;
                warningSpan.textContent = "[Filtre Listesinde]"; // Normalized text
                warningSpan.title = `Yazar (${authorName}) listede, içerikleri ${config.filterMode === 'hide' ? 'gizleniyor' : 'daraltılıyor'}.`;
                authorElement.appendChild(warningSpan);
                logger.log(`Profil sayfasına "${authorName}" uyarısı eklendi.`);
            } catch (err) {
                logger.error(`Profil uyarısı eklenirken hata (${authorName}):`, err);
            }
        } else {
            logger.debug(`Profildeki yazar "${authorName}" listede değil.`);
        }
    }

    let intersectionObserver = null;
    function setupIntersectionObserver() {
        if (!entryListContainerEl) {
             logger.warn("IO: #entry-item-list bulunamadı.");
             return false;
        }

        try {
            intersectionObserver = new IntersectionObserver((entries, observer) => {
                let processedCountInBatch = 0;
                entries.forEach(entry => {
                    if (entry.isIntersecting && entry.target.nodeType === Node.ELEMENT_NODE) {
                        const targetLi = entry.target;
                        if (targetLi.matches('li[data-author]') && targetLi.dataset.eusfProcessed !== 'true') {
                             if (enhanceEntry(targetLi)) {
                                 processedCountInBatch++;
                             }
                             observer.unobserve(targetLi);
                        } else if (targetLi.dataset.eusfProcessed === 'true') {
                             observer.unobserve(targetLi);
                        }
                        else if (!targetLi.matches('li[data-author]')) {
                            observer.unobserve(targetLi);
                        }
                    }
                });

                if (processedCountInBatch > 0) {
                     debouncedUpdateTopicWarning();
                }
            }, {
                root: null,
                rootMargin: '150px 0px 150px 0px',
                threshold: 0.01
             });

            const initialEntries = entryListContainerEl.querySelectorAll(`li[data-author]:not([data-eusf-processed="true"])`);
            logger.log(`IO: ${initialEntries.length} başlangıç entry'si hedefleniyor.`);
            initialEntries.forEach(entry => {
                entry.dataset.eusfObserved = 'true';
                intersectionObserver.observe(entry)
            });

            debouncedUpdateTopicWarning();
            return true;

        } catch (err) {
            logger.error("IO kurulum hatası:", err);
            intersectionObserver = null;
            return false;
        }
    }

    let mutationObserver = null;
    function setupMutationObserver() {
        if (!entryListContainerEl || !intersectionObserver) {
             logger.warn("MO: #entry-item-list veya IO eksik.");
             return false;
        }

        try {
            mutationObserver = new MutationObserver(mutations => {
                let addedToIoCount = 0;
                mutations.forEach(mutation => {
                    if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
                        mutation.addedNodes.forEach(node => {
                            if (node.nodeType === Node.ELEMENT_NODE) {
                                const entriesToAdd = [];
                                if (node.matches('li[data-author]')) {
                                    entriesToAdd.push(node);
                                }
                                else {
                                    entriesToAdd.push(...node.querySelectorAll('li[data-author]'));
                                }

                                entriesToAdd.forEach(entry => {
                                    if (entry.dataset.eusfProcessed !== 'true' && entry.dataset.eusfObserved !== 'true') {
                                        intersectionObserver.observe(entry);
                                        entry.dataset.eusfObserved = 'true';
                                        addedToIoCount++;
                                    }
                                });
                            }
                        });
                    }
                });
                if (addedToIoCount > 0) {
                    logger.debug(`MO: ${addedToIoCount} yeni entry IO'ya eklendi.`);
                }
            });

            mutationObserver.observe(entryListContainerEl, {
                childList: true,
                subtree: true
            });
            logger.log(`#entry-item-list izleniyor (MO aktif).`);
            return true;

        } catch (err) {
            logger.error("MO kurulum hatası:", err);
            mutationObserver = null;
            return false;
        }
    }

    async function initialize() {
        logger.log(`Script başlatılıyor... v${GM_info?.script?.version || '?'}`);
        await loadConfig();

        entryListContainerEl = document.getElementById('entry-item-list');

        const currentPath = window.location.pathname;

        if (currentPath.startsWith('/biri/')) {
            addProfileWarning();
            logger.log("Profil sayfası işlendi.");
        } else if (entryListContainerEl) {
            logger.log("Entry listesi sayfası.");
            if (!config.paused) {
                 if (setupIntersectionObserver()) {
                     setupMutationObserver();
                 }
            } else {
                logger.log("Script duraklatılmış, Observer'lar kurulmadı.");
            }
        } else {
             logger.log("Entry listesi/profil başlığı bulunamadı.");
        }

        if (!config.paused) {
            const now = Date.now();
            const timeSinceUpdate = now - (config.lastUpdate || 0);
            const needsUpdateCheck = filteredListSize === 0 || timeSinceUpdate > UPDATE_INTERVAL_MS;

            if (needsUpdateCheck) {
                logger.log(`Liste ${filteredListSize === 0 ? 'boş/ilk' : 'güncel değil'} (${Math.round(timeSinceUpdate / (60*60*1000))} saat). Senkronizasyon deneniyor...`);
                syncList(filteredListSize === 0).then(updated => {
                    if (updated) {
                        logger.log("Arka plan liste güncellemesi tamamlandı. Yeni boyut: " + filteredListSize);
                        if (currentPath.startsWith('/biri/')) {
                           addProfileWarning();
                        }
                    } else {
                        logger.log("Arka plan güncellemesi listeyi değiştirmedi/başarısız oldu.");
                    }
                }).catch(err => {
                    logger.error("Arka plan senkronizasyonunda hata:", err);
                });
            } else {
                logger.log(`Liste güncel. Son kontrol: ${config.lastUpdate ? new Date(config.lastUpdate).toLocaleString() : 'Hiç'}.`);
            }

            if (filteredAuthorsSet.size === 0 && !needsUpdateCheck && config.listRaw && config.listRaw.trim().length > 0) {
                logger.warn("Uyarı: Yerel liste var ama ayrıştırma boş!");
            } else if (filteredAuthorsSet.size === 0 && !config.listRaw) {
                logger.warn("Uyarı: Filtre listesi boş!");
            }
        } else {
            logger.log("Filtre duraklatılmış, güncelleme atlandı.");
        }

        registerMenuCommands();

        logger.log(`🎉 ${SCRIPT_NAME} ${config.paused ? 'Duraklatıldı' : 'aktif'}. Mod: ${config.filterMode}.`);
    }

    function registerMenuCommands() {
        const commandIds = [];

        const setConfigAndReload = async (key, value, msg) => {
            try {
                await GM_setValue(key, value);
                config[key] = value;
                showFeedback("Ayar Değişti", msg, { silent: true });
                logger.log(`Ayar: ${key}=${value}. Yenileniyor...`);
                location.reload();
            } catch (err) {
                logger.error(`Ayar (${key}) kaydedilemedi:`, err);
                showFeedback("Depolama Hatası", `Ayar (${key}) kaydedilemedi.\n${err.message}`, { isError: true });
            }
        };

        // Menu commands with normalized text
        commandIds.push(GM_registerMenuCommand(`${config.paused ? "▶️ Aktif Et" : "⏸️ Durdur"}`, () => {
            const newState = !config.paused;
            setConfigAndReload(KEY_PAUSED, newState, `Filtre ${newState ? 'durduruldu' : 'aktif edildi'}. Yenileniyor...`);
        }));

        commandIds.push(GM_registerMenuCommand(`Mod: ${config.filterMode === 'hide' ? 'Gizle' : 'Daralt'} (Değiştir)`, () => {
            const newMode = config.filterMode === 'hide' ? 'collapse' : 'hide';
            setConfigAndReload(KEY_MODE, newMode, `Mod "${newMode === 'hide' ? 'Gizle' : 'Daralt'}" yapıldı. Yenileniyor...`);
        }));

        commandIds.push(GM_registerMenuCommand(`Uyarılar: ${config.showWarning ? "🚫 Gizle" : "⚠️ Göster"}`, () => {
            const newState = !config.showWarning;
            setConfigAndReload(KEY_SHOW_WARNING, newState, `Uyarılar ${newState ? 'gösterilecek' : 'gizlenecek'}. Yenileniyor...`);
        }));

        commandIds.push(GM_registerMenuCommand("🔄 Listeyi Şimdi Güncelle", async () => {
            showFeedback("Güncelleme", "Liste alınıyor...", { silent: true });
            logger.log("Manuel güncelleme başlatıldı...");
            try {
                const updated = await syncList(true);
                if (updated) {
                    showFeedback("Güncelleme Başarılı", `Liste güncellendi (${filteredListSize} yazar). Yenileniyor...`);
                    location.reload();
                } else {
                     logger.warn("Manuel güncelleme: Liste değişmedi/hata.");
                     showFeedback("Güncelleme Sonucu", "Liste güncellenemedi/değişmedi. Konsolu kontrol edin.", { isError: (filteredListSize === 0 && !config.listRaw) });
                }
            } catch (err) {
                 logger.error("Manuel güncelleme hatası:", err);
                 showFeedback("Güncelleme Hatası", `Hata: ${err.message}`, { isError: true });
            }
        }));

        commandIds.push(GM_registerMenuCommand(`📊 İstatistikler`, async () => {
            const total = await GM_getValue(KEY_TOTAL_FILTERED, config.totalFiltered);
            const lastUpdateDate = config.lastUpdate ? new Date(config.lastUpdate).toLocaleString("tr-TR") : "Hiç";
            const statsText = `Toplam Filtrelenen: ${total}\n`
                            + `Liste Boyutu: ${filteredListSize}\n`
                            + `Son Güncelleme: ${lastUpdateDate}\n`
                            + `Durum: ${config.paused ? 'Duraklatıldı' : 'Aktif'}\n`
                            + `Mod: ${config.filterMode === 'hide' ? 'Gizle' : 'Daralt'}\n`
                            + `Uyarılar: ${config.showWarning ? 'Açık' : 'Kapalı'}`;
            showFeedback("Filtre İstatistikleri", statsText);
        }));


        commandIds.push(GM_registerMenuCommand(`🗑️ Ayarları ve Önbelleği Sıfırla`, async () => {
             if (confirm(`[${SCRIPT_NAME}] Emin misiniz?\n\nTüm ayarları ve yerel filtre listesi önbelleğini sıfırlayacak.\n\nSayfa yenilendikten sonra liste tekrar indirilecek.`)) {
                 logger.warn("Kullanıcı sıfırlamayı onayladı.");
                 try {
                     const keysToDelete = [
                         KEY_PAUSED, KEY_MODE, KEY_SHOW_WARNING, KEY_LIST_RAW,
                         KEY_LAST_UPDATE, KEY_TOTAL_FILTERED
                     ];
                     const results = await Promise.allSettled(keysToDelete.map(key => GM_deleteValue(key)));

                     results.forEach((result, index) => {
                         if (result.status === 'rejected') {
                             logger.error(`'${keysToDelete[index]}' silinirken hata:`, result.reason);
                         }
                     });

                     filteredAuthorsSet = new Set();
                     filteredListSize = 0;
                     config = { paused: false, filterMode: 'collapse', showWarning: true, listRaw: '', lastUpdate: 0, totalFiltered: 0 };

                     showFeedback("Sıfırlandı", "Ayarlar ve önbellek temizlendi. Yenileniyor...");
                     location.reload();
                 } catch (err) {
                      logger.error("Sıfırlama hatası:", err);
                      showFeedback("Sıfırlama Hatası", `Hata: ${err.message}`, { isError: true });
                 }
             } else {
                 logger.log("Kullanıcı sıfırlamayı iptal etti.");
                 showFeedback("İptal Edildi", "Sıfırlama iptal edildi.", { silent: true });
             }
        }));

        logger.debug(`${commandIds.length} menü komutu kaydedildi.`);
    }

    try {
         initialize().catch(err => {
            logger.error("Başlatma hatası (initialize promise):", err);
            showFeedback("Başlatma Başarısız", `Hata:\n${err.message}\nScript çalışmayabilir.`, { isError: true });
        });
    } catch (err) {
        logger.error("Başlatma senkron hatası:", err);
        showFeedback("Başlatma Hatası", `Senkron hata:\n${err.message}\nScript çalışamayabilir.`, { isError: true });
    }

})();