u9a9-预览

u9a9·列表预览图片

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         u9a9-预览
// @version      0.1.0
// @namespace    https://sleazyfork.org/zh-CN/users/1461640-%E6%98%9F%E5%AE%BF%E8%80%81%E9%AD%94
// @author       星宿老魔
// @description  u9a9·列表预览图片
// @match        https://u9a9.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=u9a9.com
// @license      MIT
// @grant        GM_setValue
// @grant        GM_getValue
// @run-at       document-start
// ==/UserScript==

(function(){"use strict";const e=200,t="1px solid #ddd",n="4px",s="contain",i="pointer",r="transform 0.2s ease",CONFIG={MAX_PREVIEW_IMAGES:5,
PREVIEW_IMAGE_HEIGHT:e,getPreviewImageStyle:(o=1)=>{const a=o<=3?"auto":Math.floor(100/o)-1+"%"
;return`height: ${e}px; width: ${a}; object-fit: ${s}; cursor: ${i}; border: ${t}; border-radius: ${n}; flex-shrink: 0; transition: ${r};`},
selectors:{listTable:"table.torrent-list",listRows:"table.torrent-list tbody tr.default",titleLinks:"td:nth-child(2) a",
imgContainer:"div.img-container",images:"div.img-container img"},regex:{viewUrl:/^\/view\/\d+\/[a-f0-9]+$/,imageUrl:/\.(jpg|jpeg|png|gif|webp)$/i},
styles:{previewImages:"display: flex; gap: 4px; flex-wrap: nowrap; overflow: hidden; width: 100%; padding: 4px; margin: 0;",
previewRow:"\n      .u9a9-preview-row {\n        background-color: #f8f9fa !important;\n      }\n      .u9a9-preview-row td {\n        padding: 10px !important;\n        text-align: center !important;\n      }\n      .u9a9-preview-row img {\n        border-radius: 4px;\n        transition: transform 0.2s ease;\n      }\n      .u9a9-preview-row img:hover {\n        transform: scale(1.05);\n      }\n    "
}},o=class{static info(...e){this.enabled,0}static error(...e){this.enabled,0}};o.enabled=!1,o.load=(...e)=>{},o.loadError=()=>{};let a=o
;function debounce(e,t){let n;return(...s)=>{clearTimeout(n),n=setTimeout(()=>e(...s),t)}}function l(e){return CONFIG.regex.imageUrl.test(e)}
function c(e){return e.startsWith("//")?`https:${e}`:e.startsWith("/")?`${window.location.origin}${e}`:e}function h(e,...t){a.info(e,...t)}
const d=class{static init(){this.overlay||(this.overlay=document.createElement("div"),
this.overlay.style.cssText="\n      position: fixed;\n      top: 0;\n      left: 0;\n      width: 100%;\n      height: 100%;\n      background: rgba(0, 0, 0, 0.95);\n      z-index: 999999;\n      display: none;\n      align-items: center;\n      justify-content: center;\n    ",
this.img=document.createElement("img"),
this.img.style.cssText="\n      width: 80vw;\n      height: 80vh;\n      max-width: 90%;\n      max-height: 90%;\n      object-fit: contain;\n      border-radius: 4px;\n    ",
this.counter=document.createElement("div"),
this.counter.style.cssText="\n      position: absolute;\n      top: 20px;\n      left: 50%;\n      transform: translateX(-50%);\n      color: white;\n      background: rgba(0, 0, 0, 0.6);\n      padding: 8px 16px;\n      border-radius: 20px;\n      font-size: 14px;\n    ",
this.prevBtn=this.createNavButton("‹","left"),this.nextBtn=this.createNavButton("›","right"),this.closeBtn=this.createCloseButton(),
this.overlay.appendChild(this.img),this.overlay.appendChild(this.counter),this.overlay.appendChild(this.prevBtn),
this.overlay.appendChild(this.nextBtn),this.overlay.appendChild(this.closeBtn),document.body.appendChild(this.overlay),this.setupEvents())}
static createNavButton(e,t){const n=document.createElement("button")
;return n.innerHTML="‹"===e?'<svg viewBox="0 0 24 24" fill="white" width="50" height="50"><path d="M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z"/></svg>':'<svg viewBox="0 0 24 24" fill="white" width="50" height="50"><path d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z"/></svg>',
n.style.cssText=`\n      position: fixed;\n      ${t}: 16px;\n      top: 50%;\n      transform: translateY(-50%);\n      width: 60px;\n      height: 60px;\n      background: rgba(255, 255, 255, 0.2);\n      border-radius: 50%;\n      border: none;\n      display: flex;\n      align-items: center;\n      justify-content: center;\n      color: white;\n      cursor: pointer;\n      user-select: none;\n      z-index: 10002;\n    `,
n}static createCloseButton(){const e=document.createElement("button")
;return e.innerHTML='<svg viewBox="0 0 24 24" fill="white" width="30" height="30"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/></svg>',
e.style.cssText="\n      position: fixed;\n      right: 20px;\n      top: 20px;\n      width: 50px;\n      height: 50px;\n      background: rgba(255, 255, 255, 0.2);\n      border-radius: 50%;\n      border: none;\n      display: flex;\n      align-items: center;\n      justify-content: center;\n      cursor: pointer;\n      user-select: none;\n      z-index: 10002;\n      transition: background 0.2s;\n    ",
e.onmouseover=()=>{e.style.background="rgba(255, 255, 255, 0.3)"},e.onmouseout=()=>{e.style.background="rgba(255, 255, 255, 0.2)"},e}
static setupEvents(){this.overlay.onclick=()=>{this.close()},this.prevBtn.onclick=e=>{e.stopPropagation(),this.prev()},this.nextBtn.onclick=e=>{
e.stopPropagation(),this.next()},this.closeBtn.onclick=e=>{e.stopPropagation(),this.close()},document.addEventListener("keydown",e=>{
"flex"===this.overlay?.style.display&&("Escape"===e.key?this.close():"ArrowLeft"===e.key?this.prev():"ArrowRight"===e.key&&this.next())})}
static show(e,t=0){this.init(),this.images=e,this.currentIndex=t,this.updateImage(),this.overlay.style.display="flex"}static close(){
this.overlay&&(this.overlay.style.display="none")}static prev(){this.currentIndex=(this.currentIndex-1+this.images.length)%this.images.length,
this.updateImage()}static next(){this.currentIndex=(this.currentIndex+1)%this.images.length,this.updateImage()}static updateImage(){
const e=this.images[this.currentIndex];this.img.style.display="none",this.img.src="",
this.counter.textContent=`${this.currentIndex+1} / ${this.images.length}`,this.images.length<=1?(this.prevBtn.style.display="none",
this.nextBtn.style.display="none",this.counter.style.display="none"):(this.prevBtn.style.display="flex",this.nextBtn.style.display="flex",
this.counter.style.display="block"),this.img.onload=()=>{this.img.style.display="block"},this.img.onerror=()=>{this.img.alt="图片加载失败"},this.img.src=e}}
;d.overlay=null,d.img=null,d.counter=null,d.prevBtn=null,d.nextBtn=null,d.closeBtn=null,d.images=[],d.currentIndex=0;let u=d
;const m=class _SimpleCache{constructor(){this.cache=new Map}static getInstance(){return this.instance||(this.instance=new _SimpleCache),this.instance
}get(e){return this.cache.get(e)||null}set(e,t){this.cache.set(e,t)}delete(e){return this.cache.delete(e)}clear(){this.cache.clear()}static get(e){
return _SimpleCache.getInstance().get(e)}static set(e,t){_SimpleCache.getInstance().set(e,t)}};m.instance=null;let g=m;class PreviewImageLoader{
constructor(){this.loading=new Set}async processAllRows(){const e=document.querySelectorAll("table.torrent-list tbody tr.default")
;if(!e||0===e.length)return a.info("预览脚本:未在本页找到任何帖子行"),void 0;a.info(`找到 ${e.length} 行,开始加载预览图`);const t=Array.from(e).map(e=>this.processRow(e))
;await Promise.all(t)}async processRow(e){const t=e.querySelector("td:nth-child(2) a");if(!t)return;const n=t.href
;if("true"===e.dataset.previewProcessed)return;e.dataset.previewProcessed="true";const s=`images_${n}`,i=g.get(s)
;if(i)return this.insertPreviewRow(e,i,n),void 0;if(!this.loading.has(n)){this.loading.add(n);try{const t=await this.fetchImages(n);g.set(s,t),
this.insertPreviewRow(e,t,n)}catch(r){a.error(`处理 ${n} 时发生错误:`,r)}finally{this.loading.delete(n)}}}async fetchImages(e){const t=await fetch(e)
;if(!t.ok)throw new Error(`HTTP 错误!状态: ${t.status}`)
;const n=await t.text(),s=(new DOMParser).parseFromString(n,"text/html").querySelector("div.img-container");if(!s)return[]
;const i=Array.from(s.querySelectorAll("img")).map(e=>c(e.src)).filter(e=>e&&l(e)).slice(0,CONFIG.MAX_PREVIEW_IMAGES)
;return a.info(`从 ${e} 提取到 ${i.length} 张图片`),i}insertPreviewRow(e,t,n){const s=e.nextElementSibling
;if(s&&s.classList.contains("u9a9-preview-row"))return this.updatePreviewRow(s,t,n),void 0;const i=document.createElement("tr")
;i.className="u9a9-preview-row";const r=document.createElement("td"),o=e.cells.length;r.setAttribute("colspan",o.toString()),
r.style.cssText="padding: 10px; text-align: center; background: #f8f9fa;",0===t.length?r.textContent="无预览图":this.renderImages(r,t,n),i.appendChild(r),
e.insertAdjacentElement("afterend",i)}updatePreviewRow(e,t,n){const s=e.querySelector("td");s&&(s.innerHTML="",
0===t.length?s.textContent="无预览图":this.renderImages(s,t,n))}renderImages(e,t,n){const s=document.createElement("div")
;s.style.cssText=CONFIG.styles.previewImages,t.forEach((e,n)=>{const i=document.createElement("img");i.src=e,i.loading="lazy",i.alt=`预览图 ${n+1}`,
i.style.cssText=CONFIG.getPreviewImageStyle(t.length),i.addEventListener("click",()=>{u.show(t,n)}),i.addEventListener("error",()=>{
i.style.display="none"}),i.addEventListener("mouseenter",()=>{i.style.transform="scale(1.05)"}),i.addEventListener("mouseleave",()=>{
i.style.transform="scale(1)"}),s.appendChild(i)}),e.appendChild(s)}}class StyleManager{constructor(){this.styleElement=null}init(){this.applyStyles(),
"loading"===document.readyState&&document.addEventListener("DOMContentLoaded",()=>{this.applyStyles()}),h("样式管理器初始化完成")}applyStyles(){
this.styleElement&&this.styleElement.remove(),this.styleElement=document.createElement("style"),this.styleElement.type="text/css",
this.styleElement.textContent=CONFIG.styles.previewRow,(document.head||document.getElementsByTagName("head")[0]).appendChild(this.styleElement),
h("预览行样式已应用")}destroy(){this.styleElement&&(this.styleElement.remove(),this.styleElement=null),h("样式管理器已销毁")}}class TorrentLinkRemover{constructor(){
this.observer=null,this.debouncedRemove=debounce(this.removeTorrentLinks.bind(this),100)}init(){this.removeTorrentLinks(),this.startObserver(),
h("种子链接移除器初始化完成")}removeTorrentLinks(){const e=document.querySelectorAll('a[href*=".torrent"]:not([href^="magnet:"])');let t=0;e.forEach(e=>{try{
const n=e.getAttribute("href");if(n&&n.includes(".torrent")&&!n.startsWith("magnet:")){const n=e.nextElementSibling
;if(n&&n.classList.contains("ext-push-resource-to-115")){const e=n.getAttribute("data-resource-url");e&&e.includes(".torrent")&&n.remove()}e.remove(),
t++}}catch(n){}}),t>0&&h(`已移除 ${t} 个种子下载链接`)}startObserver(){this.observer&&this.observer.disconnect(),this.observer=new MutationObserver(e=>{let t=!1
;e.forEach(e=>{"childList"===e.type&&e.addedNodes.length>0&&e.addedNodes.forEach(e=>{
e instanceof HTMLElement&&("A"===e.tagName&&e.getAttribute("href")?.includes(".torrent")||e.querySelector&&e.querySelector('a[href*=".torrent"]'))&&(t=!0)
})}),t&&this.debouncedRemove()}),this.observer.observe(document,{childList:!0,subtree:!0})}destroy(){this.observer&&(this.observer.disconnect(),
this.observer=null),h("种子链接移除器已销毁")}}class U9A9Preview{constructor(){this.imageLoader=new PreviewImageLoader,this.styleManager=new StyleManager,
this.torrentLinkRemover=new TorrentLinkRemover}init(){if(h("u9a9预览脚本启动(简化版)"),!this.isSupportedDomain())return h("不支持的域名,退出"),void 0
;this.styleManager.init(),"loading"===document.readyState?document.addEventListener("DOMContentLoaded",()=>{this.startFeatures()
}):this.startFeatures()}startFeatures(){try{this.torrentLinkRemover.init(),this.imageLoader.processAllRows(),h("u9a9预览脚本功能初始化完成")}catch(e){
h("功能初始化失败:",e)}}isSupportedDomain(){return window.location.hostname.includes("u9a9.com")}}(new U9A9Preview).init()})();