// ==UserScript==
// @name 98T Picture Preview
// @description [REFACTORED] Combines the full functionality of v1.9.0 with a modern, multi-column, dark-mode grid UI at the top of the page.
// @version 2.0.1
// @icon https://www.google.com/s2/favicons?sz=64&domain=www.sehuatang.net
// @author UnforgetMemory
// @namespace https://www.sehuatang.net/*
// @namespace https://www.sehuatang.org/*
// @match https://www.sehuatang.net/forum*
// @match https://www.sehuatang.org/forum*
// @match https://www.sehuatang.net/forum.php?mod=forumdisplay&fid=103&page=*
// @match https://www.sehuatang.org/forum.php?mod=forumdisplay&fid=103&page=*
// @require https://cdn.jsdelivr.net/npm/[email protected]/lodash.min.js
// @require https://cdn.jsdelivr.net/npm/sweetalert2@11
// @require https://cdn.jsdelivr.net/npm/[email protected]/i18next.min.js
// @grant GM_xmlhttpRequest
// @grant GM_addStyle
// @grant GM_registerMenuCommand
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_setClipboard
// @license GNU GPLv3
// ==/UserScript==
(function () {
"use strict";
// --- CONFIGURATION ---
const CONFIG = {
AVID_REGEX: /[a-zA-Z]{2,6}[-\s]?\d{2,5}/gi,
JAVDB_HOST: "javdb.com",
HTTP_HEADERS: {
"User-Agent": window.navigator.userAgent,
Accept:
"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
Cookie: document.cookie,
Referer: document.location.href,
},
LOCAL_STORAGE_KEYS: {
VIEWED_AVIDS: "68905cf391b2428572e6446042ab1029",
VIEWED_US_TITLES: "abba24c58fc69bf0955bddc7a0eadee1",
HIDE_VIEWED_MODE: "780fbed5c332f7f96ca73e19e94a9749",
JIANGUOYUN: "97f6483755d45ad927caf3108b61be91",
LOCALE: "ee2757153264e82a1c8f64db8ddcb3e2",
},
JIANGUOYUN: {
AVIDS_FILENAME: "95551967d3da7c5af36b141f630683c4",
US_FILENAME: "53648e7622cfa657f1f6de856efd67c9",
ELEMENT_IDS: {
DAV_URL: "jgy_dav_url",
ACCOUNT: "jgy_account",
PASSWORD: "jgy_password",
},
},
LOCALES: { enUS: "en-US", zhCN: "zh-CN", zhHK: "zh-HK", zhTW: "zh-TW" },
};
// --- I18N INITIALIZATION ---
i18next.init({
lng: GM_getValue(CONFIG.LOCAL_STORAGE_KEYS.LOCALE, CONFIG.LOCALES.enUS),
fallbackLng: CONFIG.LOCALES.enUS,
resources: {
"en-US": {
translation: {
Language: "🕮 Language",
"Hide Viewed": "Hide Viewed",
Jianguoyun: "☁️ Jianguoyun",
"Jianguoyun Config": "☁️ Jianguoyun Config",
"Upload To Jianguoyun": "↑ Upload To ☁️ Jianguoyun",
"Download from Jianguoyun": "↓ Download from ☁️ Jianguoyun",
"Local and Jianguoyun merge": "🔄 Local and ☁️ Jianguoyun merge",
"DAV URL": "☁️ DAV URL",
Account: "👤 Account",
Password: "🔑 Password",
"Show Password": "Show Password",
Save: "Save",
"Save Successful!": "Save Successful!",
"Update Successful!": "Update Successful!",
"Update Bad": "Update Bad",
"Upload successful!": "Upload successful!",
"Sync successful!": "Sync successful!",
"Download successful!": "Download successful!",
"Hidden Password": "Hidden Password",
"Status ERROR": "Webdav Status Error",
"Check Config!": "Check Config!",
"No Data": "Cloud No historical data exists!",
"Bad Download": "Bad Download Data",
"Page to Refresh":
"The page is about to refresh due to new data updates. Please wait...",
"Viewed Total": "Viewed Total",
click_all_magnet: "Copy All Magnet Links",
click_all_torrent: "Download All Torrents",
},
},
"zh-CN": {
translation: {
Language: "🕮 语言(简体)",
"Hide Viewed": "隐藏已阅",
Jianguoyun: "☁️ 坚果云",
"Jianguoyun Config": "☁️ 坚果云配置",
"Upload To Jianguoyun": "↑ 上传至 ☁️ 坚果云",
"Download from Jianguoyun": "从 ☁️ 坚果云 ↓ 下载",
"Local and Jianguoyun merge": "🔄 双端同步 ☁️ 坚果云 ",
"DAV URL": "☁️ DAV URL",
Account: "👤 账号",
Password: "🔑 密码",
"Show Password": "显示密码",
Save: "保存",
"Save Successful!": "保存成功!",
"Update Successful!": "更新成功!",
"Update Bad": "更新失败",
"Upload successful!": "上传成功!",
"Sync successful!": "同步成功!",
"Download successful!": "下载完成!",
"Hidden Password": "隐藏密码",
"Status ERROR": "Webdav 状态异常",
"Check Config!": "检查配置!",
"No Data": "云端没有历史数据!",
"Bad Download": "下载数据出错",
"Page to Refresh": "数据更新,页面即将刷新,请稍候...",
"Viewed Total": "浏览量",
click_all_magnet: "复制所有磁力",
click_all_torrent: "下载所有种子",
},
},
"zh-TW": {
translation: {
Language: "🕮 語言(台)",
"Hide Viewed": "隱藏已讀",
Jianguoyun: "☁️ 堅果雲",
"Jianguoyun Config": "☁️ 堅果雲配置",
"Upload To Jianguoyun": "⬆️ 上傳至 ☁️ 堅果雲",
"Download from Jianguoyun": "從 ☁️ 堅果雲 ⬇️ 下載",
"Local and Jianguoyun merge": "本地與 ☁️ 堅果雲同步",
"DAV URL": "☁️ DAV 網址",
Account: " 帳戶",
Password: " 密碼",
"Show Password": "顯示密碼",
Save: "儲存",
"Save Successful!": "儲存成功!",
"Hidden Password": "隱藏密碼",
"Update Bad": "更新失敗",
"Upload successful!": "上傳成功!",
"Sync successful!": "同步成功!",
"Download successful!": "下載完成!",
"Status ERROR": "Webdav 狀態錯誤",
"Check Config!": "檢查設定!",
"No Data": "雲端沒有歷史資料!",
"Bad Download": "下載數據錯誤",
"Page to Refresh": "頁面即將重新整理,以更新資料。請稍候...",
"Viewed Total": "瀏覽量",
click_all_magnet: "點擊所有磁力連結",
click_all_torrent: "點擊所有種子連結",
},
},
"zh-HK": {
translation: {
Language: "🕮 語言(港)",
"Hide Viewed": "收埋睇過",
Jianguoyun: "☁️ 堅果雲",
"Jianguoyun Config": "☁️ 堅果雲設定",
"Upload To Jianguoyun": "⬆️ 上載到 ☁️ 堅果雲",
"Download from Jianguoyun": "由 ☁️ 堅果雲 ⬇️ 下載",
"Local and Jianguoyun merge": "本地同 ☁️ 堅果雲同步",
"DAV URL": "☁️ DAV 網址",
Account: "帳戶",
Password: "密碼",
"Show Password": "睇密碼",
Save: "儲存",
"Save Successful!": "儲存成功喇!",
"Hidden Password": "收埋密碼",
"Update Bad": "更新搞唔掂",
"Upload successful!": "上傳成功喇!",
"Sync successful!": "同步成功喇!",
"Download successful!": "下載掂咗喇!",
"Status ERROR": "Webdav 狀態搞唔掂",
"Check Config!": "睇吓設定啱唔啱!",
"No Data": "雲端咩資料都冇呀!",
"Bad Download": "下載嘅資料壞咗",
"Page to Refresh": "资料更新紧系,页面要更新喇!等阵先!",
"Viewed Total": "瀏覽量",
click_all_magnet: "點擊所有磁力連結",
click_all_torrent: "點擊所有種子連結",
},
},
},
});
// --- MODERN STYLES ---
GM_addStyle(`
:root { --bg-color: #121212; --card-bg-color: #1e1e1e; --text-color: #e0e0e0; --text-secondary-color: #a0a0a0; --accent-color: #03dac6; --border-color: #333333; --shadow-color: rgba(0, 0, 0, 0.4); }
body { background-color: var(--bg-color) !important; }
#filtered-info-bar { background-color: var(--card-bg-color); color: var(--text-secondary-color); padding: 10px 25px; font-size: 0.9rem; text-align: center; border-bottom: 1px solid var(--border-color); box-sizing: border-box; width: 100%; }
#modern-preview-container { display: grid; grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); gap: 25px; padding: 25px; width: 100%; box-sizing: border-box; }
.preview-card { background-color: var(--card-bg-color); border-radius: 12px; border: 1px solid var(--border-color); overflow: hidden; display: flex; flex-direction: column; box-shadow: 0 4px 15px var(--shadow-color); transition: transform 0.3s ease, box-shadow 0.3s ease; }
.preview-card:hover { transform: translateY(-8px); box-shadow: 0 10px 25px var(--shadow-color); }
.preview-card.viewed { opacity: 0.6; transition: opacity 0.5s ease; }
.preview-card.viewed:hover { opacity: 1; }
.card-image-container { aspect-ratio: 16 / 10; background-color: #2a2a2a; display: flex; align-items: center; justify-content: center; overflow: hidden; cursor: pointer; }
.card-image-container img { width: 100%; height: 100%; object-fit: cover; transition: transform 0.3s ease; }
.preview-card:hover .card-image-container img { transform: scale(1.05); }
.card-content { padding: 15px; display: flex; flex-direction: column; flex-grow: 1; }
.card-title { font-size: 1.1rem; font-weight: 600; color: var(--text-color); margin: 0 0 8px 0; line-height: 1.4; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.card-title a { color: inherit; text-decoration: none; transition: color 0.2s ease; }
.card-title a:hover { color: var(--accent-color); }
.card-meta { font-size: 0.8rem; color: var(--text-secondary-color); margin: 0 0 15px 0; }
.card-links { margin-top: auto; display: flex; flex-direction: row; justify-content: flex-end; gap: 15px; }
.card-links a { display: flex; align-items: center; justify-content: center; padding: 6px 12px; text-decoration: none; border-radius: 8px; font-size: 1.5rem; transition: background-color 0.2s ease, color 0.2s ease, transform 0.2s ease; }
.card-links a:hover { transform: scale(1.1); }
.magnet-link { background-color: #443b17; color: #ffc107; }
.magnet-link:hover { background-color: #ffc107; color: var(--card-bg-color); }
.torrent-link { background-color: #1c3a1e; color: #4caf50; }
.torrent-link:hover { background-color: #4caf50; color: var(--card-bg-color); }
`);
// --- UTILITY & SETUP ---
const gmFetch = (details) =>
new Promise((resolve, reject) => {
details.onload = resolve;
details.onerror = reject;
details.ontimeout = reject;
GM_xmlhttpRequest(details);
});
const getJsonValue = (key, defaultValue = "[]") =>
JSON.parse(GM_getValue(key, defaultValue));
const setJsonValue = (key, value) => GM_setValue(key, JSON.stringify(value));
const showToast = (title, icon = "success") =>
Swal.fire({
toast: true,
position: "top-end",
showConfirmButton: false,
timer: 3000,
title,
icon,
});
// --- LOCAL STORAGE HELPERS ---
const viewedAVIDs = {
list: () => getJsonValue(CONFIG.LOCAL_STORAGE_KEYS.VIEWED_AVIDS),
has: (id) => viewedAVIDs.list().includes(id),
add: (id) => {
const c = viewedAVIDs.list();
if (!c.includes(id))
setJsonValue(CONFIG.LOCAL_STORAGE_KEYS.VIEWED_AVIDS, [...c, id]);
},
reset: (ids) => setJsonValue(CONFIG.LOCAL_STORAGE_KEYS.VIEWED_AVIDS, ids),
merge: (newIds) => viewedAVIDs.reset(_.union(viewedAVIDs.list(), newIds)),
};
const viewedUSTitles = {
list: () => getJsonValue(CONFIG.LOCAL_STORAGE_KEYS.VIEWED_US_TITLES),
has: (title) => viewedUSTitles.list().includes(title),
add: (title) => {
const c = viewedUSTitles.list();
if (!c.includes(title))
setJsonValue(CONFIG.LOCAL_STORAGE_KEYS.VIEWED_US_TITLES, [...c, title]);
},
reset: (titles) =>
setJsonValue(CONFIG.LOCAL_STORAGE_KEYS.VIEWED_US_TITLES, titles),
merge: (newTitles) =>
viewedUSTitles.reset(_.union(viewedUSTitles.list(), newTitles)),
};
// --- JIANGUOYUN (CLOUD SYNC) ---
class JianguoyunClient {
constructor() {
const config = getJsonValue(CONFIG.LOCAL_STORAGE_KEYS.JIANGUOYUN, "{}");
this.davURL = config.url;
this.auth =
config.account && config.password
? `Basic ${btoa(`${config.account}:${config.password}`)}`
: null;
}
isValid() {
return !!(this.davURL && this.auth);
}
async request(method, fileName, data = null) {
if (!this.isValid()) return Promise.reject("Bad Jianguoyun Config!");
const url = `${this.davURL}/${fileName}.json`.replace(
/(?<!:)\/{2,}/g,
"/"
);
try {
return await gmFetch({
method,
url,
data,
headers: { Authorization: this.auth },
timeout: 5000,
});
} catch (error) {
console.error(
`[Jianguoyun] ${method} request failed for ${fileName}`,
error
);
throw error;
}
}
async download(fileName) {
return this.request("GET", fileName);
}
async upload(fileName, data) {
return this.request("PUT", fileName, JSON.stringify(data));
}
}
// --- CORE LOGIC ---
function extractThreadInfo(el) {
const linkEl = el.querySelector("th a.s.xst");
if (!linkEl) return null;
const title = linkEl.innerText.trim();
const avIdMatch = title.match(CONFIG.AVID_REGEX);
const avId = avIdMatch ? avIdMatch[0].toUpperCase() : null;
const dateEl =
el.querySelector("td.by em span span") ||
el.querySelector("td.by em span");
return {
url: linkEl.href,
fullTitle: title,
avId,
releaseDate: dateEl ? dateEl.innerText.trim() : "N/A",
};
}
async function createAndAppendCard(info, container) {
const isViewed = info.avId
? viewedAVIDs.has(info.avId)
: viewedUSTitles.has(info.fullTitle);
if (GM_getValue(CONFIG.LOCAL_STORAGE_KEYS.HIDE_VIEWED_MODE) && isViewed)
return;
const card = document.createElement("div");
card.className = "preview-card";
// [FIXED] Add data attributes for later retrieval, avoiding the need to simulate clicks.
if (info.avId) card.dataset.avid = info.avId;
card.dataset.fullTitle = info.fullTitle;
if (isViewed) card.classList.add("viewed");
card.innerHTML = `
<div class="card-image-container"></div>
<div class="card-content">
<h3 class="card-title">
<a href="${
info.avId
? `https://${CONFIG.JAVDB_HOST}/search?q=${info.avId}&f=all`
: info.url
}" target="_blank" rel="noopener noreferrer" title="${
info.fullTitle
}">${info.fullTitle}</a>
</h3>
<p class="card-meta">${info.releaseDate}</p>
<div class="card-links"></div>
</div>
`;
container.appendChild(card);
const markCardAsViewed = () => {
if (card.classList.contains("viewed")) return;
if (info.avId) viewedAVIDs.add(info.avId);
else viewedUSTitles.add(info.fullTitle);
card.classList.add("viewed");
};
try {
const res = await gmFetch({
method: "GET",
url: info.url,
headers: CONFIG.HTTP_HEADERS,
});
const doc = new DOMParser().parseFromString(
res.responseText,
"text/html"
);
const imgFile = doc
.querySelector("ignore_js_op > img")
?.getAttribute("zoomfile");
const magnetLink = doc.querySelector(
".blockcode > div > ol > li"
)?.innerText;
const torrentEl = doc.querySelector(
"div.pattl > ignore_js_op > dl > dd > p.attnm a"
);
const torrentLink = torrentEl ? torrentEl.href : null;
const torrentText = torrentEl
? torrentEl.parentElement.innerText.trim()
: "Download Torrent";
if (imgFile) {
const img = document.createElement("img");
img.src = imgFile;
img.alt = "Preview Cover";
img.onclick = () => {
window.open(info.url, "_blank");
markCardAsViewed();
};
card.querySelector(".card-image-container").appendChild(img);
}
const linksContainer = card.querySelector(".card-links");
if (magnetLink) {
const a = document.createElement("a");
a.href = magnetLink;
a.className = "magnet-link";
a.title = "复制磁力链接";
a.textContent = "⚡";
a.onclick = markCardAsViewed;
linksContainer.appendChild(a);
}
if (torrentLink) {
const a = document.createElement("a");
a.href = torrentLink;
a.className = "torrent-link";
a.title = torrentText;
a.textContent = "🌱";
a.onclick = markCardAsViewed;
linksContainer.appendChild(a);
}
} catch (error) {
console.error(
`[Modern Preview] Failed to fetch details for ${info.url}:`,
error
);
card.querySelector(".card-meta").textContent += " (Failed to load)";
}
}
function processAndMigrateElement(el, container) {
const info = extractThreadInfo(el);
if (info) {
createAndAppendCard(info, container);
el.style.display = "none";
}
}
// --- MENU COMMANDS ---
function setupMenu() {
const { AVIDS_FILENAME, US_FILENAME } = CONFIG.JIANGUOYUN;
GM_registerMenuCommand(i18next.t("Language"), () => {
Swal.fire({
title: "🕮 Language",
input: "select",
inputOptions: {
"en-US": "🕮 Language",
"zh-CN": "🕮 语言(简体)",
"zh-HK": "🕮 語言(港)",
"zh-TW": "🕮 語言(台)",
},
inputValue: i18next.language,
showCancelButton: true,
confirmButtonText: i18next.t("Save"),
}).then((result) => {
if (result.isConfirmed) {
GM_setValue(CONFIG.LOCAL_STORAGE_KEYS.LOCALE, result.value);
showToast(i18next.t("Save Successful!"), "success");
setTimeout(() => location.reload(), 1000);
}
});
});
GM_registerMenuCommand(
`${
GM_getValue(CONFIG.LOCAL_STORAGE_KEYS.HIDE_VIEWED_MODE) ? "✅" : "❌"
} ${i18next.t("Hide Viewed")}`,
() => {
GM_setValue(
CONFIG.LOCAL_STORAGE_KEYS.HIDE_VIEWED_MODE,
!GM_getValue(CONFIG.LOCAL_STORAGE_KEYS.HIDE_VIEWED_MODE)
);
location.reload();
}
);
GM_registerMenuCommand(i18next.t("Jianguoyun Config"), () => {
const oldConfig = getJsonValue(
CONFIG.LOCAL_STORAGE_KEYS.JIANGUOYUN,
"{}"
);
Swal.fire({
title: i18next.t("Jianguoyun Config"),
html: `
<input type="text" id="jgy_dav_url" class="swal2-input" placeholder="${i18next.t(
"DAV URL"
)}" value="${oldConfig.url || ""}">
<input type="text" id="jgy_account" class="swal2-input" placeholder="${i18next.t(
"Account"
)}" value="${oldConfig.account || ""}">
<input type="password" id="jgy_password" class="swal2-input" placeholder="${i18next.t(
"Password"
)}" value="${oldConfig.password || ""}">`,
confirmButtonText: i18next.t("Save"),
showCancelButton: true,
preConfirm: () => {
const config = {
url: document.getElementById("jgy_dav_url").value,
account: document.getElementById("jgy_account").value,
password: document.getElementById("jgy_password").value,
};
setJsonValue(CONFIG.LOCAL_STORAGE_KEYS.JIANGUOYUN, config);
},
}).then(
(result) =>
result.isConfirmed && showToast(i18next.t("Save Successful!"))
);
});
GM_registerMenuCommand(i18next.t("Upload To Jianguoyun"), async () => {
const jgy = new JianguoyunClient();
if (!jgy.isValid()) {
showToast(i18next.t("Check Config!"), "error");
return;
}
try {
await jgy.upload(AVIDS_FILENAME, viewedAVIDs.list());
await jgy.upload(US_FILENAME, viewedUSTitles.list());
showToast(i18next.t("Upload successful!"));
} catch (e) {
showToast(i18next.t("Update Bad"), "error");
}
});
GM_registerMenuCommand(i18next.t("Download from Jianguoyun"), async () => {
const jgy = new JianguoyunClient();
if (!jgy.isValid()) {
showToast(i18next.t("Check Config!"), "error");
return;
}
try {
const avidRes = await jgy.download(AVIDS_FILENAME);
if (avidRes.status === 200)
viewedAVIDs.merge(JSON.parse(avidRes.responseText));
const usRes = await jgy.download(US_FILENAME);
if (usRes.status === 200)
viewedUSTitles.merge(JSON.parse(usRes.responseText));
showToast(i18next.t("Download successful!"));
setTimeout(() => location.reload(), 1000);
} catch (e) {
showToast(i18next.t("Bad Download"), "error");
}
});
GM_registerMenuCommand(
i18next.t("Local and Jianguoyun merge"),
async () => {
const jgy = new JianguoyunClient();
if (!jgy.isValid()) {
showToast(i18next.t("Check Config!"), "error");
return;
}
try {
const avidRes = await jgy.download(AVIDS_FILENAME);
const cloudAvids =
avidRes.status === 200 ? JSON.parse(avidRes.responseText) : [];
const mergedAvids = _.union(viewedAVIDs.list(), cloudAvids);
viewedAVIDs.reset(mergedAvids);
const usRes = await jgy.download(US_FILENAME);
const cloudUsTitles =
usRes.status === 200 ? JSON.parse(usRes.responseText) : [];
const mergedUsTitles = _.union(viewedUSTitles.list(), cloudUsTitles);
viewedUSTitles.reset(mergedUsTitles);
await jgy.upload(AVIDS_FILENAME, mergedAvids);
await jgy.upload(US_FILENAME, mergedUsTitles);
showToast(i18next.t("Sync successful!"));
setTimeout(() => location.reload(), 1000);
} catch (e) {
showToast(i18next.t("Update Bad"), "error");
}
}
);
GM_registerMenuCommand(
`${i18next.t("Viewed Total")} ${viewedAVIDs.list().length}`,
() => {}
);
GM_registerMenuCommand(i18next.t("click_all_magnet"), () => {
const links = Array.from(
document.querySelectorAll(".preview-card:not(.viewed) .magnet-link")
);
if (links.length === 0) {
showToast("没有新的磁力链接", "info");
return;
}
// 1. 复制所有链接到剪贴板
GM_setClipboard(links.map((l) => l.href).join("\r\n"));
// 2. [FIXED] 标记所有对应的卡片为已阅,但不触发点击事件
links.forEach((link) => {
const card = link.closest(".preview-card");
if (card) {
const avid = card.dataset.avid;
const fullTitle = card.dataset.fullTitle;
if (avid) {
viewedAVIDs.add(avid);
} else if (fullTitle) {
// Fallback for items without AVID
viewedUSTitles.add(fullTitle);
}
card.classList.add("viewed");
}
});
showToast(`已复制 ${links.length} 个新磁力链接!`);
});
GM_registerMenuCommand(i18next.t("click_all_torrent"), () => {
const links = document.querySelectorAll(
".preview-card:not(.viewed) .torrent-link"
);
if (links.length === 0) {
showToast("没有新的种子文件", "info");
return;
}
links.forEach((l) => l.click()); // Torrent links are for download, so clicking is the correct behavior.
showToast(`正在下载 ${links.length} 个新种子!`, "info");
});
}
// --- INITIALIZATION & OBSERVERS ---
function main() {
const threadListTable = document.getElementById("threadlisttableid");
if (!threadListTable) return;
threadListTable.style.display = "none";
let filteredCount = 0;
if (GM_getValue(CONFIG.LOCAL_STORAGE_KEYS.HIDE_VIEWED_MODE, false)) {
threadListTable
.querySelectorAll('tbody[id^="normalthread_"]')
.forEach((el) => {
const info = extractThreadInfo(el);
if (
info &&
(info.avId
? viewedAVIDs.has(info.avId)
: viewedUSTitles.has(info.fullTitle))
) {
filteredCount++;
}
});
}
const infoBar = document.createElement("div");
infoBar.id = "filtered-info-bar";
infoBar.textContent = `当前已为您隐藏 ${filteredCount} 个已阅条目。`;
const modernContainer = document.createElement("div");
modernContainer.id = "modern-preview-container";
document.body.prepend(modernContainer);
document.body.prepend(infoBar);
const run = () => {
threadListTable
.querySelectorAll('tbody[id^="normalthread_"]:not([data-processed])')
.forEach((el) => {
el.dataset.processed = "true";
processAndMigrateElement(el, modernContainer);
});
};
const runDebounced = _.debounce(run, 300, { maxWait: 1000 });
run();
const observer = new MutationObserver(() => runDebounced());
observer.observe(threadListTable, { childList: true, subtree: true });
}
// --- SCRIPT EXECUTION ---
setupMenu();
main();
})();