// ==UserScript== // @name 图集连续看图与广告跳转拦截 // @namespace local.gallery.reader // @version 1.0.0 // @description 连续看图、逐张保存整套,并拦截常见广告跳转。 // @match https://aouq7273oo.gcliuchu7.cyou/* // @run-at document-start // @grant GM_download // @grant GM.download // @grant unsafeWindow // @connect imgclh.com // ==/UserScript== (function () { 'use strict'; if (window.top !== window.self) return; var origin = location.origin; if (location.hostname !== 'aouq7273oo.gcliuchu7.cyou') return; var albumPath = location.pathname; var albumPage = /^\/detail\/id\/\d+\.html$/.test(albumPath); var cache = new Map(), current = null, index = 0, overlay = null, picture = null, info = null; var prevButton = null, nextButton = null, entry = null, active = false, loading = false, controller = null, generation = 0, imageFolder = '', basePage = 0; var pageWindow=typeof unsafeWindow!=='undefined' && unsafeWindow ? unsafeWindow : window; var downloadButton=null,stopButton=null,downloadStatus=null,downloading=false,downloadGeneration=0,downloadController=null,downloadHandle=null,preloadedImage=null; function siteUrl(raw, base) { try { var url = new URL(raw, base || location.href);return url.protocol === 'https:' && url.origin === origin && !url.username && !url.password ? url : null; } catch (_) { return null; } } function outbound(raw) { if (!raw || /^\s*(?:#|mailto:|tel:)/i.test(raw) || /^\s*javascript:\s*(?:void\s*\(\s*0\s*\)\s*;?|;)\s*$/i.test(raw)) return false; if (/^\s*javascript:/i.test(raw)) return true; try {var u = new URL(raw, location.href);return /^(?:https?:)$/.test(u.protocol) && (u.origin !== origin || /^\/link\/go\/?$/.test(u.pathname));} catch (_) {return true;} } function mediaUrl(raw, base) { try { var u = new URL(raw, base); if (u.protocol !== 'https:' || u.username || u.password || !/^(?:[a-z0-9-]+\.)*imgclh\.com$/i.test(u.hostname) || !/^\/contents\/albums\/sources\/.+\.(?:jpe?g|png|webp|gif)$/i.test(u.pathname)) return null; return u.href; } catch (_) { return null; } } function pageNumber(url) { var text = url.searchParams.get('page');if (text === null) return 1; return /^[1-9]\d{0,4}$/.test(text) ? Number(text) : 0; } function parsePage(doc, url) { if (!siteUrl(url.href) || url.pathname !== albumPath || !pageNumber(url)) throw new Error('图集地址不匹配'); if (doc.querySelector('input[name="GOEDGE_WAF_CAPTCHA_CODE"], #loginForm')) throw new Error('下一页需要验证或登录'); var images = Array.from(doc.querySelectorAll('.thumb-item a[href]')).map(function (a) { return mediaUrl(a.getAttribute('href'), url.href); }).filter(Boolean); if (!images.length) throw new Error('下一页没有找到可用的图集图片'); var folder = new URL(images[0]);folder = folder.origin + folder.pathname.slice(0,folder.pathname.lastIndexOf('/') + 1); if (!imageFolder) imageFolder=folder; if (folder !== imageFolder || images.some(function(src){var img=new URL(src);return img.origin + img.pathname.slice(0,img.pathname.lastIndexOf('/')+1)!==imageFolder;})) throw new Error('图片地址与当前图集不一致'); var number = pageNumber(url), previous = null, next = null; doc.querySelectorAll('.pagination a[href]').forEach(function (a) { var candidate = siteUrl(a.getAttribute('href'), url.href); if (!candidate || candidate.pathname !== albumPath) return; var n = pageNumber(candidate); if (n === number - 1) previous = candidate.href; if (n === number + 1) next = candidate.href; }); return { number:number, images:images, previous:previous, next:next }; } function createStyle() { if (document.getElementById('bm-gallery-style')) return; if (!document.head && !document.documentElement) {document.addEventListener('DOMContentLoaded',createStyle,{once:true});return;} var style=document.createElement('style');style.id='bm-gallery-style'; style.textContent='a[href^="/link/go"],.links-grid{display:none!important}' + '#bm-gallery-entry{position:fixed;z-index:2147482000;left:12px;top:calc(100vh - 72px);top:calc(100dvh - 72px);border:0;border-radius:25px;padding:12px 18px;background:#266d5d;color:#fff;font:600 15px sans-serif;box-shadow:0 3px 12px #0005;touch-action:manipulation}' + '#bm-gallery{position:fixed;top:0;left:0;width:100vw;max-width:100vw;height:100vh;height:100dvh;z-index:2147483647;background:#111;color:#fff;display:flex;flex-direction:column;align-items:center;justify-content:space-between;box-sizing:border-box;font:15px/1.4 sans-serif;padding:env(safe-area-inset-top) 0 env(safe-area-inset-bottom)}' + '#bm-gallery[hidden]{display:none!important}#bm-gallery *{box-sizing:border-box}' + '#bm-gallery .bm-top{width:100%;display:flex;align-items:center;justify-content:space-between;padding:8px 12px;gap:8px;min-height:52px}' + '#bm-gallery .bm-stage{flex:1;min-height:0;width:100%;display:flex;align-items:center;justify-content:center;overflow:auto;touch-action:pan-y}' + '#bm-gallery img{display:block;max-width:100%;max-height:100%;object-fit:contain}' + '#bm-gallery .bm-controls{width:100%;max-width:100%;min-width:0;display:flex;align-items:center;justify-content:center;gap:8px;padding:10px 8px 16px;flex-wrap:wrap}' + '#bm-gallery button{min-height:44px;min-width:72px;border:1px solid #ffffff66;border-radius:10px;background:#2b2b2b;color:#fff;padding:8px 14px;font:15px sans-serif;touch-action:manipulation}' + '#bm-gallery button:disabled{opacity:.35}#bm-gallery [data-bm-gallery-status]{font-size:13px;color:#ddd;text-align:center;overflow-wrap:anywhere}'; (document.head || document.documentElement).appendChild(style); } createStyle(); // Block confirmed outbound ad links and the site's own redirect endpoint. // Browser-level rules are still needed for redirects not caused by a click or window.open. var originalOpen=pageWindow.open; try {pageWindow.open=function(url){var destination=url && siteUrl(String(url));if(!destination || /^\/link\/go\/?$/.test(destination.pathname))return null;return originalOpen.apply(pageWindow,arguments);};} catch (_) {} function intercept(event) { var target=event.target;if (!target || !target.closest) return; var a=target.closest('a[href]');if (!a) return; if (albumPage && a.closest('.thumb-item') && mediaUrl(a.getAttribute('href'),location.href)) { event.preventDefault();event.stopImmediatePropagation(); if(event.type==='click')openFromAnchor(a);return; } if (outbound(a.getAttribute('href'))) {event.preventDefault();event.stopImmediatePropagation();} } document.addEventListener('click',intercept,true); document.addEventListener('auxclick',intercept,true); function button(text, label, action) { var b=document.createElement('button');b.type='button';b.textContent=text;b.setAttribute('aria-label',label);b.addEventListener('click',action);return b; } function installViewer() { if (overlay) return; overlay=document.createElement('div');overlay.id='bm-gallery';overlay.hidden=true;overlay.setAttribute('role','dialog');overlay.setAttribute('aria-label','连续看图');overlay.setAttribute('aria-modal','true'); var top=document.createElement('div');top.className='bm-top';info=document.createElement('span');info.setAttribute('data-bm-gallery-status','');top.appendChild(info); top.appendChild(button('关闭','关闭看图',closeViewer));overlay.appendChild(top); var stage=document.createElement('div');stage.className='bm-stage';picture=document.createElement('img');picture.alt='图集图片';stage.appendChild(picture);overlay.appendChild(stage); var controls=document.createElement('div');controls.className='bm-controls';prevButton=button('上一张','上一张',function(){step(-1);});nextButton=button('下一张','下一张',function(){step(1);}); controls.appendChild(prevButton);controls.appendChild(nextButton); downloadButton=button('保存整套','逐张保存整套图片',startDownload);stopButton=button('停止保存','停止保存图片',stopDownload);stopButton.disabled=true; controls.appendChild(downloadButton);controls.appendChild(stopButton);overlay.appendChild(controls); downloadStatus=document.createElement('div');downloadStatus.setAttribute('data-bm-gallery-download-status','');downloadStatus.style.cssText='font-size:12px;color:#ddd;text-align:center;padding:0 12px 6px;';overlay.appendChild(downloadStatus);document.body.appendChild(overlay); picture.addEventListener('error',function(){if(active)info.textContent='图片加载失败,可尝试下一张或检查网络。';}); var start=null;stage.addEventListener('touchstart',function(e){if(e.touches.length===1)start={x:e.touches[0].clientX,y:e.touches[0].clientY};},{passive:true}); stage.addEventListener('touchend',function(e){if(!start || e.changedTouches.length!==1)return;var dx=e.changedTouches[0].clientX-start.x,dy=e.changedTouches[0].clientY-start.y;start=null;if(Math.abs(dx)>65&&Math.abs(dx)>Math.abs(dy)*1.5)step(dx<0?1:-1);},{passive:true}); } function paint() { if (!active || !current) return; picture.src=current.images[index];info.textContent='网页 '+current.number+' · 本页 '+(index+1)+' / '+current.images.length; if(index+1=0)openViewer(at); } async function loadPage(raw, previousPage, token) { var url=siteUrl(raw);if(!url || url.pathname!==albumPath)throw new Error('下一页地址不属于当前图集'); var wanted=pageNumber(url);if(!wanted || Math.abs(wanted-previousPage.number)!==1)throw new Error('分页顺序异常'); if(cache.has(wanted))return cache.get(wanted); var requestController=new AbortController();controller=requestController;var timeout=setTimeout(function(){requestController.abort();},15000); try { var response=await fetch(url.href,{credentials:'same-origin',redirect:'manual',signal:requestController.signal}); if(!response.ok || response.type==='opaqueredirect')throw new Error('网页加载失败,可能需要登录或验证'); var final=siteUrl(response.url || url.href);if(!final || final.pathname!==albumPath || pageNumber(final)!==wanted)throw new Error('网站返回了其他页面'); var contentType=response.headers.get('content-type') || '';if(contentType && !/text\/html|application\/xhtml\+xml/i.test(contentType))throw new Error('网站返回的不是图集网页'); var html=await response.text();if(!active || generation!==token)throw new Error('已关闭'); var doc=new DOMParser().parseFromString(html,'text/html'),page=parsePage(doc,final); if(page.images.length===previousPage.images.length && page.images.every(function(v,i){return v===previousPage.images[i];}))throw new Error('下一页与当前页重复,已停止翻页'); cache.set(wanted,page);return page; } finally {clearTimeout(timeout);if(controller===requestController)controller=null;} } async function step(direction) { if (!active || loading || !current) return; var n=index+direction;if(n>=0 && n0?current.next:current.previous;if(!url){info.textContent=direction>0?'已经是最后一张。':'已经是第一张。';return;} var previous=current,token=generation;loading=true;prevButton.disabled=nextButton.disabled=true;info.textContent='正在加载'+(direction>0?'下一':'上一')+'网页…'; try {var page=await loadPage(url,previous,token);if(!active || generation!==token)return;current=page;index=direction>0?0:page.images.length-1;} catch(error){if(active && generation===token)info.textContent=error.name==='AbortError'?'网页加载超时,请重试。':(error.message || '网页加载失败,请重试。');return;} finally {if(active && generation===token){loading=false;prevButton.disabled=index===0&&!current.previous;nextButton.disabled=index===current.images.length-1&&!current.next;}} paint(); } function downloadApi() { if(typeof GM_download==='function')return function(options){return GM_download(options);}; if(typeof GM!=='undefined' && GM && typeof GM.download==='function')return function(options){return GM.download(options);}; return null; } function updateDownload(message) { if(downloadStatus)downloadStatus.textContent=message; if(downloadButton)downloadButton.disabled=downloading; if(stopButton)stopButton.disabled=!downloading; if(entry)entry.textContent=downloading?'保存中 · 看图':'开始看图'; } function stopDownload() { downloadGeneration++;downloading=false; if(downloadController){try{downloadController.abort();}catch(_){}downloadController=null;} if(downloadHandle && typeof downloadHandle.abort==='function'){try{downloadHandle.abort();}catch(_){}}downloadHandle=null; updateDownload('已停止后续保存;已保存的图片保留。'); } async function downloadPage(raw, from, token) { var url=siteUrl(raw);if(!url || url.pathname!==albumPath || Math.abs(pageNumber(url)-from.number)!==1)throw new Error('分页地址不属于当前图集'); var wanted=pageNumber(url);if(cache.has(wanted))return cache.get(wanted); var own=new AbortController();downloadController=own;var timer=setTimeout(function(){own.abort();},15000); try { var response=await fetch(url.href,{credentials:'same-origin',redirect:'manual',signal:own.signal}); if(!response.ok || response.type==='opaqueredirect')throw new Error('网页加载失败,可能需要登录或验证'); var final=siteUrl(response.url || url.href);if(!final || final.pathname!==albumPath || pageNumber(final)!==wanted)throw new Error('网站返回了其他页面'); var type=response.headers.get('content-type')||'';if(type&&!/text\/html|application\/xhtml\+xml/i.test(type))throw new Error('网站返回的不是图集网页'); var body=await response.text();if(!downloading || token!==downloadGeneration)throw new Error('已停止'); var page=parsePage(new DOMParser().parseFromString(body,'text/html'),final); if(page.images.length===from.images.length&&page.images.every(function(v,i){return v===from.images[i];}))throw new Error('相邻网页图片重复,已停止'); cache.set(wanted,page);return page; } finally {clearTimeout(timer);if(downloadController===own)downloadController=null;} } function downloadOne(api,src,name,tag,token) { return new Promise(function(resolve,reject){ var settled=false,timer=setTimeout(function(){finish(new Error('下载器没有返回结果,请检查浏览器下载设置。'));},30000); function finish(error){if(settled)return;settled=true;clearTimeout(timer);if(token===downloadGeneration)downloadHandle=null;if(error)reject(error);else resolve();} try { var handle=api({url:src,name:name,tag:tag,headers:{Referer:origin+'/'},onload:function(){if(token===downloadGeneration&&downloading)finish();else finish(new Error('已停止'));},onerror:function(){finish(new Error('图片保存失败,请检查网络或下载权限。'));},ontimeout:function(){finish(new Error('图片保存超时。'));}}); downloadHandle=handle;if(handle&&typeof handle.then==='function')handle.then(function(){if(token===downloadGeneration&&downloading)finish();},function(){finish(new Error('图片保存失败,请检查网络或下载权限。'));}); } catch(_){finish(new Error('浏览器下载接口调用失败。'));} }); } async function startDownload() { if(downloading || !current)return; var api=downloadApi();if(!api){updateDownload('当前脚本环境没有下载接口;可长按当前图片保存。');return;} downloading=true;var token=++downloadGeneration,page=current,count=0,seen=new Set(),album=(albumPath.match(/\/id\/(\d+)/)||[])[1] || 'images',tag='图集_'+album; updateDownload('正在准备整套图片;请保持此网页打开。'); try { while(page.number>1){if(!page.previous)throw new Error('找不到第一页,已停止。');page=await downloadPage(page.previous,page,token);if(!downloading||token!==downloadGeneration)return;} while(page){ if(seen.has(page.number)||seen.size>=1000)throw new Error('分页重复或超过上限,已停止。');seen.add(page.number); for(var i=0;i