您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
aktroll entry'lerini otomatik olarak gizler/daraltır, yazarları işaretlemenizi sağlar
当前为
// ==UserScript== // @name anti-troll // @version 1.0 // @description aktroll entry'lerini otomatik olarak gizler/daraltır, yazarları işaretlemenizi sağlar // @match *://eksisozluk.com/* // @exclude *://eksisozluk.com/biri/* // @exclude *://eksisozluk.com/mesaj/* // @exclude *://eksisozluk.com/ayarlar/* // @exclude *://eksisozluk.com/hesap/* // @connect raw.githubusercontent.com // @grant GM_setValue // @grant GM_getValue // @grant GM_xmlhttpRequest // @grant GM_registerMenuCommand // @grant GM_addStyle // @namespace https://greasyfork.org/ // @license MIT // @run-at document-idle // ==/UserScript== (async () => { 'use strict'; // --- Sabitler --- const SCRIPT_NAME = "Anti-Troll Filtreleyici"; const TROLL_LIST_URL = "https://raw.githubusercontent.com/unless7146/stardust3903/main/173732994.txt"; const UPDATE_INTERVAL_MS = 24 * 60 * 60 * 1000; // 24 saat const NETWORK_TIMEOUT_MS = 20000; // 20 saniye const LOG_PREFIX = `[${SCRIPT_NAME}]`; const AUTHOR_MARKINGS_KEY = "authorMarkings_v2"; // Anahtar adı versiyonlandı (isteğe bağlı) const DEBOUNCE_DELAY_MS = 250; // Gecikme (ms) // --- CSS Stilleri (Net ve Sert Dil Teması) --- GM_addStyle(` /* --- Troll Engelleyici Stilleri --- */ .anti-troll-topic-warning { background-color: #f8d7da; border: 1px solid #f5c2c7; border-left: 3px solid #dc3545; border-radius: 3px; padding: 5px 10px; margin-top: 5px; margin-left: 5px; font-size: 0.9em; color: #842029; display: inline-block; vertical-align: middle; cursor: default; box-sizing: border-box; font-weight: bold; } .anti-troll-collapsed {} /* Daraltılmış entry için ana sınıf (şu an boş, stil gerekirse eklenebilir) */ .anti-troll-collapse-placeholder { min-height: 25px; background-color: rgba(128, 128, 128, 0.1); border-left: 3px solid #dc3545; padding: 6px 0 6px 12px; margin-top: 5px; border-radius: 0 4px 4px 0; font-style: normal; color: #495057; position: relative; display: flex; align-items: center; flex-wrap: wrap; } .anti-troll-collapse-placeholder .anti-troll-collapse-icon { margin-right: 6px; opacity: 0.9; font-style: normal; display: inline-block; } .anti-troll-collapse-placeholder .anti-troll-collapse-text { font-style: normal; margin-right: 10px; flex-grow: 1; display: inline-block; font-size: 0.95em; font-weight: 500; } .anti-troll-show-link { font-style: normal; flex-shrink: 0; } .anti-troll-show-link a { cursor: pointer; text-decoration: none; color: #dc3545; opacity: 1.0; transition: color 0.15s ease-in-out, text-decoration 0.15s ease-in-out, background-color 0.15s ease-in-out; font-size: 0.9em; margin-left: auto; padding: 2px 5px; border-radius: 3px; font-weight: bold; } .anti-troll-show-link a::before { content: "» "; opacity: 0.7; } .anti-troll-show-link a:hover { color: #a71d2a; text-decoration: underline; background-color: rgba(220, 53, 69, 0.1); } .anti-troll-hidden { display: none !important; } .anti-troll-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: default; display: inline-block; font-style: normal; font-weight: bold; } /* --- Yazar İşaretleme Buton Stilleri --- */ li[data-author] footer:not(:hover) .author-actions-container { opacity: 0; visibility: hidden; transition: opacity 0.2s ease-in-out, visibility 0s linear 0.2s; } li[data-author] footer:hover .author-actions-container { opacity: 1; visibility: visible; transition: opacity 0.2s ease-in-out; } .author-actions-container { display: inline-block; margin-left: 8px; vertical-align: middle; font-size: 0.9em; } .mark-button { appearance: none; -webkit-appearance: none; background: none; border: none; font-family: inherit; cursor: pointer; background-color: #f8f9fa; border: 1px solid #dee2e6; color: #495057; border-radius: 4px; padding: 2px 7px; margin-left: 5px; font-size: 0.85em; line-height: 1.3; user-select: none; box-sizing: border-box; vertical-align: middle; font-weight: 500; text-align: center; transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; } .mark-button:hover { background-color: #e9ecef; border-color: #ced4da; color: #212529; box-shadow: 0 1px 1px rgba(0,0,0,0.05); } .mark-button:focus { outline: none; border-color: #86b7fe; box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.25); } .mark-button:focus:not(:focus-visible) { box-shadow: none; border-color: #ced4da; } .mark-button:focus-visible { border-color: #86b7fe; box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.25); } .mark-button:active { background-color: #dee2e6; border-color: #adb5bd; box-shadow: inset 0 2px 3px rgba(0,0,0,0.1); transform: translateY(1px); } .mark-button.marked-success { background-color: #d1e7dd; color: #0f5132; border-color: #badbcc; box-shadow: none; } .mark-button.marked-error { background-color: #f8d7da; color: #842029; border-color: #f5c2c7; box-shadow: none; } .mark-button:disabled { opacity: 0.65; cursor: not-allowed; box-shadow: none; background-color: #f8f9fa; border-color: #dee2e6; transform: none; } `); // --- Yardımcı Fonksiyonlar --- 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), // Debug logları için }; const debounce = (func, wait) => { let timeout; return function executedFunction(...args) { const later = () => { clearTimeout(timeout); func.apply(this, args); }; clearTimeout(timeout); timeout = setTimeout(later, wait); }; }; // --- GM API Kontrolü --- const requiredGmFunctions = ['GM_getValue', 'GM_setValue', 'GM_xmlhttpRequest', 'GM_registerMenuCommand', 'GM_addStyle']; const missingGmFunctions = requiredGmFunctions.filter(fn => typeof window[fn] !== 'function'); if (missingGmFunctions.length > 0) { const errorMsg = `KRİTİK HATA: Gerekli Tampermonkey API fonksiyonları eksik: ${missingGmFunctions.join(', ')}! Script ÇALIŞMAYACAK. Eklentiyi (Tampermonkey/Greasemonkey vb.) ve script izinlerini KONTROL EDİN!`; logger.error(errorMsg); alert(`${SCRIPT_NAME} - KRİTİK HATA:\n${errorMsg}`); return; // Scripti durdur } // --- Geri Bildirim Fonksiyonu (Daha Net Başlıklar) --- function showFeedback(title, text, options = {}) { const { isError = false, silent = false } = options; const prefix = isError ? "HATA" : "BİLGİ"; const finalTitle = `[${SCRIPT_NAME}] ${prefix}: ${title}`; if (isError) { logger.error(title, text); } else { logger.log(title, text); } if (!silent) { // Alert yerine daha modern bir bildirim sistemi düşünülebilir (örn: GM_notification), // ancak alert basit ve her yerde çalışır. alert(`${finalTitle}\n\n${text}`); } } // --- Yapılandırma ve Durum --- let config = {}; // Ayarlar (paused, trollMode, vb.) let trollList = []; // Troll kullanıcı adlarının listesi let trollSet = new Set(); // Hızlı kontrol için Set versiyonu let trollListSize = 0; // Mevcut liste boyutu let currentBlockedCount = 0; // Mevcut sayfada işlenen ve engellenen/daraltılan entry sayısı let topicWarningElement = null; // Başlık uyarısı DOM elementi let authorMarkings = {}; // Yazar işaretleme verileri (bellekte tutulacak) // --- Yapılandırmayı Yükle --- async function loadConfiguration() { logger.debug("Yapılandırma yükleniyor..."); try { // GM_getValue ile tüm ayarları paralel olarak çek const [ paused, trollMode, showTrollTopicWarning, trollListRaw, trollListLastUpdate, totalBlocked, loadedMarkings, storedParsedTrollList ] = await Promise.all([ GM_getValue("paused", false), // Filtre duraklatılmış mı? GM_getValue("trollMode", "hide"), // 'hide' veya 'collapse' GM_getValue("showTrollTopicWarning", true), // Başlık uyarısı gösterilsin mi? GM_getValue("trollListRaw", ""), // Ham liste metni GM_getValue("trollListLastUpdate", 0), // Listenin son güncellenme zamanı GM_getValue("totalBlocked", 0), // Toplam engellenen entry sayısı GM_getValue(AUTHOR_MARKINGS_KEY, {}), // Yazar işaretlemeleri GM_getValue("trollListParsed", []) // Ayrıştırılmış liste (önbellek) ]); config = { paused, trollMode, showTrollTopicWarning, trollListRaw, trollListLastUpdate, totalBlocked }; authorMarkings = loadedMarkings || {}; // Yüklenen işaretlemeleri ata // Troll listesini işle if (trollListRaw) { // Önbellekteki ayrıştırılmış liste, ham veriyle tutarlıysa onu kullan const potentialParsedList = parseTrollList(trollListRaw); if (storedParsedTrollList && storedParsedTrollList.length === potentialParsedList.length && storedParsedTrollList.join(',') === potentialParsedList.join(',')) { trollList = storedParsedTrollList; logger.debug("Önbellekten ayrıştırılmış troll listesi kullanıldı."); } else { // Değilse, ham veriyi tekrar ayrıştır trollList = potentialParsedList; // Yeni ayrıştırılmış listeyi kaydet (hata olursa yakala) GM_setValue("trollListParsed", trollList) .catch(e => logger.error("Ayrıştırılmış troll listesi kaydedilemedi:", e)); logger.debug("Ham troll listesi ayrıştırıldı."); } } else { trollList = []; // Ham veri yoksa liste boş logger.debug("Kaydedilmiş ham troll listesi bulunamadı."); } trollSet = new Set(trollList); // Set oluştur trollListSize = trollSet.size; // Boyutu kaydet logger.log(`Yapılandırma yüklendi. Filtre: ${config.paused ? 'DURDURULDU' : 'AKTİF'}, Mod: ${config.trollMode}, Liste Boyutu (bellek): ${trollListSize}, İşaretleme Anahtarı: ${AUTHOR_MARKINGS_KEY}`); } catch (err) { logger.error("Yapılandırma YÜKLENEMEDİ:", err); // Hata durumunda güvenli varsayılanlara dön config = { paused: false, trollMode: 'hide', showTrollTopicWarning: true, trollListRaw: '', trollListLastUpdate: 0, totalBlocked: 0 }; trollList = []; trollSet = new Set(); trollListSize = 0; authorMarkings = {}; showFeedback( "Yapılandırma Başarısız", "Ayarlar yüklenemedi! Varsayılanlar kullanılıyor. Script düzgün çalışmayabilir veya bazı veriler sıfırlanmış olabilir.", { isError: true } ); } } // --- Çekirdek Fonksiyonlar --- // Troll listesini sunucudan çeker const fetchTrollList = () => { logger.log("Liste sunucudan çekiliyor...", TROLL_LIST_URL); return new Promise((resolve, reject) => { try { GM_xmlhttpRequest({ method: "GET", url: TROLL_LIST_URL, timeout: NETWORK_TIMEOUT_MS, responseType: 'text', headers: { 'Cache-Control': 'no-cache' }, // Her zaman taze veri iste onload: function(response) { if (response.status >= 200 && response.status < 300 && typeof response.responseText === 'string') { logger.log(`Liste başarıyla çekildi (HTTP ${response.status}). Boyut: ${response.responseText.length} bytes.`); resolve(response.responseText); } else { logger.error(`Liste ALINAMADI: Sunucu hatası ${response.status} (${response.statusText})`, response); reject(new Error(`Liste ALINAMADI: Sunucu hatası ${response.status} (${response.statusText})`)); } }, onerror: function(response) { logger.error(`AĞ HATASI: Liste alınamadı (${response.statusText || 'Bilinmeyen sorun'})`, response); reject(new Error(`AĞ HATASI: Liste alınamadı (${response.statusText || 'Bilinmeyen sorun'})`)); }, ontimeout: function() { logger.error(`ZAMAN AŞIMI (${NETWORK_TIMEOUT_MS / 1000}s)! Liste alınamadı.`); reject(new Error(`ZAMAN AŞIMI (${NETWORK_TIMEOUT_MS / 1000}s)! Liste alınamadı.`)); } }); } catch (err) { logger.error("GM_xmlhttpRequest BAŞLATILAMADI:", err); reject(new Error("Kritik: GM_xmlhttpRequest başlatılamadı. Tarayıcı eklentisiyle ilgili bir sorun olabilir.")); } }); }; // Ham metni troll listesine ayrıştırır const parseTrollList = (rawText) => { if (!rawText || typeof rawText !== 'string') { logger.warn("Ayrıştırılacak troll listesi verisi yok veya geçersiz format."); return []; } try { // Satırlara böl, boşlukları temizle, boş satırları ve yorumları (# ile başlayan) filtrele const lines = rawText.split(/[\r\n]+/) .map(line => line.trim()) .filter(line => line && !line.startsWith("#")); // Küçük harfe çevirerek tutarlılık sağla (isteğe bağlı ama önerilir) // return lines.map(line => line.toLowerCase()); return lines; // Orijinal haliyle bırakmak istenirse } catch (err) { logger.error("Troll listesi AYRIŞTIRILAMADI:", err); return []; // Hata durumunda boş liste dön } }; // Troll listesini sunucuyla senkronize eder const syncTrollList = async (forceUpdate = false) => { logger.log(`Liste güncellemesi ${forceUpdate ? 'ZORLANIYOR' : 'kontrol ediliyor'}...`); let newRawText; try { newRawText = await fetchTrollList(); } catch (err) { logger.error("Liste ÇEKME hatası:", err.message); // Kullanıcıya sadece zorunlu güncelleme veya ilk yüklemede hata göster if (forceUpdate || trollListSize === 0) { showFeedback("Liste Güncelleme Başarısız", `Liste sunucudan alınamadı.\n${err.message}\n\nMevcut liste (varsa) kullanılmaya devam edilecek.`, { isError: true }); } return false; // Güncelleme başarısız } try { // Veri değişti mi veya güncelleme zorlandı mı? if (forceUpdate || config.trollListRaw !== newRawText) { if (!forceUpdate) logger.log("Liste içeriği değişmiş, güncelleme gerekli."); else logger.log("Zorunlu güncelleme tetiklendi."); const newList = parseTrollList(newRawText); // Güvenlik kontrolü: Yeni liste (parse sonrası) boşsa ama çekilen veri boş değilse, // parse hatası olabilir, eski listeyi koru. if (trollListSize > 0 && newList.length === 0 && newRawText.length > 0) { logger.warn("Yeni çekilen liste verisi var ama ayrıştırma sonucu boş liste döndü! Ayrıştırma hatası olabilir. Eski liste KORUNUYOR."); // Zaman damgasını yine de güncelle (kontrol yapıldığını belirtmek için) config.trollListLastUpdate = Date.now(); try { await GM_setValue("trollListLastUpdate", config.trollListLastUpdate); } catch (e) { logger.error("Son kontrol zamanı kaydedilemedi (ayrıştırma hatası sonrası):", e); } return false; // Gerçek bir güncelleme olmadı } // Yeni listeyi ve state'i güncelle const oldSize = trollListSize; trollList = newList; trollSet = new Set(trollList); trollListSize = trollSet.size; config.trollListRaw = newRawText; // Ham veriyi de güncelle config.trollListLastUpdate = Date.now(); // Zaman damgasını güncelle logger.log(`Liste ayrıştırıldı ve güncellendi. Eski boyut: ${oldSize}, Yeni boyut: ${trollListSize}`); // Güncellenmiş verileri kaydetmeyi dene try { await Promise.all([ GM_setValue("trollListParsed", trollList), // Ayrıştırılmış listeyi kaydet GM_setValue("trollListRaw", config.trollListRaw), GM_setValue("trollListLastUpdate", config.trollListLastUpdate) ]); logger.log("Güncellenmiş liste verileri başarıyla kaydedildi."); } catch (saveErr) { logger.error("Güncellenmiş liste bilgileri KAYDEDİLEMEDİ:", saveErr); showFeedback("Depolama Hatası", "Liste güncellendi ancak bazı veriler (ham liste, zaman damgası) KAYDEDİLEMEDİ. Script çalışmaya devam edecek ama sonraki açılışta tekrar güncelleme gerekebilir.", { isError: true }); // Kaydetme hatası olsa bile state güncel, devam edebiliriz } return true; // Güncelleme yapıldı } else { // İçerik aynı logger.log("Liste zaten güncel."); config.trollListLastUpdate = Date.now(); // Sadece kontrol zamanını güncelle try { await GM_setValue("trollListLastUpdate", config.trollListLastUpdate); logger.debug("Son kontrol zamanı güncellendi ve kaydedildi."); } catch (err) { logger.error("Son kontrol zamanı kaydedilemedi:", err); } return false; // Güncelleme yapılmadı (zaten günceldi) } } catch (err) { logger.error("Liste İŞLEME hatası (Ayrıştırma/Durum Güncelleme):", err); showFeedback("Liste İşleme Başarısız", `Liste başarıyla alındı ancak işlenirken KRİTİK bir hata oluştu.\n${err.message}\n\nScript eski listeyle devam etmeye çalışacak (varsa).`, { isError: true }); return false; // İşleme hatası } }; // Entry içeriğini daraltır veya yer tutucu gösterir (Sert Dil Teması) function collapseContent(entry) { const contentElement = entry.querySelector(".content"); if (!contentElement) { logger.warn("Daraltılacak içerik elementi (.content) bulunamadı, işlem atlanıyor:", entry.dataset.entryId || entry); return; } // Zaten daraltılmışsa tekrar işlem yapma if (entry.classList.contains('anti-troll-collapsed')) { logger.debug(`Entry ${entry.dataset.entryId || 'ID Yok'} zaten daraltılmış.`); return; } const author = entry.getAttribute("data-author") || "BİLİNMEYEN YAZAR"; const originalContentDisplay = contentElement.style.display; // Orijinal display değerini sakla contentElement.style.display = 'none'; // İçeriği hemen gizle // Placeholder'ı bul veya oluştur let placeholder = entry.querySelector('.anti-troll-collapse-placeholder'); if (!placeholder) { placeholder = document.createElement('div'); placeholder.className = 'anti-troll-collapse-placeholder'; placeholder.innerHTML = ` <span class="anti-troll-collapse-icon" title="Yazar '${author}' Listede.">⛔</span> <span class="anti-troll-collapse-text">"${author}" engellendi (Liste). İçerik gizlendi.</span> <div class="anti-troll-show-link"> <a href="#" role="button">Yine de göster</a> </div> `; // Placeholder'ı içeriğin yerine ekle try { // contentElement'in hemen sonrasına ekle contentElement.parentNode.insertBefore(placeholder, contentElement.nextSibling); } catch (err) { logger.error(`Placeholder eklenemedi! (Entry ID: ${entry.dataset.entryId || 'yok'}, Yazar: ${author})`, err, entry); contentElement.style.display = originalContentDisplay || ''; // Hata olursa gizlemeyi geri al return; // Placeholder eklenemezse işlemi durdur } // "Yine de göster" linkine olay dinleyici ekle const showLink = placeholder.querySelector('.anti-troll-show-link a'); if (showLink) { showLink.addEventListener("click", (e) => { e.preventDefault(); e.stopPropagation(); // Diğer tıklama olaylarını engelle if (contentElement) { contentElement.style.display = originalContentDisplay || ''; // Orijinal durumu geri yükle } else { logger.warn("Geri yüklenecek '.content' elementi bulunamadı!", entry.dataset.entryId); } // Placeholder'ı kaldır placeholder.remove(); entry.classList.remove('anti-troll-collapsed'); // Daraltma sınıfını kaldır // Açıldığına dair uyarı ekle const footer = entry.querySelector('footer'); if (footer && !footer.querySelector('.anti-troll-opened-warning')) { const warningSpan = document.createElement('span'); warningSpan.className = 'anti-troll-opened-warning'; warningSpan.textContent = '⚠️ Engellendi'; warningSpan.title = `Bu içerik Liste nedeniyle gizlenmişti, tarafınızca ZORLA açıldı.`; footer.appendChild(warningSpan); } else if (!footer) { logger.warn("Footer bulunamadı, 'zorla açıldı' uyarısı eklenemedi:", entry.dataset.entryId); } }, { once: true }); // Sadece bir kere çalışsın } else { logger.error("Placeholder içindeki 'Yine de göster' linki bulunamadı!", placeholder); } } else { // Mevcut placeholder varsa görünür yap (nadiren gerekli olmalı) placeholder.style.display = ''; } entry.classList.add('anti-troll-collapsed'); // Entry'yi daraltılmış olarak işaretle logger.debug(`Entry daraltıldı: ${entry.dataset.entryId || 'ID Yok'} (Yazar: ${author})`); } // Yazar işaretlemesini kaydeder const recordMarking = async (author, category, buttonElement) => { if (!author || !category) { logger.error("İŞARETLEME BAŞARISIZ: Yazar veya kategori bilgisi EKSİK."); if (buttonElement) flashButtonState(buttonElement, 'marked-error', "HATA!"); return; } logger.log(`Yazar '${author}' Kategori '${category}' olarak işaretleniyor...`); // Bellekteki state'i güncelle authorMarkings[category] = authorMarkings[category] || {}; authorMarkings[category][author] = (authorMarkings[category][author] || 0) + 1; // Değişikliği GM deposuna kaydetmeyi dene try { await GM_setValue(AUTHOR_MARKINGS_KEY, authorMarkings); logger.log(`Yazar '${author}' Kategori '${category}' olarak İŞARETLENDİ ve kaydedildi.`); if (buttonElement) flashButtonState(buttonElement, 'marked-success', "✓"); // Başarı işareti } catch (err) { logger.error(`İŞARETLEME KAYDI BAŞARISIZ (GM_setValue hatası - Yazar: ${author}, Kategori: ${category}):`, err); // Hatayı kullanıcıya bildir showFeedback( "Depolama Hatası", `"${author}" için işaretleme KAYDEDİLEMEDİ.\n${err.message}\n\nİşaretleme bellekte yapıldı ancak kalıcı olarak kaydedilemedi. Sayfa yenilenince kaybolabilir.`, { isError: true } ); // Buton durumu hata olarak ayarla if (buttonElement) flashButtonState(buttonElement, 'marked-error', "HATA!"); // Başarısız kaydetme sonrası bellekteki değişikliği geri almak isteyebiliriz, // ancak bu kullanıcı için kafa karıştırıcı olabilir. Şimdilik bellekte bırakalım. // authorMarkings[category][author] = (authorMarkings[category][author] || 1) - 1; // Geri alma (opsiyonel) // if (authorMarkings[category][author] <= 0) delete authorMarkings[category][author]; } }; // Buton durumunu geçici olarak değiştirir (örn: başarı/hata) const flashButtonState = (button, className, tempText = null) => { if (!button) return; const originalText = button.textContent; button.classList.add(className); button.disabled = true; // İşlem sırasında tekrar tıklanmasın if (tempText) button.textContent = tempText; setTimeout(() => { button.classList.remove(className); button.disabled = false; if (tempText) button.textContent = originalText; // Orijinal metni geri yükle }, 1200); // 1.2 saniye görünür kalsın }; // Yazar işaretleme butonlarını oluşturur (Sert Dil Teması) const createMarkButtons = (author) => { const container = document.createElement('span'); container.className = 'author-actions-container'; const categories = [ { id: 'sycophancy', label: 'Yalama', title: 'YALAMA olarak damgala' }, // Büyük harf vurgu { id: 'slander', label: 'Karalama', title: 'KARALAMA olarak damgala' }, { id: 'provocation', label: 'Kışkırtma', title: 'KIŞKIRTMA olarak damgala' } ]; categories.forEach(cat => { const button = document.createElement('button'); button.className = 'mark-button'; button.textContent = cat.label; button.title = `Yazar: ${author} - ${cat.title}`; // Net açıklama button.dataset.author = author; button.dataset.category = cat.id; button.setAttribute('role', 'button'); button.addEventListener('click', (e) => { e.preventDefault(); // Link davranışını engelle (gerekirse) e.stopPropagation(); // Footer'daki diğer olayları engelle recordMarking(author, cat.id, button); }); container.appendChild(button); }); return container; }; // Tek bir entry'yi işler: işaretleme butonu ekler ve filtre uygular function enhanceEntry(entry) { // Geçersiz veya zaten işlenmiş entry'leri atla if (!entry || entry.nodeType !== Node.ELEMENT_NODE || !entry.matches('li[data-author]')) return; if (entry.dataset.antiTrollEnhanced === 'true') return; // Zaten işlenmiş const author = entry.getAttribute("data-author"); const entryId = entry.getAttribute("data-id") || entry.getAttribute("data-entry-id") || 'ID Yok'; // Entry ID'sini al // Yazar bilgisi yoksa, işaretle ve çık (hata durumu değil) if (!author) { logger.warn(`Entry'de (ID: ${entryId}) 'data-author' özniteliği YOK, işlenemiyor.`); entry.dataset.antiTrollEnhanced = 'true'; // Yine de işlenmiş say return; } // --- İşaretleme Butonlarını Ekle --- const footer = entry.querySelector('footer'); if (footer) { // Eğer butonlar zaten eklenmemişse ekle if (!footer.querySelector('.author-actions-container')) { try { const buttonsContainer = createMarkButtons(author); footer.appendChild(buttonsContainer); } catch (err) { logger.error(`Yazar '${author}' için işaretleme butonları EKLENEMEDİ (Entry ID: ${entryId}):`, err); // Buton eklenemese bile filtrelemeye devam et } } } else { logger.debug(`Entry (ID: ${entryId}, Yazar: ${author}) için footer bulunamadı, işaretleme butonları eklenemiyor.`); } // --- Troll Filtresini Uygula --- let actionTaken = 'none'; // Başlangıçta bir işlem yapılmadı try { // Filtre aktifse ve yazar troll listesindeyse if (!config.paused && trollSet.has(author)) { currentBlockedCount++; // Bu sayfada engellenen sayısını artır if (config.trollMode === "hide") { entry.classList.add('anti-troll-hidden'); // Tamamen gizle actionTaken = 'hide'; logger.debug(`Entry gizlendi: ${entryId} (Yazar: ${author})`); } else { // 'collapse' modu collapseContent(entry); // Daraltma fonksiyonunu çağır // collapseContent başarılı olursa 'anti-troll-collapsed' sınıfını ekler if (entry.classList.contains('anti-troll-collapsed')) { actionTaken = 'collapse'; } else { // collapseContent başarısız olursa (örn. .content yoksa) actionTaken = 'collapse_failed'; logger.warn(`Entry ${entryId} (Yazar: ${author}) için daraltma işlemi BAŞARISIZ OLDU (collapseContent iç hatası?).`); } } } else if (config.paused) { actionTaken = 'paused'; // Filtre duraklatıldığı için işlem yapılmadı } else if (!trollSet.has(author)) { actionTaken = 'not_in_list'; // Yazar listede olmadığı için işlem yapılmadı } } catch (err) { logger.error(`Entry ${entryId} İŞLENEMEDİ (Filtreleme/Daraltma Hatası - Yazar: ${author}):`, err); actionTaken = 'error'; // İşlem sırasında hata oluştu } // Entry'nin durumunu ve işlendiğini işaretle entry.dataset.antiTrollTrollAction = actionTaken; entry.dataset.antiTrollEnhanced = 'true'; } // Konu başlığına troll uyarısı ekler/günceller (Sert Dil Teması) const updateTopicWarning = () => { try { // Mevcut uyarıyı kaldır if (topicWarningElement && topicWarningElement.parentNode) { topicWarningElement.remove(); topicWarningElement = null; } // Uyarıyı gösterme koşulları if (!config.showTrollTopicWarning || config.paused) return; // Ayar kapalıysa veya filtre duraklatılmışsa gösterme const entryList = document.querySelector("#entry-item-list"); if (!entryList) return; // Entry listesi yoksa çık const isFirstPage = !window.location.search.includes('p=') || window.location.search.includes('p=1'); if (!isFirstPage) return; // Sadece ilk sayfada göster const firstEntry = entryList.querySelector("li[data-author]"); if (!firstEntry) return; // İlk entry yoksa çık const firstAuthor = firstEntry.getAttribute("data-author"); if (!firstAuthor) return; // İlk yazar yoksa çık // Bu sayfada işlenmiş ve engellenmiş/daraltılmış trolleri say // (enhanceEntry tarafından eklenen öznitelikleri kullan) const processedTrollsOnPage = entryList.querySelectorAll("li[data-author][data-anti-troll-troll-action='hide'], li[data-author][data-anti-troll-troll-action='collapse']").length; const isFirstAuthorTroll = trollSet.has(firstAuthor); // Uyarıyı göster: İlk yazar troll ise VEYA sayfada en az 3 troll engellenmişse const showWarning = isFirstAuthorTroll || processedTrollsOnPage >= 3; if (showWarning) { const subTitleMenu = document.querySelector(".sub-title-menu"); if (!subTitleMenu) { logger.warn("Başlık menüsü (.sub-title-menu) BULUNAMADI, konu uyarısı EKLENEMİYOR."); return; } topicWarningElement = document.createElement("div"); topicWarningElement.className = "anti-troll-topic-warning"; topicWarningElement.textContent = "⚠️ TROLL BAŞLIK UYARISI"; // Sert uyarı metni // Uyarı nedenini (title) oluştur let titleText = "NEDEN: "; const otherTrollsCount = processedTrollsOnPage - (isFirstAuthorTroll ? 1 : 0); // İlk yazar dışındaki troller if (isFirstAuthorTroll && otherTrollsCount <= 0) { titleText += `Başlığı açan "${firstAuthor}" Listede.`; } else if (isFirstAuthorTroll && otherTrollsCount > 0) { titleText += `Başlığı açan "${firstAuthor}" VE sayfadaki ${otherTrollsCount} diğer yazar Listede.`; } else { // İlk yazar troll değil ama sayfada >= 3 troll var titleText += `Sayfadaki ${processedTrollsOnPage} yazar Listede.`; } topicWarningElement.title = titleText; subTitleMenu.appendChild(topicWarningElement); logger.log("Troll başlık uyarısı eklendi.", titleText); } else { logger.debug("Troll başlık uyarısı için koşullar sağlanmadı."); } } catch (err) { logger.error("Konu başlığı uyarısı güncellenirken HATA:", err); // Hata durumunda kalıntı uyarıyı temizle if (topicWarningElement && topicWarningElement.parentNode) { topicWarningElement.remove(); topicWarningElement = null; } } }; // Görünürdeki (henüz işlenmemiş) entry'leri işler (debounce ile) const processVisibleEntries = debounce(() => { logger.debug("Görünürdeki entry'ler işleniyor..."); currentBlockedCount = 0; // Bu parti için engellenen sayısını sıfırla const selector = '#entry-item-list > li[data-author]:not([data-anti-troll-enhanced="true"])'; // Henüz işlenmemiş ve yazarı olan entry'ler let entriesToProcess; try { entriesToProcess = document.querySelectorAll(selector); } catch (err) { logger.error("İşlenecek entry'ler seçilirken DOM hatası:", err); return; // Seçim başarısızsa devam etme } if (entriesToProcess.length > 0) { logger.log(`${entriesToProcess.length} yeni entry işlenecek.`); entriesToProcess.forEach(entry => { try { enhanceEntry(entry); // Her bir entry'yi işle } catch (err) { // enhanceEntry içindeki hatalar normalde yakalanır, bu beklenmedik durumlar için const entryId = entry ? (entry.dataset.id || entry.dataset.entryId || 'bilinmeyen') : 'bilinmeyen'; const author = entry ? entry.dataset.author : 'bilinmeyen'; logger.error(`Entry ${entryId} (Yazar: ${author}) işlenirken BEKLENMEDİK HATA (processVisibleEntries döngüsü):`, err); // Hatalı entry'yi tekrar işlememek için işaretle if (entry && entry.dataset) { entry.dataset.antiTrollEnhanced = 'true'; // İşlenmiş say entry.dataset.antiTrollTrollAction = 'processing_error'; // Hata durumu } } }); // Toplu işlem sonrası başlık uyarısını güncelle updateTopicWarning(); // Toplam engellenen sayısını güncelle (eğer bu partide engelleme olduysa) if (currentBlockedCount > 0) { const previousTotal = config.totalBlocked || 0; config.totalBlocked = previousTotal + currentBlockedCount; logger.log(`Bu sayfada ${currentBlockedCount} entry engellendi/daraltıldı. Toplam: ${config.totalBlocked}`); // Kaydetmeyi dene (arka planda, hatayı yakala) GM_setValue("totalBlocked", config.totalBlocked) .catch(err => logger.error("Toplam engellenen sayısı KAYDEDİLEMEDİ:", err)); } } else { logger.debug("İşlenecek yeni entry bulunamadı."); } }, DEBOUNCE_DELAY_MS); // --- Başlatma ve Yürütme --- async function initialize() { logger.log("Script BAŞLATILIYOR..."); await loadConfiguration(); // Önce ayarları yükle // Filtre duraklatılmamışsa ve liste boşsa veya güncelleme zamanı gelmişse, başlangıçta senkronize et if (!config.paused) { const now = Date.now(); const timeSinceLastUpdate = now - (config.trollListLastUpdate || 0); const needsUpdate = timeSinceLastUpdate > UPDATE_INTERVAL_MS; if (trollListSize === 0 || needsUpdate) { logger.log(`Başlangıç kontrolü: Liste ${trollListSize === 0 ? 'boş' : 'güncelleme zamanı geldi'}. Senkronizasyon denenecek... (Son güncelleme: ${config.trollListLastUpdate ? new Date(config.trollListLastUpdate).toLocaleString() : 'Hiç'})`); // Senkronizasyonu arka planda başlat, UI bloklamasın // İlk yükleme sonrası sayfayı yenilemeye gerek yok, processVisibleEntries halleder. syncTrollList().then(updated => { if (updated) { logger.log("Başlangıçtaki arka plan liste güncellemesi TAMAMLANDI. Yeni liste boyutu: " + trollListSize); // Liste güncellendiyse, DOM'u tekrar işlemek gerekebilir. // processVisibleEntries zaten MutationObserver tarafından tetiklenecek, // ama garanti olsun diye burada da çağırabiliriz. // Ancak bu, çift işlemeye yol açabilir. Şimdilik observer'a güvenelim. // processVisibleEntries(); // Gerekirse aktif edilebilir } else { logger.log("Başlangıçtaki arka plan liste kontrolü tamamlandı (güncelleme yapılmadı veya hata oluştu)."); } }).catch(err => { // syncTrollList içindeki hatalar zaten loglanıyor ve bazen alert veriyor. logger.error("Başlangıç senkronizasyonu sırasında beklenmedik hata:", err); }); } else { logger.log("Liste güncel görünüyor, başlangıçta senkronizasyon atlanıyor."); } // Liste boşsa veya yüklenemediyse bir uyarı ver if (trollSet.size === 0 && config.trollListRaw) { logger.warn("UYARI: Liste verisi var ama ayrıştırılmış liste boş. Parse hatası olabilir!"); } else if (trollSet.size === 0) { logger.warn("UYARI: Troll listesi boş veya yüklenemedi. Filtreleme yapılamayacak!"); } } else { logger.log("Filtre DURDURULMUŞ olarak başlatıldı. Liste güncellenmeyecek ve filtreleme yapılmayacak."); } logger.log(`Entry işleme motoru başlatılıyor. Mod: ${config.paused ? 'DURDURULMUŞ' : config.trollMode}.`); // Başlangıçta görünür olan entry'leri işle try { processVisibleEntries(); } catch (err) { logger.error("İlk entry işleme sırasında KRİTİK HATA:", err); showFeedback("Başlatma Hatası", "Sayfadaki mevcut entry'ler işlenirken bir hata oluştu. Script düzgün çalışmayabilir.", { isError: true }); } // Dinamik olarak eklenen entry'leri izlemek için MutationObserver ayarla const entryListContainer = document.querySelector('#entry-item-list'); if (entryListContainer) { try { const observer = new MutationObserver(mutations => { // Sadece eklenen node'ları kontrol et let needsProcessing = false; for (const mutation of mutations) { if (mutation.type === 'childList' && mutation.addedNodes.length > 0) { for (const node of mutation.addedNodes) { // Eklenen node bir element mi? if (node.nodeType === Node.ELEMENT_NODE) { // Eklenen node'un kendisi veya bir alt elemanı işlenmesi gereken bir entry mi? if ( (node.matches && node.matches('li[data-author]:not([data-anti-troll-enhanced="true"])')) || (node.querySelector && node.querySelector('li[data-author]:not([data-anti-troll-enhanced="true"])')) ) { needsProcessing = true; break; // İşlenecek bir tane bulundu, bu mutation kaydını daha fazla kontrol etmeye gerek yok } } } } if (needsProcessing) break; // İşlenecek bulundu, diğer mutation kayıtlarına bakmaya gerek yok } // Eğer işlenmesi gereken node eklendiyse, debounced fonksiyonu tetikle if (needsProcessing) { logger.debug("MutationObserver: İşlenmesi gereken yeni entry(ler) tespit edildi."); processVisibleEntries(); } }); observer.observe(entryListContainer, { childList: true, // Doğrudan alt eleman ekleme/çıkarma subtree: true // Alt ağaçtaki değişiklikler (örn: entry içeriğinin yüklenmesi) }); logger.log("Entry listesi (#entry-item-list) dinamik değişiklikler için İZLENİYOR."); } catch (err) { logger.error("MutationObserver BAŞLATILAMADI:", err); showFeedback("Kritik Hata", "Sayfa değişiklikleri İZLENEMİYOR! Sonsuz kaydırma veya dinamik olarak yüklenen yeni entry'ler otomatik olarak işlenmeyecek.", { isError: true }); } } else { logger.warn("Entry listesi konteyneri (#entry-item-list) BULUNAMADI! Dinamik olarak yüklenen entry'ler (örn: sonsuz kaydırma) işlenemeyecek."); } // Menü komutlarını kaydet try { registerMenuCommands(); // Menüyü oluştur/güncelle logger.log("Menü komutları başarıyla kaydedildi/güncellendi."); } catch (err) { logger.error("Menü komutları YÜKLENEMEDİ:", err); showFeedback("Hata", "Script ayar menüsü OLUŞTURULAMADI.", { isError: true }); } } // --- Yapılandırma Menüsü (Sert Dil Teması) --- function registerMenuCommands() { // Mevcut komutları temizle (eğer varsa, Tampermonkey bunu otomatik yapabilir ama garanti olsun) // Not: GM_registerMenuCommand tekrar çağrıldığında üzerine yazar, temizlemeye gerek yok. // Ayar değiştirme yardımcısı const handleSettingChange = async (key, value, successMsg, reload = false) => { try { await GM_setValue(key, value); logger.log(`Ayar değiştirildi: ${key} = ${value}`); showFeedback("Ayar Değiştirildi", successMsg, { silent: reload }); // Yenileme varsa alert gösterme if (reload) { logger.log("Sayfa yenileniyor..."); // Kısa bir gecikme ekleyerek alert'in (varsa) kapanmasına izin ver setTimeout(() => location.reload(), reload ? 500 : 0); } else { // Ayar değiştiğinde menüyü hemen güncelle registerMenuCommands(); } } catch (err) { logger.error(`Ayar KAYDEDİLEMEDİ (Anahtar: ${key}):`, err); showFeedback("Depolama Hatası", `Ayar (${key}) KAYDEDİLEMEDİ.\n${err.message}`, { isError: true }); // Başarısız olsa bile menüyü eski haliyle tekrar çizmek için çağırabiliriz registerMenuCommands(); } }; // --- Filtreleme Komutları --- GM_registerMenuCommand(`${config.paused ? "▶️ Filtreyi AKTİF ET" : "⏸️ Filtreyi DURDUR"}`, async () => { config.paused = !config.paused; // Durumu tersine çevir await handleSettingChange("paused", config.paused, `Troll Filtresi ${config.paused ? 'DURDURULDU' : 'AKTİF EDİLDİ'}. Sayfa YENİLENECEK!`, true); }); GM_registerMenuCommand(`Mod: ${config.trollMode === 'hide' ? 'GİZLE' : 'DARALT'} (Değiştir)`, async () => { config.trollMode = config.trollMode === 'hide' ? 'collapse' : 'hide'; // Modu değiştir const modeText = config.trollMode === 'hide' ? 'TAMAMEN GİZLE' : 'DARALT'; await handleSettingChange("trollMode", config.trollMode, `Filtre Modu: "${modeText}" olarak ayarlandı. Sayfa YENİLENECEK!`, true); }); GM_registerMenuCommand(`Başlık Uyarısını ${config.showTrollTopicWarning ? "🚫 GİZLE" : "⚠️ GÖSTER"}`, async () => { config.showTrollTopicWarning = !config.showTrollTopicWarning; // Durumu tersine çevir await handleSettingChange("showTrollTopicWarning", config.showTrollTopicWarning, `Troll Başlık Uyarısı ${config.showTrollTopicWarning ? 'GÖSTERİLECEK' : 'GİZLENECEK'}. Sayfa YENİLENECEK!`, true); }); GM_registerMenuCommand("🔄 Listeyi ŞİMDİ Güncelle", async () => { showFeedback("Liste Güncelleme", "Liste sunucudan çekiliyor ve güncelleniyor...", { silent: true }); // Anında geri bildirim (sessiz) logger.log("Manuel liste güncellemesi başlatıldı..."); const updated = await syncTrollList(true); // Güncellemeyi zorla if (updated) { // Başarılı güncelleme sonrası yeni boyutu almayı dene try { // Güncellenmiş veriyi direkt GM'den okumak yerine state'i kullanabiliriz // const latestSize = (await GM_getValue("trollListParsed", [])).length; const latestSize = trollListSize; // syncTrollList state'i zaten güncelledi showFeedback("Liste Güncelleme Başarılı", `Liste başarıyla güncellendi. Yeni liste boyutu: ${latestSize}. Değişikliklerin etkili olması için sayfa YENİLENECEK!`); location.reload(); } catch (err) { logger.error("Güncelleme sonrası boyut kontrolü hatası:", err); showFeedback("Liste Güncelleme Başarılı", `Liste güncellendi (boyut kontrol edilemedi). Sayfa YENİLENECEK!`); location.reload(); } } else { // syncTrollList false döndü: ya hata oldu ya da zaten günceldi. // syncTrollList içinde hata mesajları zaten gösterilmiş olmalı. // Zaten güncel olma durumunu kontrol edelim. try { // Son çekilen ham veri ile mevcut ham veriyi karşılaştır (bellekteki) // Alternatif: Tekrar fetch etmek yerine, son güncelleme zamanına bakılabilir. // Eğer son güncelleme çok yeniyse, muhtemelen zaten günceldi. const timeSinceLastUpdate = Date.now() - (config.trollListLastUpdate || 0); if (timeSinceLastUpdate < 60000) { // Son 1 dakika içinde güncellendiyse showFeedback("Liste Güncelleme", "Liste ZATEN GÜNCEL görünüyor (kısa süre önce kontrol edildi/güncellendi).", { silent: false }); } else { // Hata mesajı syncTrollList tarafından verildi, burada ek bir şeye gerek yok. // Veya belirsiz bir durum varsa genel bir mesaj verilebilir: showFeedback("Liste Güncelleme", "Liste güncellenemedi veya zaten günceldi. Detaylar için konsolu kontrol edin.", { silent: false }); } } catch (err) { logger.error("Güncellik durumu kontrol edilirken hata:", err); } } // Menüyü yeniden kaydetmeye gerek yok, sayfa yenilenecek veya zaten güncel. // registerMenuCommands(); }); GM_registerMenuCommand(`📊 Engelleme İstatistikleri`, async () => { try { // Güncel toplam engellenen sayısını GM'den tekrar oku (başka sekmelerde değişmiş olabilir) const latestTotalBlocked = await GM_getValue("totalBlocked", config.totalBlocked); // Bellekteki ile başla const lastUpdateTimestamp = config.trollListLastUpdate; const formattedDate = lastUpdateTimestamp ? new Date(lastUpdateTimestamp).toLocaleString("tr-TR", { dateStyle: 'short', timeStyle: 'medium' }) : "HENÜZ GÜNCELLENMEDİ"; const currentListSize = trollListSize; // Bellekteki güncel boyut const statsText = `Toplam Engellenen/Daraltılan Entry: ${latestTotalBlocked}\nMevcut Liste Boyutu: ${currentListSize}\nListe Son Kontrol/Güncelleme: ${formattedDate}`; showFeedback("Troll Filtresi Raporu", statsText); } catch (err) { logger.error("Engelleme İstatistikleri okunurken HATA:", err); showFeedback("Rapor Hatası", "Engelleme İstatistikleri okunurken depolama hatası!", { isError: true }); } }); // --- İşaretleme Komutları --- GM_registerMenuCommand(`📊 Yazar İşaretleme Raporu`, async () => { try { // Güncel işaretlemeleri GM'den tekrar oku const currentMarkings = await GM_getValue(AUTHOR_MARKINGS_KEY, {}); authorMarkings = currentMarkings; // Bellekteki state'i de güncelle let output = `--- Yazar İşaretleme Raporu (${SCRIPT_NAME}) ---\nVeri Anahtarı: ${AUTHOR_MARKINGS_KEY}\n\n`; const categoryLabels = { sycophancy: 'YALAMA', slander: 'KARALAMA', provocation: 'KIŞKIRTMA' }; // Büyük harf etiketler const knownCategories = Object.keys(categoryLabels); let totalMarks = 0; let totalMarkedAuthors = new Set(); let unknownCategoriesFound = []; // Bilinen kategorileri işle knownCategories.forEach(catId => { const categoryName = categoryLabels[catId]; output += `=== KATEGORİ: ${categoryName} ===\n`; const authorsInCategory = authorMarkings[catId]; if (authorsInCategory && Object.keys(authorsInCategory).length > 0) { const sortedAuthors = Object.entries(authorsInCategory) .sort(([, countA], [, countB]) => countB - countA); // Sayıya göre çoktan aza sırala sortedAuthors.forEach(([author, count]) => { output += ` - ${author}: ${count} kez\n`; totalMarks += count; totalMarkedAuthors.add(author); }); } else { output += ` (Bu kategoride işaretlenmiş yazar YOK)\n`; } output += '\n'; }); // Bilinmeyen (eski veya hatalı) kategorileri kontrol et Object.keys(authorMarkings).forEach(catId => { if (!knownCategories.includes(catId)) { const count = Object.keys(authorMarkings[catId] || {}).length; if (count > 0) { unknownCategoriesFound.push(`'${catId}' (${count} yazar)`); } } }); if (unknownCategoriesFound.length > 0) { output += `UYARI: Raporda gösterilmeyen eski/bilinmeyen kategorilerde veri bulundu: ${unknownCategoriesFound.join(', ')}. Bu veriler aşağıdaki toplamlara dahil DEĞİLDİR. Silmek için 'TÜM Verileri SİL' seçeneğini kullanabilirsiniz.\n\n`; } output += `--------------------\n`; output += `Toplam İşaretleme Sayısı (bilinen kategoriler): ${totalMarks}\n`; output += `Toplam İşaretlenen Farklı Yazar Sayısı: ${totalMarkedAuthors.size}\n`; if (totalMarks === 0 && totalMarkedAuthors.size === 0 && unknownCategoriesFound.length === 0) { output = `--- Yazar İşaretleme Raporu (${SCRIPT_NAME}) ---\n\n(HENÜZ HİÇBİR YAZAR İŞARETLENMEMİŞ)`; } showFeedback("Yazar İşaretleme Raporu", output); } catch (err) { logger.error("İşaretleme raporu okunurken veya oluşturulurken HATA:", err); showFeedback("Rapor Hatası", "İşaretleme raporu okunurken/oluşturulurken bir hata oluştu!", { isError: true }); } }); GM_registerMenuCommand(`🗑️ TÜM İşaretleme Verilerini SİL (DİKKAT!)`, async () => { const confirmation = confirm( `[${SCRIPT_NAME}] - KESİN EMİN MİSİNİZ?\n\n` + `'${AUTHOR_MARKINGS_KEY}' anahtarındaki TÜM yazar işaretleme verileri (Yalama, Karalama, Kışkırtma ve varsa diğer tüm eski/bilinmeyen kategoriler) ` + `KALICI OLARAK SİLİNECEKTİR!\n\n` + `BU İŞLEM GERİ ALINAMAZ!` ); if (confirmation) { logger.warn(`Kullanıcı '${AUTHOR_MARKINGS_KEY}' anahtarındaki TÜM işaretleme verilerini silmeyi onayladı.`); try { await GM_setValue(AUTHOR_MARKINGS_KEY, {}); // Veriyi boş bir obje ile değiştir authorMarkings = {}; // Bellekteki state'i de sıfırla logger.log("TÜM yazar işaretleme verileri başarıyla silindi."); showFeedback("İşlem Başarılı", "TÜM yazar işaretleme verileri başarıyla SİLİNDİ."); } catch (err) { logger.error("İşaretleme verileri SİLİNİRKEN HATA (GM_setValue):", err); showFeedback("Silme Başarısız", `İşaretlemeler temizlenirken KRİTİK HATA oluştu: ${err.message}`, { isError: true }); } finally { // Başarılı veya başarısız, menüyü güncelle (örn. rapor artık boş görünecek) registerMenuCommands(); } } else { logger.log("Kullanıcı işaretleme verilerini silme işlemini iptal etti."); showFeedback("İşlem İptal Edildi", "İşaretleme verileri SİLİNMEDİ.", { silent: true }); // Sessiz geri bildirim } }); } // --- Scripti Başlat --- try { await initialize(); logger.log(`🎉 ${SCRIPT_NAME} başarıyla YÜKLENDİ ve çalışıyor. v${GM_info.script.version}`); } catch (err) { // Initialize içindeki hatalar zaten loglanıyor ve alert veriyor olabilir. // Bu, initialize'ın kendisindeki beklenmedik hatalar için son bir güvenlik ağı. logger.error("Userscript başlatılırken YAKALANAMAYAN KRİTİK HATA:", err); alert(`[${SCRIPT_NAME}] BAŞLATMA BAŞARISIZ OLDU!\n\nBeklenmedik bir hata oluştu: ${err.message}\n\nScript işlevini yerine getiremeyebilir. Tarayıcı konsolunu (F12) kontrol edin!`); } })(); // --- DOSYA SONU anti-troll.user.js ---