u9a9-预览

为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()})();