Onet De-Baiter

Zamienia tytuły na stronie Onet.pl na mniej click-baitowe

スクリプトをインストールするには、Tampermonkey, GreasemonkeyViolentmonkey のような拡張機能のインストールが必要です。

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

スクリプトをインストールするには、TampermonkeyViolentmonkey のような拡張機能のインストールが必要です。

スクリプトをインストールするには、TampermonkeyUserscripts のような拡張機能のインストールが必要です。

このスクリプトをインストールするには、Tampermonkeyなどの拡張機能をインストールする必要があります。

このスクリプトをインストールするには、ユーザースクリプト管理ツールの拡張機能をインストールする必要があります。

(ユーザースクリプト管理ツールは設定済みなのでインストール!)

このスタイルをインストールするには、Stylusなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus などの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus tなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

(ユーザースタイル管理ツールは設定済みなのでインストール!)

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください
// ==UserScript==
// @name         Onet De-Baiter
// @version      1.3.4
// @description  Zamienia tytuły na stronie Onet.pl na mniej click-baitowe
// @match        *://www.onet.pl/*
// @grant        GM_xmlhttpRequest
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_addStyle
// @connect      debaiter.westeurope.cloudapp.azure.com
// @namespace https://greasyfork.org/users/1575957
// ==/UserScript==

(function() {
    'use strict';

    // --- KONFIGURACJA ---
    // Selektor elementów, w których skrypt szuka linków i nagłówków H3
    const ARTICLE_SELECTOR = 'article a[href^="https://www.onet.pl/"], article a[href^="https://wiadomosci.onet.pl/"]';

    // --- SEKCJA CSS ---
    // Przeniesione style dla przetworzonych nagłówków
    GM_addStyle(`
        .debaiter-processed {
            text-decoration: underline dotted currentColor !important;
            text-underline-offset: 4px !important;
        }
    `);

    // --- SEKCJA CACHE ---

    // Zwraca zbuforowany tytuł dla danego hasha lub null, jeśli nie istnieje
    async function getFromCache(hash) {
        return GM_getValue(hash, null);
    }

    // Zapisuje nowy tytuł do pamięci podręcznej
    async function saveToCache(hash, title) {
        GM_setValue(hash, title);
    }

    // --- FUNKCJE POMOCNICZE I MODYFIKUJĄCE ---

    // Funkcja wyliczająca SHA-256
    async function sha256(message) {
        const msgBuffer = new TextEncoder().encode(message);
        const hashBuffer = await crypto.subtle.digest('SHA-256', msgBuffer);
        const hashArray = Array.from(new Uint8Array(hashBuffer));
        return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
    }

    // Funkcja aktualizująca H3 i dodająca klasę "przetworzony"
    function updateH3(h3Element, newTitle) {
        h3Element.title = h3Element.innerText;
        h3Element.innerText = newTitle;
        // Przypisanie klasy CSS zamiast stylów inline
        h3Element.classList.add('debaiter-processed');
    }

    // --- LOGIKA GŁÓWNA ---

    // Zmienne do grupowania (batching) zapytań
    let pendingPayload = [];
    let pendingElementsMap = new Map();
    let batchTimeout = null;

    // Funkcja wysyłająca zgromadzoną paczkę danych na serwer
    function sendBatch() {
        if (pendingPayload.length === 0) return;

        // Kopiujemy obecny stan do wysyłki i od razu czyścimy globalne zmienne na poczet kolejnych artykułów
        const payloadToSend = [...pendingPayload];
        const mapForThisBatch = new Map(pendingElementsMap);

        pendingPayload = [];
        pendingElementsMap.clear();

        GM_xmlhttpRequest({
            method: "POST",
            url: "https://debaiter.westeurope.cloudapp.azure.com/api/v1/",
            data: JSON.stringify(payloadToSend),
            headers: {
                "Content-Type": "application/json"
            },
            onload: function(response) {
                try {
                    const newTitles = JSON.parse(response.responseText);
                    for (const [hash, newTitle] of Object.entries(newTitles)) {
                        if (mapForThisBatch.has(hash)) {
                            updateH3(mapForThisBatch.get(hash), newTitle);
                            saveToCache(hash, newTitle); // Zapis do cache po otrzymaniu z serwera
                        }
                    }
                } catch (error) {
                    console.error("De-Baiter: Błąd parsowania odpowiedzi", error);
                    console.error(response.responseText);
                }
            },
            onerror: function(error) {
                console.error("De-Baiter: Błąd sieci podczas wysyłania", error);
            }
        });
    }

    // Funkcja szukająca i przetwarzająca nowe elementy na stronie
    async function processElements() {
        // Używamy zmiennej ARTICLE_SELECTOR zdefiniowanej na górze skryptu
        const articles = document.querySelectorAll(ARTICLE_SELECTOR);
        let addedNew = false;

        for (const a of articles) {
            // Zapobiegamy ponownemu przetwarzaniu tego samego artykułu (np. przy scrollowaniu)
            if (a.dataset.debaiterProcessed) continue;

            const h3 = a.querySelector('h3');
            if (h3) {
                const title = h3.innerText.trim();
                if (title && a.href) {
                    // Oznaczamy element jako już znaleziony
                    a.dataset.debaiterProcessed = "true";

                    // Oczyszczamy URL z parametrów śledzących (np. ?srcc=...) dla spójnego hasha
                    const cleanHref = a.href.split('?')[0];
                    const hash = await sha256(cleanHref); // Hash z czystego hrefa

                    // Wstępna obsługa cache - sprawdzamy przed dodaniem do kolejki
                    const cachedTitle = await getFromCache(hash);

                    if (cachedTitle) {
                        updateH3(h3, cachedTitle);
                    } else {
                        pendingElementsMap.set(hash, h3);
                        pendingPayload.push({
                            hash: hash,
                            title: title,
                            href: cleanHref // Wysyłamy czysty href
                        });
                        addedNew = true;
                    }
                }
            }
        }

        // Jeśli znaleziono nowe tytuły (niepochodzące z cache), restartujemy odliczanie (debounce).
        // Zapytanie poleci 500ms po pojawieniu się ostatniego elementu w oknie.
        if (addedNew) {
            if (batchTimeout) clearTimeout(batchTimeout);
            batchTimeout = setTimeout(sendBatch, 500);
        }
    }

    // 1. Uruchomienie dla elementów już istniejących w momencie ładowania
    processElements();

    // 2. Użycie MutationObserver do reagowania na hydrację i doładowywanie przy scrollowaniu
    const observer = new MutationObserver((mutations) => {
        let shouldProcess = false;
        // Sprawdzamy czy modyfikacja DOM polegała na dodaniu nowych węzłów
        for (const mutation of mutations) {
            if (mutation.addedNodes.length > 0) {
                shouldProcess = true;
                break;
            }
        }
        if (shouldProcess) {
            processElements();
        }
    });

    // Podpinamy obserwatora pod całe <body>
    observer.observe(document.body, {
        childList: true,
        subtree: true
    });

})();