gcbt-预览

优化国产BT列表页面样式,添加帖子图片预览和磁力链接获取功能

// ==UserScript==
// @name         gcbt-预览
// @version      1.1.4
// @namespace    https://sleazyfork.org/zh-CN/users/1461640-%E6%98%9F%E5%AE%BF%E8%80%81%E9%AD%94
// @author       星宿老魔
// @description  优化国产BT列表页面样式,添加帖子图片预览和磁力链接获取功能
// @match        https://gcbt.net/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=gcbt.net
// @license      MIT
// @grant        GM_xmlhttpRequest
// @connect      gcbt.net
// @run-at       document-start
// ==/UserScript==

(function(){"use strict";function createElement(e,t=[],n={}){const i=document.createElement(e)
;if(t.length)i.className=t.join(" ");Object.assign(i,n);return i}function waitForDOMContentLoaded(){
return new Promise(e=>{if("loading"===document.readyState)document.addEventListener("DOMContentLoaded",()=>e());else e()
})}const CONFIG={selectors:{postList:"article.post-list",contentArea:".content-area",entryTitle:"h2.entry-title a",
metaDate:"li.meta-date time",pagination:"main .numeric-pagination",
entryContent:".entry-content, .entry-wrapper, .article-content",
imageSelectors:[".entry-content img",".entry-wrapper img"]},cacheTTL:24*60*60*1e3,maxPreviewImages:5,concurrentLimit:15}
;const e=class _StyleManager{static init(){if(this.styleInjected)return;this.injectStyles();this.styleInjected=true}
static injectStyles(){
const e=`article.post-list{visibility:hidden}.torrent-list-container{width:100%;margin:0;padding:0}.item-container{margin-bottom:15px;border-radius:6px;overflow:hidden;border:1px solid #e1e8ed;box-shadow:0 2px 8px rgba(0,0,0,.05);background:#fff}.item-container:hover{box-shadow:0 4px 12px rgba(0,0,0,.1)}.item-title-row{padding:15px;border-bottom:1px solid #f0f0f0;border-left:4px solid #1890ff;transition:background-color .2s}.item-container:nth-child(odd) .item-title-row{background:#f8f9fa}.item-container:nth-child(even) .item-title-row{background:#f0f8ff}.item-title-row:hover{background:#f0f8ff}.item-title{font-weight:600;color:#333;font-size:15px}.item-title a{color:#333;text-decoration:none}.item-title a:hover{color:#1890ff!important}.item-preview{padding:15px;background:#fff}.item-meta-info{display:flex;justify-content:space-between;align-items:center;margin-bottom:10px;padding-bottom:10px;border-bottom:1px dashed #eaeaea}.item-date,.item-size{padding:3px 8px;border-radius:3px;font-size:12px}.item-date{background:rgba(24,144,255,.08)}.item-size{background:rgba(230,126,34,.08);color:#e67e22;font-weight:500}.item-loading{text-align:center;padding:15px;color:#666}#gcbt-image-viewer{display:none;position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,.85);z-index:9999;justify-content:center;align-items:center;flex-direction:column}.gcbt-viewer-close{position:absolute;top:20px;right:20px;color:white;font-size:32px;cursor:pointer;z-index:10000}.gcbt-viewer-img-container{display:flex;justify-content:center;align-items:center;height:100%;width:100%}.gcbt-viewer-img{object-fit:contain}.gcbt-nav-btn{position:absolute;top:50%;transform:translateY(-50%);color:white;font-size:36px;cursor:pointer;padding:10px;z-index:10000;user-select:none}.gcbt-nav-btn.prev{left:20px}.gcbt-nav-btn.next{right:20px}.gcbt-viewer-counter{position:absolute;bottom:20px;color:white;font-size:14px;padding:5px 10px;background:rgba(0,0,0,.5);border-radius:4px;z-index:10000}.preview-content{padding:5px}.preview-img-container{display:flex;gap:10px;justify-content:flex-start;padding:5px 0;margin:5px 0}.preview-img-wrapper{cursor:pointer;border-radius:4px;overflow:hidden;box-shadow:0 2px 8px rgba(0,0,0,.1);transition:transform .2s;height:300px;flex:0 0 auto;position:relative}.preview-img-wrapper:hover{transform:translateY(-3px)}.preview-img{width:100%;height:100%;object-fit:cover;border-radius:3px;border:1px solid #ddd}.preview-img-wrapper.is-vertical .preview-img{object-fit:contain}.loading-indicator{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);font-size:12px;color:#999;z-index:1;background:rgba(255,255,255,.8);padding:4px 8px;border-radius:3px}.preview-info-container{margin-top:15px;padding-top:12px;border-top:1px dashed #e1e8ed}.action-bar{display:flex;justify-content:space-between;align-items:center;margin-bottom:10px}.gcbt-magnet-btn{padding:5px 14px;font-size:13px;background:#409eff;color:white;border:none;border-radius:4px;cursor:pointer;box-shadow:0 2px 4px rgba(0,0,0,.1);margin:5px 0}.gcbt-magnet-btn:hover{background:#1890ff!important;box-shadow:0 2px 6px rgba(24,144,255,.2)!important}.magnet-link-container{margin:8px 0;padding:10px 12px;display:none;width:100%;background:#f0f8ff;border-radius:4px;border:1px solid #d0e1f9;font-size:13px;word-break:break-all}.magnet-link-container a{color:#1890ff;text-decoration:none}.rollbar-item.tap-dark,.navbar-button:has(.mdi-brightness-4){display:none!important}body[class*="dark"],html[class*="dark"]{background:#fff!important;color:#333!important}`
;const t=createElement("style",[],{id:"gcbt-styles",textContent:e});document.documentElement.appendChild(t)}
static unhidePosts(){const e=document.getElementById("gcbt-styles")
;if(e)e.textContent=e.textContent?.replace("article.post-list { visibility: hidden; }","")||""}};e.styleInjected=false
;let t=e;class DomManager{static cleanPage(){document.body.classList.remove("dark","dark-mode","night-mode")
;document.documentElement.classList.remove("dark","dark-mode","night-mode");document.body.style.backgroundColor="#fff"
;document.body.style.color="#333"}static optimizeListPage(){const e=document.querySelectorAll(CONFIG.selectors.postList)
;if(!e.length)return false;const t=createElement("div",["torrent-list-container"])
;const n=document.querySelector(CONFIG.selectors.contentArea);if(!n)return false;e.forEach(e=>{
const n=e.querySelector(CONFIG.selectors.entryTitle);if(!n)return;const i=e.querySelector(CONFIG.selectors.metaDate)
;const o=createElement("a",[],{href:n.href,target:"_blank",textContent:n.textContent})
;o.setAttribute("data-thread-url",n.href);const r=createElement("div",["item-title"]);r.appendChild(o)
;const a=createElement("div",["item-title-row"]);a.appendChild(r);const s=createElement("span",["item-date"],{
textContent:i?i.textContent?.trim().replace(/\s*前$/,""):""});const c=createElement("div",["item-meta-info"])
;c.appendChild(s);const l=createElement("div",["item-preview"]);if(i)l.appendChild(c)
;l.appendChild(createElement("div",["item-loading"],{textContent:"🔄 正在加载预览..."}))
;const d=createElement("div",["item-container"]);d.append(a,l);t.appendChild(d)});const i=document.querySelector("main")
;const o=i?i.querySelector(CONFIG.selectors.pagination):null;n.innerHTML="";n.appendChild(t);if(o){
const e=o.cloneNode(true);e.style.margin="20px 0 0 0";n.appendChild(e)}return true}static updateTitleSize(e,t){
if(!t)return;const n=e.querySelector(".item-title");if(!n)return;let i=n.querySelector(".item-size");if(!i){
i=createElement("span",["item-size"]);i.style.marginLeft="10px";n.appendChild(i)}i.textContent=`【影片容量】:${t}`}}
const n=class _LightboxManager{static init(){if(this.initialized||document.getElementById("gcbt-image-viewer"))return
;const e=createElement("img",["gcbt-viewer-img"]);const t=createElement("div",["gcbt-viewer-counter"])
;const n=createElement("div",["gcbt-nav-btn","prev"],{innerHTML:"❮"})
;const i=createElement("div",["gcbt-nav-btn","next"],{innerHTML:"❯"});const o=createElement("div",[],{
id:"gcbt-image-viewer"});const r=createElement("div",["gcbt-viewer-img-container"])
;const a=createElement("div",["gcbt-viewer-close"],{innerHTML:"×"});r.appendChild(e);o.append(a,n,i,t,r)
;document.body.appendChild(o);const updateImage=o=>{if(!this.viewerState.images.length)return
;this.viewerState.currentIndex=(o+this.viewerState.images.length)%this.viewerState.images.length
;const r=this.viewerState.images[this.viewerState.currentIndex];this.updateImageSize(e,r)
;t.textContent=`${this.viewerState.currentIndex+1} / ${this.viewerState.images.length}`
;const a=this.viewerState.images.length>1;n.style.display=i.style.display=a?"block":"none"};const closeViewer=()=>{
o.style.display="none";document.body.style.overflow=""};e.addEventListener("click",e=>e.stopPropagation())
;a.addEventListener("click",closeViewer);o.addEventListener("click",closeViewer);n.addEventListener("click",e=>{
e.stopPropagation();updateImage(this.viewerState.currentIndex-1)});i.addEventListener("click",e=>{e.stopPropagation()
;updateImage(this.viewerState.currentIndex+1)});document.addEventListener("keydown",e=>{
if("none"!==o.style.display)if("Escape"===e.key)closeViewer();else if("ArrowLeft"===e.key)updateImage(this.viewerState.currentIndex-1);else if("ArrowRight"===e.key)updateImage(this.viewerState.currentIndex+1)
});this.initialized=true}static show(e,t=0){if(!this.initialized)this.init();this.viewerState.images=e
;this.viewerState.currentIndex=t;const n=document.getElementById("gcbt-image-viewer")
;const i=document.querySelector(".gcbt-viewer-img");const o=document.querySelector(".gcbt-viewer-counter")
;const r=document.querySelector(".gcbt-nav-btn.prev");const a=document.querySelector(".gcbt-nav-btn.next")
;if(n&&i&&o&&r&&a){n.style.display="flex";document.body.style.overflow="hidden";const s=e[t];this.updateImageSize(i,s)
;o.textContent=`${t+1} / ${e.length}`;const c=e.length>1;r.style.display=a.style.display=c?"block":"none"}}
static updateImageSize(e,t){e.style.width="";e.style.height="";e.style.maxWidth="";e.style.maxHeight=""
;e.style.display="none";e.onload=()=>{e.style.display="block";const t=.9*window.innerWidth;const n=.9*window.innerHeight
;const i=e.naturalWidth;const o=e.naturalHeight;if(i<300&&o<300){const r=2*i;const a=2*o;if(r<=t&&a<=n){
e.style.width=r+"px";e.style.height=a+"px"}else this.scaleImageToFit(e,r,a,t,n)
}else if(i>t||o>n)this.scaleImageToFit(e,i,o,t,n);else{e.style.width=i+"px";e.style.height=o+"px"}};e.onerror=()=>{
e.style.display="block";e.textContent="图片加载失败"};e.src=t}static scaleImageToFit(e,t,n,i,o){const r=i/t;const a=o/n
;const s=Math.min(r,a);const c=t*s;const l=n*s;e.style.width=c+"px";e.style.height=l+"px"}};n.initialized=false
;n.viewerState={images:[],currentIndex:0};let i=n;class ApiClient{static fetchPage(e){return new Promise((t,n)=>{
GM_xmlhttpRequest({method:"GET",url:e,onload:e=>{if(e.status>=200&&e.status<300){
const n=(new DOMParser).parseFromString(e.responseText,"text/html");t(n)}else n(new Error(`请求失败,状态码: ${e.status}`))},
onerror:e=>n(e),ontimeout:()=>n(new Error("请求超时"))})})}static async fetchMagnetLink(e){const t=await this.fetchPage(e)
;const n=t.body.innerHTML;let i=n.match(/(magnet:\?xt=urn:btih:[a-f0-9]{40})/i);if(i)return i[0]
;const o=n.match(/(?:^|:|:|;|;|】|\])\s*([0-9a-zA-Z]{40})/i);if(o)return`magnet:?xt=urn:btih:${o[1]}`;return null}}
class PreviewProcessor{static async processAll(){
const e=document.querySelectorAll(".torrent-list-container a[data-thread-url]");const t=[];const n=[];e.forEach(e=>{
const i=e.closest(".item-container");if(i&&this.isInViewport(i))t.push(e);else n.push(e)})
;const i=Math.min(CONFIG.concurrentLimit,t.length);for(let o=0;o<t.length;o+=i){const e=t.slice(o,o+i)
;Promise.all(e.map(e=>this.processSingle(e)))}if(n.length>0)setTimeout(()=>{const e=CONFIG.concurrentLimit
;for(let t=0;t<n.length;t+=e){const i=n.slice(t,t+e);setTimeout(()=>{Promise.all(i.map(e=>this.processSingle(e)))
},100*Math.floor(t/e))}},100)}static isInViewport(e){const t=e.getBoundingClientRect()
;return t.top<window.innerHeight&&t.bottom>0}static async processSingle(e){const t=e.closest(".item-container")
;const n=t?.querySelector(".item-preview");if(!n)return;const i=e.href;try{const e=await ApiClient.fetchPage(i)
;const o={}
;const r=Array.from(e.querySelectorAll(CONFIG.selectors.imageSelectors.join(","))).slice(0,CONFIG.maxPreviewImages)
;o.imgUrls=r.map(e=>e.src||e.getAttribute("data-src")).filter(Boolean)
;const a=e.querySelector(CONFIG.selectors.entryContent);const s=a?.textContent||""
;const c=s.match(/【影片(?:大小|容量)】:([0-9.]+\s*(?:MB|GB|M|G|T|TB))/i);if(c?.[1])o.filmSize=c[1];this.renderPreview(n,t,o,i)
}catch(o){n.innerHTML="加载预览失败";n.className="item-loading";n.style.color="#f56c6c";console.error("加载预览失败",o)}}
static renderPreview(e,t,n,o){e.innerHTML="";if(!n.imgUrls||0===n.imgUrls.length){e.textContent="没有找到预览图片";return}
const r=createElement("div",["preview-content"]);const a=createElement("div",["preview-img-container"])
;n.imgUrls.forEach((e,t)=>{const o=createElement("div",["preview-img-wrapper"])
;const r=createElement("img",["preview-img"]);o.style.backgroundColor="#f5f5f5";o.style.position="relative"
;const s=createElement("div",["loading-indicator"]);s.textContent="加载中..."
;s.style.cssText="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); font-size: 12px; color: #999; z-index: 1;"
;o.appendChild(s);r.style.opacity="0";r.style.transition="opacity 0.3s ease";this.preloadImageDNS(e);r.onload=()=>{
r.style.opacity="1";s.remove();o.style.backgroundColor=""};r.onerror=()=>{s.textContent="加载失败";s.style.color="#f56c6c"
;setTimeout(()=>{o.style.display="none"},1500)};this.preloadImage(e).then(t=>{const n=t.naturalHeight>t.naturalWidth
;if(n)o.classList.add("is-vertical");r.src=e}).catch(()=>{r.src=e});o.appendChild(r);o.addEventListener("click",e=>{
e.preventDefault();e.stopPropagation();i.show(n.imgUrls,t)});a.appendChild(o)})
;if(a.children.length>0)this.setImageGridWidth(a,a.children.length);r.appendChild(a)
;const s=createElement("div",["preview-info-container"]);const c=createElement("div",["action-bar"])
;const l=createElement("button",["gcbt-magnet-btn"]);c.appendChild(l)
;const d=createElement("div",["magnet-link-container"]);s.append(c,d);r.appendChild(s);e.appendChild(r)
;l.addEventListener("click",async e=>{e.preventDefault();e.stopPropagation();l.textContent="正在获取...";l.disabled=true
;d.innerHTML="";d.style.display="none";try{const e=await ApiClient.fetchMagnetLink(o)
;if(e)this.displayMagnetLink(d,e);else{d.textContent="未找到有效链接";d.style.display="block";d.style.color="#f56c6c"}
}catch(t){console.error("获取磁力失败",t);d.textContent="获取失败";d.style.display="block";d.style.color="#f56c6c"}finally{
l.textContent="重新获取";l.disabled=false}});l.textContent="获取磁力链接";if(n.filmSize)DomManager.updateTitleSize(t,n.filmSize)}
static displayMagnetLink(e,t){e.innerHTML="";const n=createElement("a",[],{href:t,textContent:t})
;if(t.startsWith("magnet:")){n.style.cursor="pointer";n.title="点击复制磁力链接";n.addEventListener("click",e=>{
e.preventDefault();navigator.clipboard.writeText(t).then(()=>{const e=n.textContent;n.textContent="已复制!"
;n.style.color="#52c41a";setTimeout(()=>{n.textContent=e;n.style.color="#1890ff"},2e3)})})}else n.target="_blank"
;e.appendChild(n);e.style.display="block"}static preloadImage(e){return new Promise((t,n)=>{const i=new Image
;i.onload=()=>t(i);i.onerror=()=>n();i.src=e})}static preloadImageDNS(e){try{const t=new URL(e);const n=t.hostname
;if(!document.querySelector(`link[rel="dns-prefetch"][href="//${n}"]`)){const e=createElement("link",[],{
rel:"dns-prefetch",href:`//${n}`});document.head.appendChild(e)}}catch(t){}}static setImageGridWidth(e,t){let n
;if(1===t)n="50%";else{const e=10*(t-1);n=`calc((100% - ${e}px) / ${t})`}Array.from(e.children).forEach(e=>{
e.style.width=n;e.style.flex="0 0 auto"})}}class GcbtScript{static async run(){console.log("gcbt启动");t.init()
;await waitForDOMContentLoaded();try{DomManager.cleanPage()
;if(document.querySelector(CONFIG.selectors.postList))if(DomManager.optimizeListPage()){i.init()
;await PreviewProcessor.processAll()}}catch(e){console.error("脚本执行失败",e)}finally{t.unhidePosts()}}}GcbtScript.run()})();