Multi Forum Read Marker (Soutong + TT1069)

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

Versione datata 08/06/2025. Vedi la nuova versione l'ultima versione.

Dovrai installare un'estensione come Tampermonkey, Greasemonkey o Violentmonkey per installare questo script.

Dovrai installare un'estensione come Tampermonkey o Violentmonkey per installare questo script.

Dovrai installare un'estensione come Tampermonkey o Violentmonkey per installare questo script.

Dovrai installare un'estensione come Tampermonkey o Userscripts per installare questo script.

Dovrai installare un'estensione come ad esempio Tampermonkey per installare questo script.

Dovrai installare un gestore di script utente per installare questo script.

(Ho già un gestore di script utente, lasciamelo installare!)

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

(Ho già un gestore di stile utente, lasciamelo installare!)

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