Multi Forum Read Marker (Soutong + TT1069)

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

Versión del día 08/06/2025. Echa un vistazo a la versión más reciente.

Tendrás que instalar una extensión para tu navegador como Tampermonkey, Greasemonkey o Violentmonkey si quieres utilizar este script.

Necesitarás instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Userscripts para instalar este script.

Necesitará instalar una extensión como Tampermonkey para instalar este script.

Necesitarás instalar una extensión para administrar scripts de usuario si quieres instalar este script.

(Ya tengo un administrador de scripts de usuario, déjame instalarlo)

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

(Ya tengo un administrador de estilos de usuario, déjame instalarlo)

// ==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});
    }
})();