Multi Forum Read Marker (Soutong + TT1069)

标记已访问帖子,支持搜同和TT1069,使用localStorage + beforeunload防止快速切换丢记录,标记文字标题左侧。

Per 08-06-2025. Zie de nieuwste versie.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey, Greasemonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Userscripts.

Voor het installeren van scripts heb je een extensie nodig, zoals {tampermonkey_link:Tampermonkey}.

Voor het installeren van scripts heb je een gebruikersscriptbeheerder nodig.

(Ik heb al een user script manager, laat me het downloaden!)

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

(Ik heb al een beheerder - laat me doorgaan met de installatie!)

// ==UserScript==
// @name         Multi Forum Read Marker (Soutong + TT1069)
// @namespace    https://felixchristian.dev/userscripts/multi-forum-read-marker
// @version      1.3.0
// @description  标记已访问帖子,支持搜同和TT1069,使用localStorage + beforeunload防止快速切换丢记录,标记文字标题左侧。
// @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/thread-*-*-*.html
// @match        https://www.tt1069.com/bbs/forum-*-*.html
// @match        https://www.tt1069.com/bbs/forum.php?mod=forumdisplay&fid=*
// @match        https://www.tt1069.com/bbs/forum.php?mod=forumdisplay&fid=*&*
// @match        https://www.tt1069.com/bbs/forum.php?mod=viewthread&tid=*
// @grant        GM_getValue
// @grant        GM_setValue
// ==/UserScript==

(function () {
    'use strict';

    const STORAGE_KEY = 'visitedTids';

    // 先尝试读取GM存储的记录,如果localStorage里有临时数据用临时数据
    let visitedTids = {};
    try {
        visitedTids = GM_getValue(STORAGE_KEY, {});
        const localData = localStorage.getItem(STORAGE_KEY + '_temp');
        if (localData) {
            const localObj = JSON.parse(localData);
            // 合并localStorage里更“新”的访问数据
            visitedTids = {...visitedTids, ...localObj};
        }
    } catch (e) {
        // 读取失败忽略
        visitedTids = {};
    }

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

    function getCurrentTid() {
        if (location.href.includes('tid=')) {
            try {
                const url = new URL(location.href);
                return url.searchParams.get('tid');
            } catch (e) {
                return null;
            }
        }
        const match = location.href.match(/thread-(\d+)-/);
        return match ? match[1] : null;
    }

    // 保存访问记录到本地缓存和 localStorage(同步)
    function saveCurrentTid(tid) {
        if (!tid) return;
        visitedTids[tid] = Date.now();
        try {
            localStorage.setItem(STORAGE_KEY + '_temp', JSON.stringify(visitedTids));
        } catch (e) {
            // localStorage写入失败忽略
        }
    }

    // 离开页面时写回GM存储,确保持久化
    window.addEventListener('beforeunload', () => {
        try {
            const localData = localStorage.getItem(STORAGE_KEY + '_temp');
            if (localData) {
                const obj = JSON.parse(localData);
                GM_setValue(STORAGE_KEY, obj);
                // 清理临时数据(可选)
                localStorage.removeItem(STORAGE_KEY + '_temp');
            }
        } catch (e) {
            // 忽略错误
        }
    });

    // 标记已访问帖子
    function markReadThreads() {
        const threadLinks = document.querySelectorAll('a.s.xst');

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

            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 = location.href.includes('mod=viewthread') || /thread-\d+-/.test(location.pathname);
    const isForumDisplay = location.href.includes('mod=forumdisplay') || /forum-\d+-\d+\.html/.test(location.pathname);

    // 当前是帖子详情页 => 记录访问
    if (isViewThread) {
        const tid = getCurrentTid();
        saveCurrentTid(tid);
    }

    // 当前是论坛列表页 => 标记已访问帖子
    if (isForumDisplay) {
        window.addEventListener('load', markReadThreads);
        const observer = new MutationObserver(markReadThreads);
        observer.observe(document.body, {childList: true, subtree: true});
    }
})();