标记已访问帖子,支持搜同和TT1069,使用localStorage + beforeunload防止快速切换丢记录,标记文字标题左侧。
Mint
// ==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});
}
})();