// ==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'));
}
})();