您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
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 }); } })();