Sleazy Fork is available in English.

u9a9-预览

u9a9·列表预览图片

// ==UserScript==
// @name         u9a9-预览
// @version      0.0.4
// @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 t=200,e="1px solid #ddd",n="4px",i="contain",r="pointer",CONFIG={MAX_PREVIEW_IMAGES:5,PREVIEW_IMAGE_HEIGHT:t,
getPreviewImageStyle:(s=1)=>{const a=s<=3?"auto":Math.floor(100/s)-1+"%"
;return`height: ${t}px; width: ${a}; object-fit: ${i}; cursor: ${r}; border: ${e}; border-radius: ${n}; flex-shrink: 0;`},selectors:{
listTable:".table.table-bordered.table-hover.table-striped.torrent-list",listRows:"tbody tr",titleLinks:'a[href^="/view/"]',
titleCell:"td:nth-child(2)",imgContainer:".img-container",images:".img-container img"},regex:{viewUrl:/^\/view\/\d+\/[a-f0-9]+$/,
imageUrl:/\.(jpg|jpeg|png|gif|webp)$/i},styles:{
pageLayout:"\n      .container-fluid, .container {\n        max-width: none !important;\n        width: 95% !important;\n        margin: 0 auto !important;\n      }\n      .table.torrent-list {\n        width: 100% !important;\n        table-layout: auto !important;\n      }\n      .u9a9-reconstructed {\n        margin-top: 0 !important;\n      }\n      .u9a9-reconstructed th:first-child {\n        width: auto !important;\n        min-width: 400px;\n      }\n      .u9a9-reconstructed th:nth-child(2) {\n        width: 80px;\n        text-align: center;\n      }\n      .u9a9-reconstructed th:nth-child(3) {\n        width: 120px;\n        text-align: center;\n      }\n      .u9a9-reconstructed th:nth-child(4) {\n        width: 140px;\n        text-align: center;\n      }\n      .u9a9-preview-row {\n        background-color: #f8f9fa !important;\n      }\n      .u9a9-preview-row td {\n        padding: 12px !important;\n        border-top: none !important;\n      }\n      .u9a9-data-row td {\n        border-bottom: none !important;\n      }\n    ",
previewImages:"display: flex; gap: 4px; flex-wrap: nowrap; overflow: hidden; width: 100%; padding: 4px; margin: 0;",
previewImage:`height: ${t}px; min-width: 60px; object-fit: ${i}; cursor: ${r}; border: ${e}; border-radius: ${n}; transition: transform 0.2s ease; flex-shrink: 0;`
}},s=class{static info(...t){this.enabled,0}static error(...t){this.enabled,0}};s.enabled=!1,s.load=(...t)=>{},s.loadError=()=>{};let a=s
;function debounce(t,e){let n;return(...i)=>{clearTimeout(n),n=setTimeout(()=>t(...i),e)}}function createElement(t,e,n){
const i=document.createElement(t);return e&&Object.entries(e).forEach(([t,e])=>{
null!=e&&("style"===t&&"string"==typeof e?i.setAttribute("style",e):"style"===t&&"object"==typeof e?Object.assign(i.style,e):"className"===t&&"string"==typeof e||"class"===t&&"string"==typeof e?i.className=e:"function"==typeof e&&t.startsWith("on")?i[t]=e:"textContent"===t?i.textContent=e:"innerHTML"===t?i.innerHTML=e:"string"==typeof e||"number"==typeof e?i.setAttribute(t,String(e)):i[t]=e)
}),void 0!==n&&(Array.isArray(n)?n:[n]).forEach(t=>{"string"==typeof t?i.appendChild(document.createTextNode(t)):i.appendChild(t)}),i}function o(t){
return CONFIG.regex.imageUrl.test(t)}function l(t){return t.startsWith("//")?`https:${t}`:t.startsWith("/")?`${window.location.origin}${t}`:t}
function c(t,...e){a.info(t,...e)}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.overlay.appendChild(this.img),
this.overlay.appendChild(this.counter),this.overlay.appendChild(this.prevBtn),this.overlay.appendChild(this.nextBtn),
document.body.appendChild(this.overlay),this.setupEvents())}static createNavButton(t,e){const n=document.createElement("button")
;return n.innerHTML="‹"===t?'<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      ${e}: 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 setupEvents(){this.overlay.onclick=()=>{this.close()},this.prevBtn.onclick=t=>{t.stopPropagation(),this.prev()},this.nextBtn.onclick=t=>{
t.stopPropagation(),this.next()},document.addEventListener("keydown",t=>{
"flex"===this.overlay?.style.display&&("Escape"===t.key?this.close():"ArrowLeft"===t.key?this.prev():"ArrowRight"===t.key&&this.next())})}
static show(t,e=0){this.init(),this.images=t,this.currentIndex=e,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 t=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=t}}
;h.overlay=null,h.img=null,h.counter=null,h.prevBtn=null,h.nextBtn=null,h.images=[],h.currentIndex=0;let d=h;const u=class _SimpleCache{constructor(){
this.cache=new Map}static getInstance(){return this.instance||(this.instance=new _SimpleCache),this.instance}get(t){return this.cache.get(t)||null}
set(t,e){this.cache.set(t,e)}delete(t){return this.cache.delete(t)}clear(){this.cache.clear()}static get(t){return _SimpleCache.getInstance().get(t)}
static set(t,e){_SimpleCache.getInstance().set(t,e)}};u.instance=null;let p=u;class PreviewImageLoader{constructor(){this.loading=new Set}
async loadPreviewImages(t,e){if(this.loading.has(t))return;const n=`images_${t}`,i=p.get(n);if(i)return this.renderImages(e,i),void 0
;this.loading.add(t),e.textContent="📷 加载中...";try{const i=await this.fetchImagesWithFetch(t);p.set(n,i),this.renderImages(e,i)}catch(r){
a.loadError(),e.textContent="❌ 加载失败"}finally{this.loading.delete(t)}}async loadMultiplePreviewImages(t){
await Promise.all(t.map(t=>this.loadPreviewImages(t.viewUrl,t.previewCell)))}async fetchImagesWithFetch(t){const e=await fetch(t)
;if(!e.ok)throw new Error(`HTTP ${e.status}: ${e.statusText}`)
;const n=await e.text(),i=(new DOMParser).parseFromString(n,"text/html").querySelector(CONFIG.selectors.imgContainer);if(!i)return[]
;const r=Array.from(i.querySelectorAll("img")).map(t=>l(t.src)).filter(t=>t&&o(t)).slice(0,CONFIG.MAX_PREVIEW_IMAGES);return a.load(r.length),r}
renderImages(t,e){if(t.innerHTML="",0===e.length)return t.textContent="无预览图",void 0;const n=createElement("div",{style:CONFIG.styles.previewImages})
;e.forEach((t,i)=>{const r=createElement("img",{src:t,alt:`预览图 ${i+1}`,style:CONFIG.getPreviewImageStyle(e.length)});r.addEventListener("click",()=>{
d.show(e,i)}),r.addEventListener("error",()=>{r.style.display="none"}),r.addEventListener("mouseenter",()=>{r.style.transform="scale(1.05)"}),
r.addEventListener("mouseleave",()=>{r.style.transform="scale(1)"}),n.appendChild(r)}),t.appendChild(n)}}class PageReconstructor{constructor(){
this.originalTable=null,this.rowsData=[],this.imageLoader=new PreviewImageLoader}init(){
if(this.originalTable=document.querySelector(CONFIG.selectors.listTable),!this.originalTable)return c("未找到原始表格"),void 0;c("开始页面重构"),
this.extractRowsData(),this.reconstructTable(),c("页面重构完成")}extractRowsData(){if(!this.originalTable)return
;const t=this.originalTable.querySelectorAll("tbody tr");c(`提取 ${t.length} 行数据`),t.forEach(t=>{if(t instanceof HTMLTableRowElement){
const e=this.extractRowData(t);e&&this.rowsData.push(e)}}),c(`成功提取 ${this.rowsData.length} 行数据`)}extractRowData(t){
const e=t.cells[1],n=t.cells[2],i=t.cells[3],r=t.cells[4];if(!(e&&n&&i&&r))return null;const s=e.querySelector('a[href^="/view/"]');if(!s)return null
;const a=n.querySelector('a[href^="magnet:"]'),o=a?a.href:"";return{title:s.textContent?.trim()||"",titleLink:s.href,magnetLink:o,
size:i.textContent?.trim()||"",date:r.textContent?.trim()||"",originalRow:t}}reconstructTable(){
if(!this.originalTable||0===this.rowsData.length)return;const t=this.createNewTable()
;this.originalTable.parentNode?.replaceChild(t,this.originalTable)}createNewTable(){const t=createElement("table",{
className:"table table-bordered table-hover table-striped torrent-list u9a9-reconstructed",style:"width: 100%; table-layout: auto;"
}),e=createElement("thead"),n=createElement("tr");["Name","Link","Size","Date"].forEach(t=>{const e=createElement("th",{
className:`hdr-${t.toLowerCase()} text-center`,style:"Name"===t?"width: auto;":""},[t]);n.appendChild(e)}),e.appendChild(n),t.appendChild(e)
;const i=createElement("tbody"),r=[];return this.rowsData.forEach((t,e)=>{const n=this.createDataRow(t);i.appendChild(n)
;const s=this.createPreviewRow();i.appendChild(s);const a=s.cells[0];r.push({viewUrl:t.titleLink,previewCell:a})}),
r.length>0&&this.imageLoader.loadMultiplePreviewImages(r),t.appendChild(i),t}createDataRow(t){const e=createElement("tr",{
className:"default u9a9-data-row"}),n=createElement("td",{},[createElement("a",{href:t.titleLink,title:t.title,target:"_blank"
},[t.title])]),i=createElement("td",{className:"text-center"});if(t.magnetLink){const e=createElement("i",{
className:"glyphicon glyphicon-magnet fa-fw"}),n=createElement("a",{href:t.magnetLink,target:"_blank"});n.appendChild(e),i.appendChild(n)}
const r=createElement("td",{className:"text-center"},[t.size]),s=createElement("td",{className:"text-center"},[t.date]);return e.appendChild(n),
e.appendChild(i),e.appendChild(r),e.appendChild(s),e}createPreviewRow(){const t=createElement("tr",{className:"u9a9-preview-row",
style:"background: #f8f9fa; border-top: none;"}),e=createElement("td",{style:"padding: 8px; background: #f8f9fa; border-top: none;"})
;return e.colSpan=4,e.textContent="⏳ 准备加载...",t.appendChild(e),t}getReconstructedData(){return this.rowsData}}class StyleManager{constructor(){
this.styleElement=null}init(){this.applyPageStyles(),"loading"===document.readyState&&document.addEventListener("DOMContentLoaded",()=>{
this.applyPageStyles()}),c("样式管理器初始化完成")}applyPageStyles(){this.styleElement&&this.styleElement.remove(),
this.styleElement=document.createElement("style"),this.styleElement.type="text/css",this.styleElement.textContent=CONFIG.styles.pageLayout,
(document.head||document.getElementsByTagName("head")[0]).appendChild(this.styleElement),c("页面样式已应用")}destroy(){
this.styleElement&&(this.styleElement.remove(),this.styleElement=null),c("样式管理器已销毁")}}class TorrentLinkRemover{constructor(){this.observer=null,
this.debouncedRemove=debounce(this.removeTorrentLinks.bind(this),100)}init(){this.removeTorrentLinks(),this.startObserver(),c("种子链接移除器初始化完成")}
removeTorrentLinks(){const t=document.querySelectorAll('a[href*=".torrent"]:not([href^="magnet:"])');let e=0;t.forEach(t=>{try{
const n=t.getAttribute("href");if(n&&n.includes(".torrent")&&!n.startsWith("magnet:")){const n=t.nextElementSibling
;if(n&&n.classList.contains("ext-push-resource-to-115")){const t=n.getAttribute("data-resource-url");t&&t.includes(".torrent")&&n.remove()}t.remove(),
e++}}catch(n){}}),e>0&&c(`已移除 ${e} 个种子下载链接`)}startObserver(){this.observer&&this.observer.disconnect(),this.observer=new MutationObserver(t=>{let e=!1
;t.forEach(t=>{"childList"===t.type&&t.addedNodes.length>0&&t.addedNodes.forEach(t=>{
t instanceof HTMLElement&&("A"===t.tagName&&t.getAttribute("href")?.includes(".torrent")||t.querySelector&&t.querySelector('a[href*=".torrent"]'))&&(e=!0)
})}),e&&this.debouncedRemove()}),this.observer.observe(document,{childList:!0,subtree:!0})}destroy(){this.observer&&(this.observer.disconnect(),
this.observer=null),c("种子链接移除器已销毁")}}class U9A9Preview{constructor(){this.pageReconstructor=new PageReconstructor,this.styleManager=new StyleManager,
this.torrentLinkRemover=new TorrentLinkRemover}init(){if(c("u9a9预览脚本启动"),!this.isSupportedDomain())return c("不支持的域名,退出"),void 0
;this.styleManager.init(),"loading"===document.readyState?document.addEventListener("DOMContentLoaded",()=>{this.startFeatures()
}):this.startFeatures()}startFeatures(){try{this.torrentLinkRemover.init(),this.pageReconstructor.init(),c("u9a9预览脚本功能初始化完成")}catch(t){c("功能初始化失败:",t)
}}isSupportedDomain(){return window.location.hostname.includes("u9a9.com")}}(new U9A9Preview).init()})();