u9a9-预览

u9a9·列表预览图片

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         u9a9-预览
// @version      0.1.1
// @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",
adContainers:[".row.ad",".row .ad",".col-md-6 .adclick","a.adclick",'img[src*="/image/a/"]','img[src*="/image/b/"]']},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 d(e,...t){a.info(e,...t)}
const h=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}}
;h.overlay=null,h.img=null,h.counter=null,h.prevBtn=null,h.nextBtn=null,h.closeBtn=null,h.images=[],h.currentIndex=0;let u=h
;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()}),d("样式管理器初始化完成")}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),
d("预览行样式已应用")}destroy(){this.styleElement&&(this.styleElement.remove(),this.styleElement=null),d("样式管理器已销毁")}}class TorrentLinkRemover{constructor(){
this.observer=null,this.debouncedRemove=debounce(this.removeTorrentLinks.bind(this),100)}init(){this.removeTorrentLinks(),this.startObserver(),
d("种子链接移除器初始化完成")}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&&d(`已移除 ${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),d("种子链接移除器已销毁")}}class AdRemover{constructor(){this.observer=null}init(){this.removeAds(),this.setupObserver(),d("广告移除模块已启动")}
setupObserver(){!this.observer&&document.body&&(this.observer=new MutationObserver(()=>{this.removeAds()}),this.observer.observe(document.body,{
childList:!0,subtree:!0}))}removeAds(){let e=0;CONFIG.selectors.adContainers.forEach(t=>{document.querySelectorAll(t).forEach(t=>{
"true"!==t.dataset.u9a9AdRemoved&&(t.dataset.u9a9AdRemoved="true",t.style.setProperty("display","none","important"),e++)})}),e>0&&d(`已隐藏 ${e} 个广告元素`)}
destroy(){this.observer?.disconnect(),this.observer=null}}class U9A9Preview{constructor(){this.imageLoader=new PreviewImageLoader,
this.styleManager=new StyleManager,this.torrentLinkRemover=new TorrentLinkRemover,this.adRemover=new AdRemover}init(){if(d("u9a9预览脚本启动(简化版)"),
!this.isSupportedDomain())return d("不支持的域名,退出"),void 0;this.styleManager.init(),
"loading"===document.readyState?document.addEventListener("DOMContentLoaded",()=>{this.startFeatures()}):this.startFeatures()}startFeatures(){try{
this.torrentLinkRemover.init(),this.adRemover.init(),this.imageLoader.processAllRows(),d("u9a9预览脚本功能初始化完成")}catch(e){d("功能初始化失败:",e)}}
isSupportedDomain(){return window.location.hostname.includes("u9a9.com")}}(new U9A9Preview).init()})();