您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
增加一些辅助阅读功能(自用)。
当前为
// ==UserScript== // @name 嗨皮漫畫閱讀輔助 // @name:zh-CN 嗨皮漫画阅读辅助 // @version 2.4.5 // @description 增加一些輔助閱讀功能(自用)。 // @description:zh-CN 增加一些辅助阅读功能(自用)。 // @author tony0809 // @match *://m.happymh.com/* // @icon https://m.happymh.com/favicon.ico // @grant none // @run-at document-end // @license GPL // @namespace https://greasyfork.org/users/20361 // ==/UserScript== (async () => { 'use strict'; const options = { //true 開啟,false 關閉 kn: true, //按鍵盤右方向鍵前往下一話。 kp: true, //按鍵盤左方向鍵前往上一話。 dn: true, //雙擊前往下一話,方便手機使用。 kdn: [false, 300], //按住空白鍵超過幾毫秒下一話。 (預設關閉) nE: true, //閱讀頁底部增加更新頁和收藏頁的按鈕。 pl: true, //閱讀頁預讀全部圖片,並且嘗試預讀下一話圖片。 hE: true, //隱藏閱讀頁頂部的公告。 ion: [false, 1000], //下一話按鈕完全進入視窗可視範圍內時經過幾毫秒後自動下一話。 (預設關閉) lM: true, //更新頁自動點擊載入更多。 list: true, //目錄頁自動展開全部章節。 oint: true //在新分頁打開漫畫鏈接。 }, ge = selector => /^\//.test(selector) ? document.evaluate(selector, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue : document.querySelector(selector), gae = selector => { if (/^\//.test(selector)) { let nodes = []; let results = document.evaluate(selector, document, null, XPathResult.ANY_TYPE, null); let node; while (node = results.iterateNext()) { nodes.push(node); } return nodes; } else { return document.querySelectorAll(selector); } }, lp = location.pathname, read = /^\/reads\/\w+\/\d+$/.test(lp), latest = /^\/latest$/.test(lp), list = /^\/manga\/\w+$/.test(lp), book = /^\/bookcase$/.test(lp), rank = /^\/rank/.test(lp), user = /^\/user/.test(lp), addGlobalStyle = css => { let style = document.createElement('style'); style.type = 'text/css'; style.innerHTML = css; document.head.appendChild(style); }, hasTouchEvents = () => { if (('ontouchstart' in window) || (navigator.maxTouchPoints > 0) || (navigator.msMaxTouchPoints > 0)) { return true; } return false; }, loadMore = selector => { let loadMoreButton = ge(selector); if (hasTouchEvents()) { let dispatchTouchEvent = (ele, type) => { let touchEvent = document.createEvent('UIEvent'); touchEvent.initUIEvent(type, true, true); touchEvent.touches = [{ clientX: 1, clientY: 1 }]; ele.dispatchEvent(touchEvent); }; dispatchTouchEvent(loadMoreButton, "touchstart"); dispatchTouchEvent(loadMoreButton, "touchend"); console.log('嗨皮漫畫模擬觸控點擊'); //loadMoreButton.dispatchEvent(new Event("touchstart")); //loadMoreButton.dispatchEvent(new Event("touchend")); } else { loadMoreButton.click(); console.log('嗨皮漫畫模擬點擊'); } }, openInNewTab = () => gae('.home-banner a:not([target=_blank]),.manga-rank a:not([target=_blank]),.manga-cover a:not([target=_blank])').forEach(a => { a.setAttribute('target', '_blank'); }), waitEle = selector => { return new Promise(resolve => { let loop = setInterval(() => { if (!!ge(selector) === true) { clearInterval(loop); resolve(); } }, 100); }); }, preLoad = (pn, text) => { let lps = pn.split('/'), mangaCode = lps[2], id = lps[3], apiUrl = `/v2.0/apis/manga/read?code=${mangaCode}&cid=${id}&v=v2.13`; fetch(apiUrl, { "headers": { "accept": "application/json, text/plain, */*", "x-requested-with": "XMLHttpRequest" } }).then(res => res.json()).then(jsonData => { try { if (jsonData.status == 0) { let imgs = jsonData.data.scans; let F = new DocumentFragment(); for (let i in imgs) { let img = new Image(); img.src = imgs[i].url; F.appendChild(img); } console.log(text + '漫畫名稱:' + jsonData.data.manga_name + '\n章節名稱:' + jsonData.data.chapter_name + '\n', jsonData, '\n', '圖片預讀\n', F); } else if (jsonData.status == 403) { console.log(text + '獲取數據失敗\n', jsonData); } } catch (error) { console.error(error); } }).catch(error => console.error(error)); }; if (ge(".no-js")) return; //Cloudflare 檢測連線安全性時,不執行後續的代碼。 if (options.oint && !read && !list && !user) { openInNewTab(); console.log('嗨皮漫畫在新分頁打開漫畫鏈接'); new MutationObserver(() => { openInNewTab(); }).observe(document.body, { childList: true, subtree: true }); } if (options.lM && latest) { new IntersectionObserver(entries => { if (entries[0].isIntersecting) { loadMore('.more-div-btn'); console.log('載入更多'); } }).observe(ge('.more-div-btn')); } if (options.list && list) { setTimeout(() => { ge('#expandButton').click(); console.log('嗨皮漫畫自動展開目錄'); }, 1000); } if (options.hE && read) { addGlobalStyle('#root>div>header+div{display:none!important}'); } if (options.kn && read) { document.addEventListener('keydown', (e) => { let key = window.event ? e.keyCode : e.which; if (key == 39) { let n = ge("//a[span[text()='下一话' or text()='下一話'] and contains(@href,'reads')]"); if (n) { location.href = n.href; } else { alert('沒有下一话了!'); } } }); } if (options.kp && read) { document.addEventListener('keydown', (e) => { let key = window.event ? e.keyCode : e.which; if (key == 37) { let p = ge("//a[span[text()='上一话' or text()='上一話'] and contains(@href,'reads')]"); if (p) { location.href = p.href; } else { alert('沒有上一话了!'); } } }); } if (options.dn && read) { document.addEventListener('dblclick', () => { let n = ge('footer a'); location.href = n.href; }); } if (options.kdn[0] && read) { let timeId; document.addEventListener('keypress', (e) => { let key = window.event ? e.keyCode : e.which; if (key == 32) { let n = ge("//a[span[text()='下一话' or text()='下一話'] and contains(@href,'reads')]"); if (n) { timeId = setTimeout(() => { location.href = n.href; }, options.kdn[1]); } else { timeId = setTimeout(() => { alert('沒有下一話了!'); }, options.kdn[1]); } } }); document.addEventListener('keyup', (e) => { let key = window.event ? e.keyCode : e.which; if (key == 32) { clearTimeout(timeId); } }); } if (options.pl && read) { await waitEle('[id^=imageLoader]'); console.log('嗨皮漫畫預讀全部圖片'); preLoad(lp, '嗨皮漫畫本話數據\n'); setTimeout(() => { let next = ge("//span[@id and text()='下一话' or text()='下一話']/following-sibling::a[1][contains(@href,'reads')]"); if (next) { preLoad(next.pathname, '嗨皮漫畫下一話數據\n'); } }, 3000); } if (options.nE && read) { await waitEle('#page-area'); addGlobalStyle('footer>article>div{padding: 0.5rem 0 !important}'); new IntersectionObserver((entries, observer) => { if (entries[0].isIntersecting) { observer.unobserve(entries[0].target); let f = ge('footer>article'); let c1 = f.firstChild.cloneNode(true); c1.firstChild.href = '/latest'; c1.firstChild.firstChild.innerText = '更新'; f.appendChild(c1); let c2 = f.firstChild.cloneNode(true); c2.firstChild.href = '/bookcase'; c2.firstChild.firstChild.innerText = '收藏'; f.appendChild(c2); let p = ge("//a[span[text()='上一话' or text()='上一話'] and contains(@href,'reads')]"); if (p) { p.classList.add('MuiButton-containedPrimary'); } let n = ge("//a[span[text()='下一话' or text()='下一話'] and contains(@href,'readMore')]"); if (n) { n.classList.remove('MuiButton-containedPrimary'); n.firstChild.innerText = '^_^感谢您的阅读~已经没有下一话了哦~'; } } }).observe(ge('#page-area')); } if (options.ion[0] && read) { await waitEle("//a[span[text()='下一话' or text()='下一話']]"); let timeId; new IntersectionObserver(entries => { if (entries[0].isIntersecting) { timeId = setTimeout(() => { let n = ge("//a[span[text()='下一话' or text()='下一話'] and contains(@href,'reads')]"); if (n) location.href = n.href; }, options.ion[1]); } else { clearTimeout(timeId); } }, { threshold: 1, }).observe(ge('footer a')); } })();