98堂-预览

98堂[原色花堂]帖子预览图片及链接,支持无缝翻页

// ==UserScript==
// @name         98堂-预览
// @version      1.9.0
// @namespace    https://sleazyfork.org/zh-CN/users/1461640-%E6%98%9F%E5%AE%BF%E8%80%81%E9%AD%94
// @author       星宿老魔
// @description  98堂[原色花堂]帖子预览图片及链接,支持无缝翻页
// @match        https://sehuatang.org/*
// @match        https://sehuatang.net/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=sehuatang.org
// @license      MIT
// @grant        none
// @run-at       document-start
// ==/UserScript==

(function(){"use strict";const e={maxPreviewImages:4,concurrencyLimit:20,cacheEnabled:true,cacheSize:50,
cacheTTL:10*60*1e3,lazyLoadThreshold:2,networkTimeout:5e3};const CONFIG={defaults:e,get(e){
const t=JSON.parse(localStorage.getItem("SHT_PREVIEW_CONFIG")||"{}");return t[e]??this.defaults[e]},set(e,t){
const n=JSON.parse(localStorage.getItem("SHT_PREVIEW_CONFIG")||"{}");n[e]=t
;localStorage.setItem("SHT_PREVIEW_CONFIG",JSON.stringify(n))},getAll(){
const e=JSON.parse(localStorage.getItem("SHT_PREVIEW_CONFIG")||"{}");return{...this.defaults,...e}}}
;const t=class _StyleManager{static inject(){if(this.injected)return;const e=document.createElement("style")
;e.textContent='.sht-panel{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);width:360px;background:#f4f4f4;border-radius:6px;box-shadow:0 5px 20px rgba(0,0,0,0.2);z-index:10001;font-family:sans-serif;display:none;flex-direction:column}.sht-header{padding:16px 20px;font-size:16px;font-weight:600;color:#333;background:#e9e9e9;border-bottom:1px solid #ddd;border-radius:6px 6px 0 0;cursor:move;user-select:none}.sht-content{padding:20px}.sht-row{display:flex;align-items:center;justify-content:space-between;margin-bottom:15px}.sht-label{font-size:14px;color:#555}.sht-select{padding:8px 12px;border-radius:5px;border:1px solid #ccc;font-size:14px;cursor:pointer;background:white;min-width:120px}.sht-footer{display:flex;justify-content:flex-end;padding:15px 20px;background:#e9e9e9;border-top:1px solid #ddd;border-radius:0 0 6px 6px}.sht-btn{padding:8px 16px;border:none;border-radius:5px;font-size:14px;cursor:pointer}.sht-btn-primary{background:#4d84c6;color:white}.sht-btn-secondary{background:#ccc;color:#333;margin-left:10px}.sht-close{position:absolute;top:16px;right:16px;width:32px;height:32px;border:none;border-radius:50%;background:rgba(0,0,0,0.1);color:#333;cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:18px}.sht-card{position:relative;padding:15px;border:1px solid #e0e0e0;border-radius:6px;margin:20px;background:#fff;width:calc(100% - 40px);box-sizing:border-box}.sht-img-grid{display:flex;flex-wrap:nowrap;align-items:flex-start;gap:8px;width:100%}.sht-img-item{overflow:hidden;border-radius:4px;position:relative;cursor:pointer;height:300px}.sht-img-item-single{overflow:hidden;border-radius:4px;position:relative;cursor:pointer;height:300px}.sht-img{width:100%;height:100%;object-fit:cover}.sht-img-item-single .sht-img{object-position:top}.sht-links{margin:6px 0;padding:4px;background:#f8f9fa;border:1px solid #e0e0e0;border-radius:4px;width:100%;box-sizing:border-box}.sht-link-title{font-weight:bold;margin-bottom:6px;font-size:13px}.sht-link-item{background:white;border:1px solid #ddd;padding:2px 4px;border-radius:3px;margin-bottom:2px;word-break:break-all;cursor:pointer;font-size:11px}.sht-nav-toggle{position:relative;display:inline-block;width:38px;height:20px}.sht-nav-toggle input{opacity:0;width:0;height:0}.sht-nav-slider{position:absolute;cursor:pointer;top:0;left:0;right:0;bottom:0;background:#ddd;border-radius:10px}.sht-nav-slider:before{position:absolute;content:"";height:16px;width:16px;left:2px;bottom:2px;background:white;border-radius:50%;box-shadow:0 1px 3px rgba(0,0,0,0.2)}.sht-nav-toggle input:checked+.sht-nav-slider{background:#4d84c6}.sht-nav-toggle input:checked+.sht-nav-slider:before{transform:translateX(18px)}.sht-restore{position:fixed;right:0;bottom:10px;z-index:9997;display:flex;flex-direction:column;align-items:center;justify-content:center;padding:8px 5px;border-radius:5px 0 0 5px;box-shadow:-1px 1px 5px rgba(0,0,0,0.2);background:rgba(50,50,50,0.7);border:none;box-sizing:border-box;cursor:pointer;color:white;font-size:11px;letter-spacing:2px;writing-mode:vertical-rl;text-orientation:mixed}'
;document.head.appendChild(e);this.injected=true}static createElement(e,t,n){const i=document.createElement(e)
;if(t)i.className=t;if(n)i.textContent=n;return i}static setImageGridWidth(e,t){const n=8*(t-1)
;const i=`calc((100% - ${n}px) / ${t})`;Array.from(e.children).forEach(e=>{e.style.width=i;e.style.flex="0 0 auto"})}}
;t.injected=false;let n=t;class Lightbox{static create(e,t=[]){const n=document.createElement("div")
;n.style.cssText="position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.9); display: flex; justify-content: center; align-items: center; z-index: 9999; cursor: pointer;"
;const i=document.createElement("img");i.src=e
;i.style.cssText="max-width: 90%; max-height: 90%; object-fit: contain; border: 2px solid white;";n.appendChild(i)
;if(t.length>1){let o=t.indexOf(e);const updateNavigation=()=>{s.style.display=o>0?"flex":"none"
;r.style.display=o<t.length-1?"flex":"none";a.textContent=`${o+1} / ${t.length}`};const s=document.createElement("div")
;s.innerHTML="‹"
;s.style.cssText="position: absolute; left: 20px; top: 50%; transform: translateY(-50%); width: 50px; height: 50px; background-color: rgba(255, 255, 255, 0.2); border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 30px; color: white; cursor: pointer; z-index: 10000;"
;s.addEventListener("click",e=>{e.stopPropagation();if(o>0){o--;i.src=t[o];updateNavigation()}});n.appendChild(s)
;const r=document.createElement("div");r.innerHTML="›"
;r.style.cssText="position: absolute; right: 20px; top: 50%; transform: translateY(-50%); width: 50px; height: 50px; background-color: rgba(255, 255, 255, 0.2); border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 30px; color: white; cursor: pointer; z-index: 10000;"
;r.addEventListener("click",e=>{e.stopPropagation();if(o<t.length-1){o++;i.src=t[o];updateNavigation()}})
;n.appendChild(r);const a=document.createElement("div")
;a.style.cssText="position: absolute; top: 20px; left: 50%; transform: translateX(-50%); background-color: rgba(0, 0, 0, 0.7); color: white; padding: 8px 16px; border-radius: 20px; font-size: 14px; z-index: 10000;"
;n.appendChild(a);updateNavigation()}n.addEventListener("click",e=>{
if(e.target!==i&&!e.target?.closest('div[style*="position: absolute"]'))document.body.removeChild(n)})
;document.body.appendChild(n)}}class PreviewSelector{static create(){const e=CONFIG.get("maxPreviewImages")
;const t=document.createElement("a");t.href="javascript:;";t.textContent=`预览${e}张图`;t.title="点击切换预览图数量"
;t.style.cssText="text-decoration: none; cursor: pointer;";t.addEventListener("click",t=>{t.preventDefault()
;t.stopPropagation();let n;if(4===e)n=5;else n=4;CONFIG.set("maxPreviewImages",n);window.location.reload()});return t}}
class UIComponents{static createLinksElement(e){
if(0===e.ed2k.length&&0===e.magnet.length&&0===e.xunlei.length&&0===e.baidu.length)return null
;const t=n.createElement("div","sht-links");const createSection=(e,i,o)=>{if(0===i.length)return;i.forEach((e,i)=>{
const o=document.createElement("div");o.style.marginBottom="4px";const s=n.createElement("div","sht-link-item",e)
;s.title="点击复制链接";s.addEventListener("click",t=>{t.preventDefault();t.stopPropagation()
;navigator.clipboard.writeText(e).catch(()=>{})});o.appendChild(s);if(i>0){o.style.display="none"
;o.classList.add("hidden-link")}t.appendChild(o)})};createSection("ED2K链接:",e.ed2k);createSection("磁力链接:",e.magnet)
;createSection("迅雷网盘:",e.xunlei);createSection("百度网盘:",e.baidu)
;const i=e.ed2k.length+e.magnet.length+e.xunlei.length+e.baidu.length;if(i>1){const e=document.createElement("button")
;e.textContent=`显示剩余 ${i-1} 个链接`
;e.style.cssText="margin-top: 5px; cursor: pointer; border: 1px solid #ccc; background-color: #f0f0f0; padding: 5px 10px; border-radius: 4px;"
;let n=false;e.addEventListener("click",o=>{o.preventDefault();o.stopPropagation();n=!n
;t.querySelectorAll(".hidden-link").forEach(e=>{e.style.display=n?"block":"none"})
;e.textContent=n?"收起链接":`显示剩余 ${i-1} 个链接`});t.appendChild(e)}return t}static createNormalPageToggle(options){
const e=document.querySelector("#nv ul");if(!e)return;const t=document.createElement("li")
;const n=document.createElement("div");n.id="nav-toggle-infinite-scroll";const i=document.createElement("a")
;i.href="javascript:;";i.textContent="无缝翻页";n.appendChild(i);t.appendChild(n);e.appendChild(t)
;const o=document.createElement("li");const s=document.createElement("div");s.id="nav-preview-selector"
;const r=PreviewSelector.create();s.appendChild(r);o.appendChild(s);e.appendChild(o)
;let a=null===localStorage.getItem(options.storageKeyEnabled)?options.defaultEnabled||false:"true"===localStorage.getItem(options.storageKeyEnabled)
;const c=i.offsetWidth||68;const updateToggle=()=>{if(a){n.classList.add("a")
;i.style.cssText=`\n          background-color: #840000 !important;\n          background-image: url(/static/image/common/nv_a.png) !important;\n          background-repeat: no-repeat !important;\n          background-position: 50% -33px !important;\n          color: #fff !important;\n          font-weight: 700 !important;\n          height: 33px !important;\n          line-height: 33px !important;\n          padding: 0 8px !important;\n          min-width: ${c}px !important;\n          width: ${c}px !important;\n          box-sizing: border-box !important;\n          text-align: center !important;\n          display: inline-block !important;\n        `
}else{n.classList.remove("a");n.classList.remove("hover");i.style.cssText=""}};n.addEventListener("click",e=>{
e.preventDefault();a=!a;localStorage.setItem(options.storageKeyEnabled,String(a));updateToggle()
;a?options.onEnable():options.onDisable()});updateToggle();if(a)options.onEnable()}
static createSearchPageSingleToggle(e){const t=document.querySelector(".sttl.mbn");if(!t)return;t.style.display="flex"
;t.style.justifyContent="space-between";t.style.alignItems="center";const i=document.createElement("div")
;i.style.display="flex";i.style.alignItems="center";const o=document.createElement("div");o.style.display="flex"
;o.style.alignItems="center";const s=document.createElement("span");s.textContent="无缝翻页:"
;s.style.cssText="font-size: 13px; color: #666; margin-right: 6px; line-height: 20px;";o.appendChild(s)
;const r=n.createElement("label","sht-nav-toggle");const a=document.createElement("input");a.type="checkbox"
;const c=n.createElement("span","sht-nav-slider");r.appendChild(a);r.appendChild(c);o.appendChild(r)
;let l=null===localStorage.getItem(e.storageKeyEnabled)?e.defaultEnabled||false:"true"===localStorage.getItem(e.storageKeyEnabled)
;a.addEventListener("change",()=>{l=a.checked;localStorage.setItem(e.storageKeyEnabled,String(l))
;l?e.onEnable():e.onDisable()});a.checked=l;if(l)e.onEnable();i.appendChild(o);t.appendChild(i)}
static createSearchPageDualToggles(e,t){const i=document.querySelector(".sttl.mbn");if(!i)return;i.style.display="flex"
;i.style.justifyContent="space-between";i.style.alignItems="center";const o=document.createElement("div")
;o.style.display="flex";o.style.alignItems="center";o.style.gap="20px";const createToggle=(e,options)=>{
const t=document.createElement("div");t.style.display="flex";t.style.alignItems="center"
;const i=document.createElement("span");i.textContent=`${e}:`
;i.style.cssText="font-size: 13px; color: #666; margin-right: 6px; line-height: 20px;";t.appendChild(i)
;const o=n.createElement("label","sht-nav-toggle");const s=document.createElement("input");s.type="checkbox"
;const r=n.createElement("span","sht-nav-slider");o.appendChild(s);o.appendChild(r);t.appendChild(o)
;let a=null===localStorage.getItem(options.storageKeyEnabled)?options.defaultEnabled||false:"true"===localStorage.getItem(options.storageKeyEnabled)
;s.addEventListener("change",()=>{a=s.checked;localStorage.setItem(options.storageKeyEnabled,String(a))
;a?options.onEnable():options.onDisable()});s.checked=a;if(a)options.onEnable();return t}
;o.appendChild(createToggle("无缝翻页",e));o.appendChild(createToggle("移除隐藏贴",t));i.appendChild(o)}
static createFixedRestoreButton(config){if(window.activeRestoreButton)window.activeRestoreButton.remove()
;const e=n.createElement("div","sht-restore");e.id=config.id;e.innerHTML=config.labelHtml
;e.addEventListener("click",config.onClick);document.body.appendChild(e);window.activeRestoreButton=e;return e}
static createUnifiedInfoCard(e,t,i,o,s){e.innerHTML="";e.className="sht-card";const r=document.createElement("a")
;let a=t.textContent||"预览";a=a.replace(/\s+/g," ").trim();r.textContent=a;r.href=t.href;r.target="_blank"
;r.style.cssText="font-size: 16px; font-weight: bold; color: #2c5aa0; margin-bottom: 15px; line-height: 1.4; cursor: pointer; text-decoration: underline; display: block;"
;r.addEventListener("click",e=>{if(!e.ctrlKey&&!e.metaKey&&!e.shiftKey){e.preventDefault();window.open(t.href,"_blank")}
});e.appendChild(r);if(i.length>0){const t=n.createElement("div","sht-img-grid");i.forEach(e=>{
const o=e.getAttribute("file");const s=1===i.length?"sht-img-item-single":"sht-img-item"
;const r=n.createElement("div",s);const a=n.createElement("img","sht-img");a.src=o;r.addEventListener("click",e=>{
e.preventDefault();e.stopPropagation();Lightbox.create(o,i.map(e=>e.getAttribute("file")))});r.appendChild(a)
;t.appendChild(r)});n.setImageGridWidth(t,i.length);e.appendChild(t)}const c=this.createLinksElement(o);if(c){
c.className="sht-links";e.appendChild(c)}}}const i=class _Logger{static debug(e){}static info(e){void 0}
static error(e,t){void 0}static summary(e,t){}};i.PREFIX="[98堂]";let o=i;class ConcurrencyManager{constructor(e){
this.queue=[];this.activeCount=0;this.limit=e}async addTask(e){return new Promise((t,n)=>{this.queue.push({task:e,
resolve:t,reject:n});this._next()})}_next(){if(this.activeCount>=this.limit||0===this.queue.length)return
;this.activeCount++;const{task:e,resolve:t,reject:n}=this.queue.shift();e().then(t,n).finally(()=>{this.activeCount--
;this._next()})}}class PreviewCache{constructor(){this.cache=new Map;this.maxSize=50;this.normalTtl=600*1e3
;this.highTtl=1800*1e3}set(e,t,n="normal"){this.cleanup()
;if(this.cache.size>=this.maxSize)for(const[i,o]of this.cache.entries())if("normal"===o.priority){this.cache.delete(i)
;break}this.cache.set(e,{data:t,timestamp:Date.now(),priority:n})}get(e){const t=this.cache.get(e);if(!t)return null
;const n="high"===t.priority?this.highTtl:this.normalTtl;if(Date.now()-t.timestamp>n){this.cache.delete(e);return null}
return t.data}has(e){const t=this.cache.get(e);if(!t)return false
;const n="high"===t.priority?this.highTtl:this.normalTtl;if(Date.now()-t.timestamp>n){this.cache.delete(e);return false}
return true}cleanup(){const e=Date.now();for(const[t,n]of this.cache.entries()){
const i="high"===n.priority?this.highTtl:this.normalTtl;if(e-n.timestamp>i)this.cache.delete(t)}}clear(){
this.cache.clear()}size(){this.cleanup();return this.cache.size}}const s=new PreviewCache
;async function fetchWithTimeout(e,options={}){const{timeout:t=5e3,...n}=options;const i=new AbortController
;const o=setTimeout(()=>i.abort(),t);try{const t=await fetch(e,{...n,signal:i.signal});clearTimeout(o);return t
}catch(s){clearTimeout(o);throw s}}async function fetchWithCache(e,t="normal"){const n=s.get(e);if(n)return n;try{
const n=await fetchWithTimeout(e);const i=await n.text();const o=new DOMParser;const r=o.parseFromString(i,"text/html")
;const a=extractPostPreviewData(r);s.set(e,a,t);return a}catch(i){o.error("获取失败:",i);return null}}
function debounce(e,t){let n;return(...i)=>{clearTimeout(n);n=window.setTimeout(()=>e(...i),t)}}function throttle(e,t){
let n=0;return(...i)=>{const o=Date.now();if(o-n>=t){e(...i);n=o}}}class LinkExtractor{static extractAllLinks(e){
const t={ed2k:[],magnet:[],xunlei:[],baidu:[]};try{this.extractResourceLinks(e,t);this.extractHtmlNetworkDiskLinks(e,t)
;this.extractCodeBlockLinks(e,t);if(0===t.magnet.length)this.extractMagnetFromFullPage(e,t);this.deduplicateLinks(t)
;o.summary("提取完成",{"磁力":t.magnet.length,ED2K:t.ed2k.length,"迅雷":t.xunlei.length,"百度":t.baidu.length})}catch(n){
o.error("链接提取错误",n)}return t}static extractResourceLinks(e,t){
e.querySelectorAll('a[href^="magnet:"], a[href^="ed2k:"]').forEach(e=>{const n=e.href;if(n)this.categorizeLink(n,t)})
;e.querySelectorAll('a[href*="ed2k://"], a[href*="magnet:"]').forEach(e=>{const n=e.href;if(n)this.categorizeLink(n,t)})
;const n=e.body.textContent||"";const i=n.match(/ed2k:\/\/\|file\|[^|]+\|\d+\|[A-F0-9]+\|\//gi)
;if(i)i.forEach(e=>t.ed2k.push(e.trim()))}static extractHtmlNetworkDiskLinks(e,t){
e.querySelectorAll('a[href*="pan.xunlei.com/s/"], a[href*="pan.baidu.com/s/"]').forEach(e=>{
const n=e.getAttribute("href");if(!n)return
;if(n.includes("pan.xunlei.com/s/"))t.xunlei.push(this.combineNetworkDiskLink(e,n));else if(n.includes("pan.baidu.com/s/"))t.baidu.push(this.combineNetworkDiskLink(e,n))
})}static extractCodeBlockLinks(e,t){e.querySelectorAll(".blockcode").forEach(e=>{const n=e.textContent||""
;const i=n.match(/magnet:\?xt=urn:btih:[a-fA-F0-9]{40}/g);if(i)i.forEach(e=>t.magnet.push(e.trim()));else{
const e=n.match(/\b([a-fA-F0-9]{40})\b/g);if(e&&e.length<=3)e.forEach(e=>t.magnet.push(`magnet:?xt=urn:btih:${e}`))}
const o=n.match(/https:\/\/pan\.xunlei\.com\/s\/[a-zA-Z0-9_-]+/g);if(o)o.forEach(e=>t.xunlei.push(e.trim()))
;const s=n.match(/https:\/\/pan\.baidu\.com\/s\/[a-zA-Z0-9_-]+/g);if(s)s.forEach(e=>t.baidu.push(e.trim()))})}
static extractMagnetFromFullPage(e,t){const n=e.querySelector(".t_f, #postmessage, .pcb")
;const i=n?n.textContent||"":e.body.textContent||"";const o=i.match(/magnet:\?xt=urn:btih:[a-fA-F0-9]{40}/g)
;if(o)o.forEach(e=>t.magnet.push(e.trim()));else{const e=i.match(/\b([a-fA-F0-9]{40})\b/g)
;if(e)e.slice(0,3).forEach(e=>t.magnet.push(`magnet:?xt=urn:btih:${e}`))}}static categorizeLink(e,t){
if(e.startsWith("ed2k://"))t.ed2k.push(e);else if(e.startsWith("magnet:"))t.magnet.push(e);else if(e.includes("pan.xunlei.com"))t.xunlei.push(e);else if(e.includes("pan.baidu.com"))t.baidu.push(e)
}static deduplicateLinks(e){e.ed2k=[...new Set(e.ed2k)];e.magnet=[...new Set(e.magnet)];e.xunlei=[...new Set(e.xunlei)]
;e.baidu=[...new Set(e.baidu)]}static hasAnyContent(e){
return e.ed2k.length>0||e.magnet.length>0||e.xunlei.length>0||e.baidu.length>0}static combineNetworkDiskLink(e,t){
if(t.includes("?pwd="))return t;const n=(e.parentElement?.textContent||"").match(/提取码[::]\s*([a-zA-Z0-9]+)/)
;return n?`${t}?pwd=${n[1]}`:t}}function extractDownloadLinks(e){return LinkExtractor.extractAllLinks(e)}
function extractPostPreviewData(e){const t=Array.from(e.querySelectorAll("img.zoom")).filter(e=>{
const t=e.getAttribute("file");if(!t||t.includes("static"))return false;const n=parseInt(e.getAttribute("width")||"0")
;if(430===n)return false;const i=t.toLowerCase().endsWith(".png")&&(t.includes("7pzzv.us")||t.includes("iili.io"))
;return!i});const n=extractDownloadLinks(e);return{images:t,links:n}}const r=Object.freeze(Object.defineProperty({
__proto__:null,ConcurrencyManager:ConcurrencyManager,LinkExtractor:LinkExtractor,Logger:o,PreviewCache:PreviewCache,
debounce:debounce,extractDownloadLinks:extractDownloadLinks,extractPostPreviewData:extractPostPreviewData,
fetchWithCache:fetchWithCache,fetchWithTimeout:fetchWithTimeout,previewCache:s,throttle:throttle},Symbol.toStringTag,{
value:"Module"}));class SettingsApplier{static applyInitialStickPostHiding(){setTimeout(()=>{this.applyStickPostHiding()
},100)}static applyStickPostHidingToNewContent(){this.hideStickPostsAndAdminPosts(":not(.hide-processed)",true)}
static applyDefaultTimeSort(){const e=window.location.href;const t=new URLSearchParams(window.location.search)
;const n=t.get("mod");const i=t.get("fid");const o="forumdisplay"===n&&i||/forum-\d+-\d+\.html/.test(e)
;if(o&&"dateline"!==t.get("orderby"))this.redirectToTimeSortedPage(e,n,i)}static applyStickPostHiding(){
this.hideStickPostsAndAdminPosts("",false)}static hideStickPostsAndAdminPosts(e,t){
document.querySelectorAll(`#threadlisttableid tbody[id^="stickthread_"]${e}`).forEach(e=>{e.style.display="none"
;if(t)e.classList.add("hide-processed")})
;document.querySelectorAll(`#threadlisttableid tbody[id^="normalthread_"]${e}`).forEach(e=>{
const n=e.querySelector("em a");if(n&&n.textContent?.includes("版务管理"))e.style.display="none"
;if(t)e.classList.add("hide-processed")})}static redirectToTimeSortedPage(e,t,n){let i;if("forumdisplay"===t&&n){
const t=new URL(e);t.searchParams.set("orderby","dateline");t.searchParams.set("filter","author");i=t.href}else{
const t=e.match(/forum-(\d+)-(\d+)\.html/);if(t){const e=t[1];const n=t[2]
;i=`forum.php?mod=forumdisplay&fid=${e}&page=${n}&orderby=dateline&filter=author`}else return}window.location.href=i}}
class ThreadRenderer{static async processSingleThread(e){const t=e.href;const n=e.closest("tbody")
;if(!n||"imagePreviewTbody"===n.nextElementSibling?.id||n.querySelector(".searchImagePreview")||"true"===n.dataset.previewProcessed)return
;n.dataset.previewProcessed="true";try{const i=await fetchWithCache(t);if(!i)return;const{images:o,links:s}=i
;const r=o.slice(0,CONFIG.get("maxPreviewImages"));if(0===r.length&&!LinkExtractor.hasAnyContent(s))return
;this.renderSimpleThreadContent(e,n,r,s,t)}catch(i){o.error(`处理失败:`,i);if(n)n.dataset.previewProcessed="false"}}
static renderSimpleThreadContent(e,t,i,o,s){n.inject();const r=e.closest("tr");if(r)r.style.display="none"
;const a=document.createElement("div");UIComponents.createUnifiedInfoCard(a,e,i,o,s)
;const c=document.createElement("tbody");c.id="imagePreviewTbody";const l=document.createElement("tr")
;const d=document.createElement("td");d.colSpan=5;d.style.cssText="padding: 0; border: 0;";d.appendChild(a)
;l.appendChild(d);c.appendChild(l);t.after(c)}static async displayInitialThreads(){
const e=document.querySelectorAll("#threadlisttableid .s.xst")
;const t=new ConcurrencyManager(CONFIG.get("concurrencyLimit"));const n=Array.from(e).map(e=>t.addTask(async()=>{
const t=e.closest("th, td");if(t?.querySelector("em a")?.textContent?.includes("版务管理"))return
;await this.processSingleThread(e)}));await Promise.all(n)}static async processNewThreads(e){
const t=Array.from(e.querySelectorAll("tbody[id^=normalthread_]:not(.processed) a.s.xst"))
;e.querySelectorAll(".processed").length;await Promise.all(t.map((e,t)=>{const n=e.closest("tbody")
;n?.classList.add("processed");if(n?.querySelector("em a")?.textContent?.includes("版务管理"))return Promise.resolve()
;return this.processSingleThread(e)}))}}const a=class _ForumStateManager{static createPageNumElement(){
if(document.getElementById("page-number-display"))return;this.pageNumDisplay=document.createElement("div")
;this.pageNumDisplay.id="page-number-display"
;this.pageNumDisplay.style.cssText="position: fixed; z-index: 9998; user-select: none; display: none; background-color: rgba(50,50,50,0.5); color: white; padding: 5px; border-radius: 5px; font-size: 12px; text-align: center;"
;document.body.appendChild(this.pageNumDisplay)}static updateAndSaveForumState(){this.updateFloatingPageNumber()
;this.saveForumState()}static updateFloatingPageNumber(){if(!this.pageNumDisplay)return
;const e=document.getElementById("scrolltop");if(e&&e.offsetHeight>0&&"hidden"!==window.getComputedStyle(e).visibility){
const t=e.getBoundingClientRect();this.pageNumDisplay.style.display="block"
;this.pageNumDisplay.style.top=`${t.top-this.pageNumDisplay.offsetHeight-5}px`
;this.pageNumDisplay.style.left=`${t.left}px`;this.pageNumDisplay.style.width=`${t.width}px`
;this.pageNumDisplay.style.boxSizing="border-box";const n=document.querySelector(".pg strong")?.textContent||"1"
;this.pageNumDisplay.textContent=`第${n}页`}else this.pageNumDisplay.style.display="none"}static saveForumState(){
const e=document.querySelector("#pt .z a:last-of-type")?.textContent
;const t=document.querySelector(".pg strong")?.textContent||"1";if(e){
const n=document.querySelector('.pg a[href*="page="]')||document.querySelector('.pg a[href*="-"]');let i=null
;if(n)i=n.href.replace(/([?&]page=)\d+/,"$1__PAGE__").replace(/-\d+\.html/,"-__PAGE__.html")
;localStorage.setItem("lastForumState",JSON.stringify({sectionName:e,page:t,urlTemplate:i}))}}
static setupForumRestore(){const e=JSON.parse(localStorage.getItem("lastForumState")||"null")
;const t=document.querySelector("#pt .z a:last-of-type")?.textContent
;const n=parseInt(new URLSearchParams(window.location.search).get("page")||window.location.pathname.match(/-(\d+)\.html$/)?.[1]||"1",10)
;if(e&&e.sectionName&&e.urlTemplate){const i=e.sectionName===t;const o=parseInt(e.page,10)>n;if(!i||i&&o){
const t=`<span style="writing-mode: vertical-rl; text-orientation: mixed; font-size: 11px; color: white; letter-spacing: 2px;">恢复: ${e.sectionName} (${e.page})</span>`
;window.activeRestoreButton=UIComponents.createFixedRestoreButton({id:"restore-forum-btn",labelHtml:t,onClick:()=>{
const t=e.urlTemplate.replace("__PAGE__",e.page);window.location.href=t}})}}}static setupStateTracking(){
const e=throttle(()=>this.updateAndSaveForumState(),100);window.addEventListener("scroll",e,{passive:true})
;window.addEventListener("resize",e,{passive:true});const t=document.getElementById("scrolltop")
;if(t)new MutationObserver(e).observe(t,{attributes:true,attributeFilter:["style","class"]})}};a.pageNumDisplay=null
;let c=a;const l=class _ScrollHandler{static setDependencies(e){this.threadRenderer=e.threadRenderer
;this.settingsApplier=e.settingsApplier;this.forumStateManager=e.forumStateManager}static enableInfiniteScroll(){
if(this.scrollHandler)return;const e=document.querySelector(".pg a.nxt");if(e)this.nextUrl=e.href;else return
;this.createLoadingIndicator();this.setupScrollListener();this.forumStateManager?.updateAndSaveForumState()}
static disableInfiniteScroll(){if(this.scrollHandler){window.removeEventListener("scroll",this.scrollHandler)
;this.scrollHandler=null}if(this.loadingIndicator)this.loadingIndicator.style.display="none"
;this.forumStateManager?.updateAndSaveForumState();this.redirectToCurrentPage()}static createLoadingIndicator(){
if(!this.loadingIndicator){this.loadingIndicator=document.createElement("div")
;this.loadingIndicator.textContent="正在加载更多内容..."
;this.loadingIndicator.style.cssText="text-align: center; padding: 20px; display: none;"
;document.querySelector("#threadlist")?.insertAdjacentElement("afterend",this.loadingIndicator)}}
static setupScrollListener(){this.scrollHandler=async()=>{if(this.isLoading||!this.nextUrl)return
;if(document.documentElement.scrollTop+document.documentElement.clientHeight>=document.documentElement.scrollHeight-500)await this.loadNextPage()
};window.addEventListener("scroll",this.scrollHandler,{passive:true})}static async loadNextPage(){this.isLoading=true
;if(this.loadingIndicator)this.loadingIndicator.style.display="block";try{const e=await fetch(this.nextUrl)
;const t=await e.text();const n=new DOMParser;const i=n.parseFromString(t,"text/html")
;const o=document.querySelector("table#threadlisttableid")
;const s=i.querySelectorAll("table#threadlisttableid > tbody[id^=normalthread_]");if(s.length>0&&o){
await this.appendNewContent(o,s);this.updatePagination(i)}this.updateNextPageUrl()}catch(e){o.error("翻页失败:",e)
;if(this.loadingIndicator)this.loadingIndicator.textContent="加载失败"}finally{this.isLoading=false
;this.updateLoadingIndicator();this.forumStateManager?.updateAndSaveForumState()}}static async appendNewContent(e,t){
const n=document.createDocumentFragment();t.forEach(e=>n.appendChild(e));e.appendChild(n)
;if(this.threadRenderer)await this.threadRenderer.processNewThreads(e)
;if(this.settingsApplier)this.settingsApplier.applyStickPostHidingToNewContent()}static updatePagination(e){
const t=e.querySelector(".pg");const n=document.querySelector(".pg");if(n&&t)n.replaceWith(t)}
static updateNextPageUrl(){const e=document.querySelector(".pg a.nxt");this.nextUrl=e?e.href:""}
static updateLoadingIndicator(){
if(this.loadingIndicator)if(this.nextUrl)this.loadingIndicator.style.display="none";else this.loadingIndicator.textContent="已加载全部内容"
}static redirectToCurrentPage(){const e=parseInt(document.querySelector(".pg strong")?.textContent||"1",10)
;if(isNaN(e))window.location.reload();else{const t=new URL(window.location.href);t.searchParams.set("page",e.toString())
;window.location.href=t.toString()}}};l.scrollHandler=null;l.isLoading=false;l.nextUrl="";l.loadingIndicator=null
;l.threadRenderer=null;l.settingsApplier=null;l.forumStateManager=null;let d=l;class NormalPageManager{static init(){
c.createPageNumElement();c.setupForumRestore();c.setupStateTracking();d.setDependencies({threadRenderer:ThreadRenderer,
settingsApplier:SettingsApplier,forumStateManager:c});this.setupToggle();ThreadRenderer.displayInitialThreads()
;SettingsApplier.applyInitialStickPostHiding();SettingsApplier.applyDefaultTimeSort()}static setupToggle(){
UIComponents.createNormalPageToggle({storageKeyEnabled:"normalPageInfiniteScroll",onEnable:()=>d.enableInfiniteScroll(),
onDisable:()=>d.disableInfiniteScroll(),defaultEnabled:false})}}class SearchStateManager{
static updateAndSaveSearchState(){const e=document.querySelector(".sttl .emfont")?.textContent;if(e){let t="1"
;const n=document.querySelector(".pg strong");if(n)t=n.textContent||"1";else if(!document.querySelector(".pg a.nxt")){
const e=window.location.href.match(/[?&]page=(\d+)/);if(e)t=e[1]}const i=new URLSearchParams(window.location.search)
;i.delete("page");const o=window.location.pathname+"?"+i.toString()
;localStorage.setItem("lastSearchState",JSON.stringify({keyword:e,page:t,baseUrl:o}))}}static setupSearchRestore(){
const e=JSON.parse(localStorage.getItem("lastSearchState")||"null")
;const t=document.querySelector(".sttl .emfont")?.textContent
;const n=parseInt(new URLSearchParams(window.location.search).get("page")||"1",10);if(e&&e.keyword){
const i=e.keyword===t;const o=parseInt(e.page,10)>n;if(!i||i&&o){
const t=`<span style="writing-mode: vertical-rl; text-orientation: mixed; font-size: 11px; color: white; letter-spacing: 2px;">恢复: ${e.keyword} (${e.page})</span>`
;window.activeRestoreButton=UIComponents.createFixedRestoreButton({id:"restore-search-btn",labelHtml:t,onClick:()=>{
window.location.href=`${e.baseUrl}&page=${e.page}`}})}}}}class SearchLayoutManager{static applyStickyLayout(){
const e=document.getElementById("ct");if(!e)return;const t=document.getElementById("toptb")
;const n=e.querySelector("form.searchform");const i=e.querySelector(".tl");if(!i)return;const o=i.querySelector(".sttl")
;const s=i.querySelector("#threadlist");const r=i.querySelector(".pgs");const a=document.getElementById("ft")
;if(!s)return;if(a)a.remove();const c=document.createElement("div");if(t)c.appendChild(t);if(n)c.appendChild(n)
;if(o)c.appendChild(o);const l=document.createElement("div");l.id="sticky-scroll-container";l.appendChild(s)
;const d=document.createElement("div");if(r)d.appendChild(r);e.innerHTML="";e.appendChild(c);e.appendChild(l)
;e.appendChild(d);this.injectLayoutStyles()}static injectLayoutStyles(){const e=document.createElement("style")
;e.textContent=`\n      html, body { height: 100%; overflow: hidden; margin: 0; }\n      #ct { height: 100% !important; display: flex; flex-direction: column; }\n      #ct > div:nth-child(1) { flex-shrink: 0; overflow: hidden; }\n      #sticky-scroll-container { flex-grow: 1; overflow-y: auto; border-top: 1px solid #e0e0e0; border-bottom: 1px solid #e0e0e0; position: relative; }\n      #ct > div:nth-child(3) { flex-shrink: 0; padding: 10px 0; background-color: #fff; border-top: 1px solid #e0e0e0; }\n      .pgs.cl { margin-bottom: 0 !important; }\n      .tl { width: 100% !important; max-width: none !important; padding: 0 20px !important; box-sizing: border-box !important; }\n      #threadlist { width: 100% !important; max-width: none !important; }\n      #threadlist > ul { width: 100% !important; max-width: none !important; padding: 0 !important; margin: 0 !important; }\n      .pbw { width: 100% !important; max-width: none !important; box-sizing: border-box !important; margin: 25px 0 !important; }\n    `
;document.head.appendChild(e)}static createRichPostCard(e,t,i,o,s){n.inject()
;UIComponents.createUnifiedInfoCard(e,t,i,o,s)}}const h=class _SearchContentProcessor{
static async processSearchContainer(e,t=false){await this.displaySearchPreviews(e,t)
;return e.querySelectorAll(".pbw").length}static async displaySearchPreviews(e,t=false){
const n=e.querySelectorAll(".xs3 a");const i=Array.from(n).filter(e=>{const t=e.closest(".pbw")
;return e.href&&e.href.includes("thread")&&t&&!t.classList.contains("hidden-transparent")})
;const s=new ConcurrencyManager(CONFIG.get("concurrencyLimit"));const r=[];const a=i.map(e=>s.addTask(async()=>{try{
const n=e.href;const o=e.closest(".pbw")
;if(!o||o.querySelector(".searchImagePreview")||o.querySelector('[id*="imagePreview"]')||"true"===o.dataset.previewProcessed)return
;o.dataset.previewProcessed="true";const s=t&&i.indexOf(e)<5;const a=await fetchWithCache(n,s?"high":"normal");if(!a){
r.push({pbwContainer:o,link:e,hasContent:false,processed:false,isHidden:this.isHiddenPost(o)});return}
const{images:c,links:l}=a;const d=c.slice(0,CONFIG.get("maxPreviewImages"))
;if(!(d.length>0||LinkExtractor.hasAnyContent(l))){r.push({pbwContainer:o,link:e,hasContent:false,processed:false,
isHidden:this.isHiddenPost(o)});return}this.createRichPostCard(o,e,d,l,n);r.push({pbwContainer:o,link:e,hasContent:true,
processed:true,isHidden:false})}catch(n){o.error("处理失败:",n);const t=e.closest(".pbw");if(t){
t.dataset.previewProcessed="false";r.push({pbwContainer:t,link:e,hasContent:false,processed:false,
isHidden:this.isHiddenPost(t)})}}}));await Promise.all(a);this.handleInvalidAndHiddenPosts(r)}
static createRichPostCard(e,t,n,i,o){SearchLayoutManager.createRichPostCard(e,t,n,i,o)}
static handleInvalidAndHiddenPosts(e){e.filter(e=>!e.hasContent&&!e.processed).forEach(e=>{
if(e.isHidden)e.pbwContainer.remove()})}static isHiddenPost(e){const t=e.textContent||""
;const n=["该主题需要回复才能浏览","内容隐藏需要,请点击进去查看","隐藏内容","此帖被隐藏","权限不足","积分不足","回复可见","购买主题","付费内容","需要权限","您无权访问","内容隐藏需要"].some(e=>t.includes(e))
;const i=!!(e.querySelector(".locked")||e.querySelector(".permission-denied")||e.querySelector(".reply-to-view"))
;const o=e.querySelector(".xs3 a")?.textContent||""
;const s=o.includes("隐藏")||o.includes("权限")||o.includes("回复可见")||o.includes("积分不足");return n||i||s}
static reprocessExistingPosts(){const e=document.querySelectorAll("#threadlist .pbw");e.forEach(e=>{
if(this.isHiddenPost(e))e.remove()})}static async showCachedContent(e){for(const t of e){const e=t.href
;const n=t.closest(".pbw");if(!n||"true"===n.dataset.previewProcessed)continue
;const{previewCache:i}=await Promise.resolve().then(()=>r);if(i.has(e)){const o=i.get(e);if(o){
n.dataset.previewProcessed="true";const{images:i,links:s}=o;const r=i.slice(0,CONFIG.get("maxPreviewImages"))
;if(r.length>0||LinkExtractor.hasAnyContent(s))this.createRichPostCard(n,t,r,s,e)}}}}
static async loadRemainingContent(e){if(0===e.length)return
;const t=new ConcurrencyManager(CONFIG.get("concurrencyLimit"));const n=e.map(e=>t.addTask(async()=>{const t=e.href
;const n=e.closest(".pbw");if(!n||"true"===n.dataset.previewProcessed)return;n.dataset.previewProcessed="true"
;const i=await fetchWithCache(t,"normal");if(i){const{images:o,links:s}=i
;const r=o.slice(0,CONFIG.get("maxPreviewImages"))
;if(r.length>0||LinkExtractor.hasAnyContent(s))this.createRichPostCard(n,e,r,s,t)}}));await Promise.allSettled(n)
;o.summary("后续加载",{"加载完成":e.length})}};h.MIN_POSTS_FOR_TRIGGER=5;let p=h;const u=class _SearchScrollManager{
static setContentProcessor(e){this.contentProcessor=e}static initializePagination(){
const e=document.querySelector(".pg");if(e?.querySelector("a.nxt"))this.nextPageUrl=e.querySelector("a.nxt").href
;const t=document.createElement("div");t.id="search-loading-indicator"
;t.style.cssText="text-align: center; padding: 20px; display: none;"
;document.getElementById("sticky-scroll-container")?.appendChild(t)}static enableInfiniteScroll(){
if(this.scrollHandler)return;const e=document.getElementById("sticky-scroll-container");if(e){
this.scrollHandler=debounce(()=>{if(this.isLoading||!this.nextPageUrl)return
;const t=this.getVisiblePostsCount()<this.MIN_VISIBLE_POSTS?2*e.clientHeight:.8*e.clientHeight
;if(e.scrollTop+e.clientHeight>=e.scrollHeight-t)this.loadNext()},150);e.addEventListener("scroll",this.scrollHandler,{
passive:true});this.startContentMonitoring();this.waitForImages(e).then(()=>{this.checkAndEnsureContent()})}}
static disableInfiniteScroll(){if(this.scrollHandler){const e=document.getElementById("sticky-scroll-container")
;if(e)e.removeEventListener("scroll",this.scrollHandler);this.scrollHandler=null}this.stopContentMonitoring()
;const e=document.getElementById("search-loading-indicator");if(e)e.style.display="none"
;const t=parseInt(document.querySelector(".pg strong")?.textContent||"1",10);if(isNaN(t))window.location.reload();else{
const e=new URL(window.location.href);e.searchParams.set("page",t.toString());window.location.href=e.toString()}}
static async loadNext(){if(this.isLoading||!this.nextPageUrl)return;this.isLoading=true
;const e=document.getElementById("search-loading-indicator");if(e){e.style.display="block";e.textContent="正在加载..."}try{
const e=await this.fetchWithTimeout(this.nextPageUrl);const t=await e.text();const n=new DOMParser
;const i=n.parseFromString(t,"text/html");const o=document.querySelector("#threadlist > ul")
;const s=i.querySelector("#threadlist > ul");if(s&&s.children.length>0&&o){
if(this.contentProcessor&&this.contentProcessor.processSearchContainer)await this.contentProcessor.processSearchContainer(s,false)
;Array.from(s.children).forEach(e=>{o.appendChild(e)})}const r=i.querySelector(".pg")
;const a=document.querySelector(".pg")
;if(a&&r)a.replaceWith(r);else if(!a&&r)document.querySelector(".tl")?.appendChild(r)
;const c=document.querySelector(".pg a.nxt");this.nextPageUrl=c?c.href:""}catch(t){o.error("翻页失败:",t)
;if(e)e.textContent="加载失败";this.nextPageUrl=""}finally{this.isLoading=false
;if(e)if(this.nextPageUrl)e.style.display="none";else e.textContent="已加载全部内容"}}
static async fetchWithTimeout(e,options={}){const t=new AbortController;const n=setTimeout(()=>t.abort(),8e3);try{
const i=await fetch(e,{...options,signal:t.signal});clearTimeout(n);return i}catch(i){clearTimeout(n);throw i}}
static getVisiblePostsCount(){const e=document.querySelectorAll("#threadlist .pbw");let t=0;e.forEach(e=>{
const n=window.getComputedStyle(e);if("none"!==n.display&&"hidden"!==n.visibility&&"0"!==n.opacity)t++});return t}
static startContentMonitoring(){if(this.contentObserver)return
;const e=document.getElementById("sticky-scroll-container");if(e){this.contentObserver=new MutationObserver(()=>{
setTimeout(()=>{this.checkAndEnsureContent()},100)});this.contentObserver.observe(e,{childList:true,subtree:true})}}
static stopContentMonitoring(){if(this.contentObserver){this.contentObserver.disconnect();this.contentObserver=null}}
static async checkAndEnsureContent(){if(this.isLoading||!this.nextPageUrl)return
;const e=document.getElementById("sticky-scroll-container");if(!e)return;const t=this.getVisiblePostsCount()
;const n=e.scrollHeight;const i=e.clientHeight;const s=n/i
;if(t<this.MIN_VISIBLE_POSTS||s<this.MIN_CONTENT_HEIGHT_RATIO){
o.debug(`内容不足,自动加载更多 - 可见帖子: ${t}, 高度比例: ${s.toFixed(2)}`);await this.loadNext();setTimeout(()=>{
this.checkAndEnsureContent()},500)}}static waitForImages(e){const t=Array.from(e.querySelectorAll("img"))
;if(0===t.length)return Promise.resolve();const n=t.map(e=>new Promise(t=>{if(e.complete)t();else{
e.addEventListener("load",()=>t(),{once:true});e.addEventListener("error",()=>t(),{once:true})}}))
;return Promise.all(n).then(()=>{})}static async ensureSufficientContent(){let e=0;const t=10
;while(this.nextPageUrl&&e<t){const t=document.getElementById("sticky-scroll-container");if(!t)break
;const n=t.scrollHeight;const i=t.clientHeight;const o=document.querySelectorAll("#threadlist .pbw").length
;if(n>1.5*i||o>=5)break;await this.loadNext();e++;await new Promise(e=>setTimeout(e,300))}}};u.scrollHandler=null
;u.isLoading=false;u.nextPageUrl="";u.contentObserver=null;u.MIN_VISIBLE_POSTS=8;u.MIN_CONTENT_HEIGHT_RATIO=1.8
;u.contentProcessor=null;let g=u;class SearchPageManager{static init(){SearchLayoutManager.applyStickyLayout()
;SearchStateManager.setupSearchRestore();this.setupToggle();setTimeout(()=>{this.setupInitialContent()},0)}
static async setupInitialContent(){const e=document.querySelector("#threadlist > ul");if(e){g.initializePagination()
;g.setContentProcessor(p);p.reprocessExistingPosts();await this.processContentInPhases(e)}}
static async processContentInPhases(e){const t=Array.from(e.querySelectorAll(".xs3 a"))
;await p.showCachedContent(t.slice(0,8));setTimeout(async()=>{const e=[...t.slice(0,8).filter(e=>{
const t=e.closest(".pbw");return t&&"true"!==t.dataset.previewProcessed}),...t.slice(8)];await p.loadRemainingContent(e)
;await g.ensureSufficientContent()},100)}static setupToggle(){UIComponents.createSearchPageSingleToggle({
storageKeyEnabled:"searchPageInfiniteScroll",onEnable:()=>g.enableInfiniteScroll(),
onDisable:()=>g.disableInfiniteScroll(),defaultEnabled:true})}}window.activeRestoreButton=null
;if("loading"===document.readyState)document.addEventListener("DOMContentLoaded",initScript);else initScript()
;function initScript(){try{
if(window.location.href.includes("search.php"))SearchPageManager.init();else NormalPageManager.init()}catch(e){void 0}}
})();