您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Greasy Fork is available in English.
不翻墙下,更快加载 imhentai.xxx 的图片
当前为
// ==UserScript== // @name For Imhentai // @namespace http://tampermonkey.net/ // @version 1.0 // @description 不翻墙下,更快加载 imhentai.xxx 的图片 // @author 水母 // @match https://imhentai.xxx/gallery/* // @icon data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw== // @grant none // @license MIT // ==/UserScript== (function () { 'use strict'; // Your code here... const IS_DEBUG = false; // 全局数据 let IS_RUN = false; let IS_PAGE_LOADING = false; // 加锁 // 页码序列 [cover.jpg, 1.jpg, ..., 30.jpg] : 页数总数定为 30; 数组 [0] 定为 cover; cover 不一定加载 let CURRENT_PAGE = 0; // 当前页码 let PAGE_LOADED = 0; // 已加载的页码 let PAGE_LOAD_STEP = IS_DEBUG ? 2 : 5; // 每次加载页数 let SCALE = IS_DEBUG ? 0.3 : 1; // 图片整体缩放 // class function BzData( name_en = 'Null', name_sub = 'Null', page = 0, root_url = '', imgInfoList = [], types = ['.jpg', '.png', '.gif', '.err'] ) { this.name_en = name_en; this.name_sub = name_sub; this.page = page; this.root_url = root_url; this.imgInfoList = imgInfoList; this.types = types; } // imgInfoList[ImgInfo] function ImgInfo( imgName, imgAlt, imgUrl = '', imgType = '', width = 0, height = 0, SCALE = 1 ) { this.imgName = imgName; this.imgAlt = !imgAlt ? imgName : imgAlt; this.imgUrl = imgUrl; this.imgType = imgType; this.width = width; this.height = height; this.SCALE = SCALE; } // 迭代器 function* BzDataIterator(bzData) { let index = 0; while (index < bzData.imgInfoList.length) { let imgInfo = bzData.imgInfoList[index]; yield [index++, bzData.root_url, imgInfo]; } yield [undefined, undefined, undefined]; } //漫画名去特殊字符处理 function processFilename(filename) { return filename .replaceAll('\\', '-') .replaceAll('/', '-') .replaceAll(':', ':') .replaceAll('*', '-') .replaceAll('?', '?') .replaceAll('"', '“') .replaceAll('<', '《') .replaceAll('>', '》') .replaceAll('|', '~'); } // 判断图片 url 有效与否 function verifyImgExists(imgUrl) { return new Promise((resolve, reject) => { let ImgObj = new Image(); ImgObj.src = imgUrl; ImgObj.onload = () => resolve(ImgObj); ImgObj.onerror = (rej) => reject(rej); }); } // 为 ImgInfo 生成有效 url async function processImgInfoAsync( root_url, imgInfo, types = ['.jpg', '.png', '.gif', '.err'] ) { // 测试三种后缀 for (let type of types) { imgInfo.imgUrl = root_url + imgInfo.imgName + type; imgInfo.imgType = type; try { let ImgObj = await verifyImgExists(imgInfo.imgUrl); imgInfo.width = ImgObj.width; imgInfo.height = ImgObj.height; // console.log(imgInfo); break; } catch (e) { continue; // 未测试最后一个,继续 } } } // 迭代器处理图片序列 async function processImgAsync(bzDataIterator) { let loadCount; for (let i = 0; i < PAGE_LOAD_STEP; i++) { const [index, root_url, imgInfo] = bzDataIterator.next().value; if (!index) break; await processImgInfoAsync(root_url, imgInfo); loadCount = index; if (IS_DEBUG) console.log(`${index}:`, imgInfo); } PAGE_LOADED = loadCount; IS_PAGE_LOADING = false; } // 获取漫画名、页数、图片的 url function initData() { let bzData = new BzData(); let coverUrl; // cover if (!IS_DEBUG) { bzData = new BzData(); bzData.imgInfoList.push(new ImgInfo('cover')); const tag_div_main = document.querySelectorAll( 'body > div.overlay > div.container > div.row.gallery_first > div' ); // 获取漫画名 bzData.name_en = tag_div_main[1].querySelector('h1').textContent; bzData.name_sub = tag_div_main[1].querySelector('p.subtitle').textContent; // 漫画名去特殊字符处理 if (bzData.name_en !== '') { bzData.name_en = processFilename(bzData.name_en); } if (bzData.name_sub !== '') { bzData.name_sub = processFilename(bzData.name_sub); } // 获取页数 let page_str = tag_div_main[1].querySelector('li.pages').textContent; bzData.page = Number.parseInt(page_str.match(/Pages: ([0-9]*)/i)[1]); // 图片序列的 url 前缀与封面 url 的相同, // eg.封面 url=https://m7.imhentai.xxx/023/mnsiote3jg/cover.jpg // eg.序列的 url=https://m7.imhentai.xxx/023/mnsiote3jg/ coverUrl = tag_div_main[0].querySelector('img').dataset.src; bzData.root_url = coverUrl.slice(0, coverUrl.lastIndexOf('/') + 1); // 图片序列的 url 生成, // eg: https://m6.imhentai.xxx/021/fh5n1d304g/1.jpg for (let p = 1; p <= bzData.page; p++) { bzData.imgInfoList.push(new ImgInfo(p.toString())); // 图片名未编码,数字序列就行 } } let bzDataIterator = BzDataIterator(bzData); // 初始化 cover 数据,让 CURRENT_PAGE 与 PAGE_LOADED 能够对齐 let [index, root_url, coverInfo] = bzDataIterator.next().value; let ImgObj = new Image(); ImgObj.src = coverUrl; ImgObj.onload = () => { coverInfo.width = ImgObj.width; coverInfo.height = ImgObj.height; }; coverInfo.imgUrl = coverUrl; if (IS_DEBUG) console.log(coverInfo); return [bzData, bzDataIterator]; } // 初始化组件 function initComponents(bzData, bzDataIterator) { // <img> const newImg = document.createElement('img'); newImg.id = 'can-img'; newImg.style = ` -webkit-user-select: none; margin:0 auto; transition: background-color 300ms; `; // <input> const changePageInput = document.createElement('input'); changePageInput.id = 'can-input'; changePageInput.type = 'number'; changePageInput.value = `${CURRENT_PAGE}`; changePageInput.disabled = true; changePageInput.style = ` width: 45%;height: 80%; font-size:18px;text-align:center; `; // <label> const pageLabel = document.createElement('label'); pageLabel.id = 'can-page'; pageLabel.style = 'width: 55%;height: 100%;font-size:18px;text-align:center;background-color: hsla(0, 0%, 90%, 90%);'; pageLabel.textContent = `[ ${PAGE_LOADED} ]`; // <button> const runBtn = document.createElement('button'); const previousBtn = document.createElement('button'); const nextBtn = document.createElement('button'); const downloadBtn = document.createElement('button'); const scaleUpBtn = document.createElement('button'); const scaleResetBtn = document.createElement('button'); const scaleDownBtn = document.createElement('button'); runBtn.id = 'run'; previousBtn.id = 'pre'; nextBtn.id = 'next'; downloadBtn.id = 'down'; scaleUpBtn.id = 'sUp'; scaleResetBtn.id = 'sReset'; scaleDownBtn.id = 'sDown'; runBtn.textContent = '启动'; previousBtn.textContent = '◁'; nextBtn.textContent = '▷'; downloadBtn.textContent = '辅助下载'; scaleUpBtn.textContent = '△'; scaleResetBtn.textContent = '○'; scaleDownBtn.textContent = '▽'; runBtn.addEventListener('click', (evt) => { evt.stopPropagation(); if (!IS_RUN) { IS_RUN = true; evt.target.textContent = `原页 ${bzData.page}`; // 生效按钮 let btns = document .querySelector('#can-app') .querySelectorAll('button'); for (const btn of btns) { btn.disabled = false; } let inputPage = document.querySelector('#can-input'); inputPage.disabled = false; // 显示 新 <img> let _newImg = document.querySelector('#can-div-img'); _newImg.style.display = 'block'; } else { IS_RUN = false; evt.target.textContent = '启动'; // 无效按钮 let btns = document .querySelector('#can-app') .querySelectorAll('button'); for (const btn of btns) { btn.disabled = btn.id !== 'run' ? true : false; } let inputPage = document.querySelector('#can-input'); inputPage.disabled = true; // 隐藏新 <img> let _newImg = document.querySelector('#can-div-img'); _newImg.style.display = 'none'; } }); previousBtn.addEventListener('click', (evt) => { evt.stopPropagation(); let imgInfo = bzData.imgInfoList[ CURRENT_PAGE > 0 ? --CURRENT_PAGE : (CURRENT_PAGE = PAGE_LOADED) ]; updateImgTag(imgInfo); let inputPage = document.querySelector('#can-input'); let page_ = document.querySelector('#can-page'); inputPage.value = CURRENT_PAGE; }); // next 会触发图片的 lazy load nextBtn.addEventListener('click', (evt) => { evt.stopPropagation(); // 如果 // 未加锁 && 页码加载未完全 && 正访问超出已加载的页码 // 添加步数个新 imgInfo if ( !IS_PAGE_LOADING && PAGE_LOADED !== bzData.page && CURRENT_PAGE === PAGE_LOADED ) { IS_PAGE_LOADING = true; processImgAsync(bzDataIterator); } else { let imgInfo = bzData.imgInfoList[ CURRENT_PAGE < PAGE_LOADED ? ++CURRENT_PAGE : (CURRENT_PAGE = 0) ]; updateImgTag(imgInfo); let inputPage = document.querySelector('#can-input'); let page_ = document.querySelector('#can-page'); inputPage.value = CURRENT_PAGE; page_.textContent = `[ ${PAGE_LOADED} ]`; } }); downloadBtn.addEventListener('click', (evt) => { evt.stopPropagation(); const stringData = JSON.stringify(bzData, null, 2); // dada 表示要转换的字符串数据,type 表示要转换的数据格式 const blob = new Blob([stringData], { type: 'application/json', }); // 根据 blob生成 url链接 const objectURL = URL.createObjectURL(blob); // 通过 a 标签下载 const aTag = document.createElement('a'); aTag.href = objectURL; aTag.download = 'PicLinks.json'; aTag.click(); aTag.remove(); // 释放 URL 对象 URL.revokeObjectURL(objectURL); }); scaleUpBtn.addEventListener('click', (evt) => { evt.stopPropagation(); SCALE += SCALE < 3 ? 0.1 : 0; let imgInfo = bzData.imgInfoList[CURRENT_PAGE]; updateImgTag(imgInfo); }); scaleResetBtn.addEventListener('click', (evt) => { evt.stopPropagation(); SCALE = 1; let imgInfo = bzData.imgInfoList[CURRENT_PAGE]; updateImgTag(imgInfo); }); scaleDownBtn.addEventListener('click', (evt) => { evt.stopPropagation(); SCALE -= SCALE > 0.3 ? 0.1 : 0; let imgInfo = bzData.imgInfoList[CURRENT_PAGE]; updateImgTag(imgInfo); }); changePageInput.addEventListener('change', (evt) => { evt.stopPropagation(); if (0 <= evt.target.value && evt.target.value <= bzData.page) { CURRENT_PAGE = evt.target.value; let imgInfo = bzData.imgInfoList[CURRENT_PAGE]; updateImgTag(imgInfo); } }); const app = document.createElement('div'); app.id = 'can-app'; app.style = ` font-size:20px; color:HotPink; width:120px; height:200px; background-color:hsla(0, 0%, 90%, 50%); display:flex; flex-direction:column; justify-content:space-between; position:fixed; top:40%; z-index:1000002; transform:translateX(calc(-50% * var(--direction))) translateY(-50%); `; const div_tool = document.createElement('div'); div_tool.style = ` display:flex; flex-direction:column; justify-content:space-between; height:160px; background-color:hsla(0, 0%, 90%, 50%); `; const div_scale = document.createElement('div'); div_scale.style = ` display:flex; flex-direction:row; justify-content:space-between; `; const div_page = document.createElement('div'); div_page.style = ` align-items:center; display:flex; flex-direction:row; justify-content:space-between; `; app.appendChild(runBtn); div_tool.appendChild(previousBtn); div_tool.appendChild(nextBtn); div_scale.appendChild(scaleUpBtn); div_scale.appendChild(scaleResetBtn); div_scale.appendChild(scaleDownBtn); div_tool.appendChild(div_scale); div_page.appendChild(changePageInput); div_page.appendChild(pageLabel); div_tool.appendChild(div_page); div_tool.appendChild(downloadBtn); app.appendChild(div_tool); document.body.appendChild(app); // 包裹 <img> 并悬浮居中 const div_img = document.createElement('div'); div_img.id = 'can-div-img'; // 粉色 div_img.style = ` display:none; position: fixed;overflow: auto; width: 80%;height: 100%;top: 0%;z-index: 1000001; left: 0;right: 0;margin:0 auto;text-align: center; background-color: hsla(338, 100%, 70%, 0.8); `; div_img.appendChild(newImg); document.body.appendChild(div_img); let btns = document.querySelector('#can-app').querySelectorAll('button'); for (const btn of btns) { btn.style = 'font-size:20px; color:HotPink;'; btn.disabled = btn.id !== 'run' ? true : false; } } // 更新 <img> function updateImgTag(imgInfo) { let newImg_ = document.querySelector('#can-img'); newImg_.src = imgInfo.imgUrl; newImg_.alt = imgInfo.imgAlt; if (imgInfo.imgType !== '.err') { // 使用 * 缩放应该在 <原数值> 上变化 newImg_.width = imgInfo.width * SCALE; newImg_.height = imgInfo.height * SCALE; } else { newImg_.style.removeProperty('width'); newImg_.style.removeProperty('height'); } } initComponents(...initData()); })();