Multi Forum Read Marker (Soutong + TT1069)

标记已访问的帖子标题,支持搜同网和 TT1069 论坛,标记放左边,避免误标!

À partir de 2025-06-08. Voir la dernière version.

// ==UserScript==
// @name         Multi Forum Read Marker (Soutong + TT1069)
// @namespace    https://felixchristian.dev/userscripts/multi-forum-read-marker
// @version      1.1.0
// @description  标记已访问的帖子标题,支持搜同网和 TT1069 论坛,标记放左边,避免误标!
// @author       FelixChristian
// @license      MIT
// @match        https://soutong.men/forum.php?mod=forumdisplay&fid=*
// @match        https://soutong.men/forum.php?mod=viewthread&tid=*
// @match        https://www.tt1069.com/bbs/forum-*-*.html
// @match        https://www.tt1069.com/bbs/thread-*-*-*.html
// @grant        GM_getValue
// @grant        GM_setValue
// ==/UserScript==

(function () {
    'use strict';

    const HOST = location.hostname;
    const isSoutong = HOST.includes('soutong.men');
    const isTT1069 = HOST.includes('tt1069.com');

    const STORAGE_KEY = 'visitedTids';
    let visitedTids = GM_getValue(STORAGE_KEY, {});

    // 解析当前页面帖子的唯一 ID(tid)
    function getCurrentTid() {
        if (isSoutong) {
            const url = new URL(location.href);
            return url.searchParams.get('tid');
        }
        if (isTT1069) {
            const match = location.pathname.match(/thread-(\d+)-\d+-\d+\.html/);
            return match ? match[1] : null;
        }
        return null;
    }

    // 记录访问
    function recordTidVisit(tid) {
        if (tid) {
            visitedTids[tid] = Date.now();
            GM_setValue(STORAGE_KEY, visitedTids);
        }
    }

    // 标记已读
    function markReadThreads() {
        let threadLinks;

        if (isSoutong) {
            threadLinks = document.querySelectorAll('a.xst');
        } else if (isTT1069) {
            threadLinks = Array.from(document.querySelectorAll('a.xst')).filter(link => {
                return /thread-(\d+)-/.test(link.href);
            });
        }

        if (!threadLinks || threadLinks.length === 0) {
            setTimeout(markReadThreads, 500);
            return;
        }

        threadLinks.forEach(link => {
            let tid;
            if (isSoutong) {
                try {
                    const fullUrl = new URL(link.href, location.origin);
                    tid = fullUrl.searchParams.get('tid');
                } catch (e) { return; }
            } else if (isTT1069) {
                const match = link.href.match(/thread-(\d+)-/);
                tid = match ? match[1] : null;
            }

            if (tid && visitedTids[tid] && !link.dataset.markedVisited) {
                const tag = document.createElement('span');
                tag.textContent = '[已读] ';
                tag.style.color = 'red';
                tag.style.fontWeight = 'bold';
                tag.style.marginRight = '4px';
                link.insertBefore(tag, link.firstChild);
                link.dataset.markedVisited = 'true';
            }
        });
    }

    // 页面识别逻辑
    const isViewThread = (isSoutong && location.href.includes('mod=viewthread')) ||
                         (isTT1069 && /thread-\d+-\d+-\d+\.html/.test(location.pathname));

    const isForumDisplay = (isSoutong && location.href.includes('mod=forumdisplay')) ||
                           (isTT1069 && /forum-\d+-\d+\.html/.test(location.pathname));

    if (isViewThread) {
        const tid = getCurrentTid();
        recordTidVisit(tid);
    }

    if (isForumDisplay) {
        window.addEventListener('load', markReadThreads);
        const observer = new MutationObserver(markReadThreads);
        observer.observe(document.body, { childList: true, subtree: true });
    }
})();