public paste
kUCxJx_Jy
—1 views18,318 chars19.0 KB
stdout
Paste content
// ==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<current.images.length){preloadedImage=new Image();preloadedImage.src=current.images[index+1];}else preloadedImage=null;
prevButton.disabled=loading || (index===0 && !current.previous);
nextButton.disabled=loading || (index===current.images.length-1 && !current.next);
}
var oldOverflow='';
function openViewer(start) {
if (!current || !current.images.length) return;
installViewer();index=Math.max(0,Math.min(start,current.images.length-1));
if (!active) oldOverflow=document.body.style.overflow;
active=true;overlay.hidden=false;document.body.style.overflow='hidden';paint();
}
function closeViewer() {
generation++;active=false;loading=false;if(controller){controller.abort();controller=null;}
if(overlay)overlay.hidden=true;if(picture)picture.removeAttribute('src');document.body.style.overflow=oldOverflow;
if(cache.has(basePage))current=cache.get(basePage);
}
function openFromAnchor(anchor) {
var url=mediaUrl(anchor.getAttribute('href'),location.href);if(!url || !current)return;
var at=current.images.indexOf(url);if(at>=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 && n<current.images.length){index=n;paint();return;}
var url=direction>0?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<page.images.length;i++){
if(!downloading||token!==downloadGeneration)return;
var path=new URL(page.images[i]).pathname,ext=(path.match(/\.(jpe?g|png|webp|gif)$/i)||[])[1]||'jpg';
updateDownload('正在保存第 '+(count+1)+' 张 · 网页 '+page.number+',请保持此网页打开。');
await downloadOne(api,page.images[i],album+'-'+String(page.number).padStart(3,'0')+'-'+String(i+1).padStart(3,'0')+'.'+ext,tag,token);
if(!downloading||token!==downloadGeneration)return;count++;
}
page=page.next?await downloadPage(page.next,page,token):null;
}
if(downloading&&token===downloadGeneration){downloading=false;updateDownload('已收到 '+count+' 张图片的下载完成回调,请在浏览器下载目录查看。');}
} catch(error){if(downloading&&token===downloadGeneration){downloading=false;updateDownload((error.name==='AbortError'?'网页加载超时。':error.message||'保存失败。')+' 已完成 '+count+' 张。');}}
}
function start() {
createStyle();if(!albumPage)return;
try {var url=new URL(location.href);current=parsePage(document,url);basePage=current.number;cache.set(current.number,current);}catch(_){return;}
entry=button('开始看图','从本页第一张开始看图',function(){openViewer(0);});entry.id='bm-gallery-entry';document.body.appendChild(entry);
}
document.addEventListener('keydown',function(e){if(!active)return;if(e.key==='Escape'){e.preventDefault();closeViewer();}else if(e.key==='ArrowRight'){e.preventDefault();step(1);}else if(e.key==='ArrowLeft'){e.preventDefault();step(-1);}},true);
window.addEventListener('pagehide',function(){if(active)closeViewer();if(downloading)stopDownload();});
if(document.readyState==='loading')document.addEventListener('DOMContentLoaded',start,{once:true});else start();
})();
$ tail -f comments.log
Comments [0]
// no comments yet