您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
为u9a9网站帖子列表添加预览图功能
// ==UserScript== // @name u9a9-预览 // @version 0.0.3 // @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/* // @match https://u9a9.org/* // @match https://u9a9.de/* // @match https://u9a9.one/* // @match https://u9a9.work/* // @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",r="contain",i="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: ${r}; cursor: ${i}; 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: ${r}; cursor: ${i}; 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(...r)=>{clearTimeout(n),n=setTimeout(()=>t(...r),e)}}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 createElement(t,e,n){ const r=document.createElement(t);return e&&Object.entries(e).forEach(([t,e])=>{ "style"===t&&"string"==typeof e?r.setAttribute("style",e):"className"===t&&"string"==typeof e?r.className=e:"function"==typeof e&&t.startsWith("on")?r[t]=e:void 0!==e&&"string"==typeof e&&r.setAttribute(t,e) }),n&&n.forEach(t=>{"string"==typeof t?r.appendChild(document.createTextNode(t)):r.appendChild(t)}),r}function h(t,...e){a.info(t,...e)} const c=class _Lightbox{constructor(){this.overlay=null,this.image=null,this.counter=null,this.prevBtn=null,this.nextBtn=null,this.currentImages=[], this.currentIndex=0,this.keydownHandler=null,this.createLightboxDOM(),this.setupEventListeners()}static getInstance(){ return this.instance||(this.instance=new _Lightbox),this.instance}createLightboxDOM(){this.overlay=createElement("div",{ style:"position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.9); z-index: 9999; display: none; align-items: center; justify-content: center; cursor: pointer;" });const t=createElement("div",{ style:"position: relative; max-width: calc(100vw - 160px); max-height: 90vh; display: flex; align-items: center; justify-content: center; margin: 0 80px;" });this.image=createElement("img",{style:"max-width: 100%; max-height: 85vh; object-fit: contain; border-radius: 8px;"}), this.counter=createElement("div",{ style:"position: absolute; top: -40px; left: 50%; transform: translateX(-50%); color: white; font-size: 14px; background: rgba(0,0,0,0.7); padding: 5px 10px; border-radius: 10px;" });const e=createElement("button",{ style:"position: fixed; top: 20px; right: 20px; width: 40px; height: 40px; border: none; border-radius: 50%; background: rgba(255,0,0,0.8); color: white; font-size: 20px; cursor: pointer; z-index: 10001; display: flex; align-items: center; justify-content: center;" },["×"]);this.prevBtn=createElement("button",{ style:"position: fixed; left: 20px; top: 50%; transform: translateY(-50%); width: 50px; height: 50px; border: none; border-radius: 50%; background: rgba(255,255,255,0.2); color: white; font-size: 24px; cursor: pointer; z-index: 10000; display: flex; align-items: center; justify-content: center;" },["‹"]),this.nextBtn=createElement("button",{ style:"position: fixed; right: 20px; top: 50%; transform: translateY(-50%); width: 50px; height: 50px; border: none; border-radius: 50%; background: rgba(255,255,255,0.2); color: white; font-size: 24px; cursor: pointer; z-index: 10000; display: flex; align-items: center; justify-content: center;" },["›"]),t.appendChild(this.image),t.appendChild(this.counter),this.overlay.appendChild(t),this.overlay.appendChild(e), this.overlay.appendChild(this.prevBtn),this.overlay.appendChild(this.nextBtn),document.body.appendChild(this.overlay),e.onclick=t=>{ t.stopPropagation(),this.hide()},this.prevBtn.onclick=t=>{t.stopPropagation(),this.showPreviousImage()},this.nextBtn.onclick=t=>{t.stopPropagation(), this.showNextImage()}}setupEventListeners(){this.overlay&&(this.overlay.onclick=t=>{t.target===this.overlay&&this.hide()})}show(t,e=0){ if(t.length&&this.overlay){if(this.currentImages=t,this.currentIndex=Math.max(0,Math.min(e,t.length-1)),this.overlay.style.display="flex", this.prevBtn&&this.nextBtn){const e=t.length>1;this.prevBtn.style.display=e?"flex":"none",this.nextBtn.style.display=e?"flex":"none"} this.setupKeyboardEvents(),this.loadCurrentImage()}}hide(){this.overlay&&(this.overlay.style.display="none"),this.removeKeyboardEvents(), this.currentImages=[],this.currentIndex=0}showPreviousImage(){ this.currentImages.length<=1||(this.currentIndex=(this.currentIndex-1+this.currentImages.length)%this.currentImages.length,this.loadCurrentImage())} showNextImage(){this.currentImages.length<=1||(this.currentIndex=(this.currentIndex+1)%this.currentImages.length,this.loadCurrentImage())} loadCurrentImage(){if(!this.image||!this.counter)return;const t=this.currentImages[this.currentIndex] ;this.counter.textContent=`${this.currentIndex+1} / ${this.currentImages.length}`,this.image.onload=()=>{this.image.style.display="block"}, this.image.src=t}setupKeyboardEvents(){this.keydownHandler=t=>{switch(t.key){case"Escape":this.hide();break;case"ArrowLeft":this.showPreviousImage() ;break;case"ArrowRight":this.showNextImage()}},document.addEventListener("keydown",this.keydownHandler)}removeKeyboardEvents(){ this.keydownHandler&&(document.removeEventListener("keydown",this.keydownHandler),this.keydownHandler=null)}static show(t,e=0){ _Lightbox.getInstance().show(t,e)}static hide(){_Lightbox.getInstance().hide()}};c.instance=null;let d=c;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}`,r=p.get(n);if(r)return this.renderImages(e,r),void 0 ;this.loading.add(t),e.textContent="📷 加载中...";try{const r=await this.fetchImagesWithFetch(t);p.set(n,r),this.renderImages(e,r)}catch(i){ 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(),r=(new DOMParser).parseFromString(n,"text/html").querySelector(CONFIG.selectors.imgContainer);if(!r)return[] ;const i=Array.from(r.querySelectorAll("img")).map(t=>l(t.src)).filter(t=>t&&o(t)).slice(0,CONFIG.MAX_PREVIEW_IMAGES);return a.load(i.length),i} 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,r)=>{const i=createElement("img",{src:t,alt:`预览图 ${r+1}`,style:CONFIG.getPreviewImageStyle(e.length)});i.addEventListener("click",()=>{ d.show(e,r)}),i.addEventListener("error",()=>{i.style.display="none"}),i.addEventListener("mouseenter",()=>{i.style.transform="scale(1.05)"}), i.addEventListener("mouseleave",()=>{i.style.transform="scale(1)"}),n.appendChild(i)}),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 h("未找到原始表格"),void 0;h("开始页面重构"), this.extractRowsData(),this.reconstructTable(),h("页面重构完成")}extractRowsData(){if(!this.originalTable)return ;const t=this.originalTable.querySelectorAll("tbody tr");h(`提取 ${t.length} 行数据`),t.forEach(t=>{if(t instanceof HTMLTableRowElement){ const e=this.extractRowData(t);e&&this.rowsData.push(e)}}),h(`成功提取 ${this.rowsData.length} 行数据`)}extractRowData(t){ const e=t.cells[1],n=t.cells[2],r=t.cells[3],i=t.cells[4];if(!(e&&n&&r&&i))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:r.textContent?.trim()||"",date:i.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 r=createElement("tbody"),i=[];return this.rowsData.forEach((t,e)=>{const n=this.createDataRow(t);r.appendChild(n) ;const s=this.createPreviewRow();r.appendChild(s);const a=s.cells[0];i.push({viewUrl:t.titleLink,previewCell:a})}), i.length>0&&this.imageLoader.loadMultiplePreviewImages(i),t.appendChild(r),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])]),r=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),r.appendChild(n)} const i=createElement("td",{className:"text-center"},[t.size]),s=createElement("td",{className:"text-center"},[t.date]);return e.appendChild(n), e.appendChild(r),e.appendChild(i),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()}),h("样式管理器初始化完成")}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),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 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&&h(`已移除 ${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),h("种子链接移除器已销毁")}}class U9A9Preview{constructor(){this.pageReconstructor=new PageReconstructor,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.pageReconstructor.init(),h("u9a9预览脚本功能初始化完成")}catch(t){h("功能初始化失败:",t) }}isSupportedDomain(){return["u9a9.com","u9a9.org","u9a9.de","u9a9.one","u9a9.work"].some(t=>window.location.hostname.includes(t))}} (new U9A9Preview).init()})();