嗨皮漫畫閱讀輔助(自用)

增加一些輔助閱讀功能。

目前为 2023-02-19 提交的版本。查看 最新版本

// ==UserScript==
// @name         嗨皮漫畫閱讀輔助(自用)
// @version      1.1
// @description  增加一些輔助閱讀功能。
// @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==

(function() {
    'use strict';
    const options = { //true 開啟,false 關閉
        kn: true, //按鍵盤右方向鍵前往下一話。
        kp: true, //按鍵盤左方向鍵前往上一話。
        dn: true, //雙擊前往下一話,方便手機使用。
        kdn: [false, 300], //按住空白鍵超過幾ms下一話。
        nE: true, //閱讀頁底部增加更新頁和收藏頁的按鈕。
        sV: [true, 1000], //閱讀前先花幾ms時間捲動至每張圖片的位置,使其觸發所有圖片請求載入全部圖片,捲完後返回頂部,在手機上使用如果圖片太多會效果不佳不一定會全部觸發。
        hE: true, //隱藏閱讀頁頂部的公告。
        ion: [false, 100], //下一話按鈕完全進入視窗可視範圍內時經過幾ms後自動下一話。
        ac: true, //更新頁自動點擊載入更多。
        list: true, //目錄頁自動展開全部章節。
        oint: true //漫畫在新分頁打開。
    }
    const ge = e => document.querySelector(e);
    const gx = x => document.evaluate(x, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
    const read = /^\/reads\/\w+\/\d+$/.test(location.pathname);
    const latest = /^\/latest$/.test(location.pathname);
    const list = /^\/manga\/\w+$/.test(location.pathname);
    const book = /^\/bookcase$/.test(location.pathname);
    const addGlobalStyle = css => {
        let style = document.createElement('style');
        style.innerText = css;
        document.head.appendChild(style);
    }
    const hasTouchEvents = () => {
        if (('ontouchstart' in window) || (navigator.maxTouchPoints > 0) || (navigator.msMaxTouchPoints > 0)) {
            return true
        }
        return false
    }
    const autoClick = e => {
        let btn = ge(e);
        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(btn, "touchstart");
            dispatchTouchEvent(btn, "touchend");
        } else {
            btn.click();
        }
    }
    const openInNewTab = () => document.querySelectorAll('.manga-cover a:not([target=_blank])').forEach(a => {
        a.setAttribute('target', '_blank');
    });

    if (options.oint) {
        openInNewTab();
        new MutationObserver(() => {
            openInNewTab();
        }).observe(document.body, {
            childList: true,
            subtree: true
        });
    }

    if (options.ac && latest) {
        new IntersectionObserver((e) => {
            if (e[0].isIntersecting) {
                autoClick('.more-div-btn');
            }
        }).observe(ge('.more-div-btn'));
    }

    if (options.list && list) {
        autoClick('#expandButton');
    }

    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 = gx("//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 = gx("//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 = gx("//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.nE && read) {
        setTimeout(() => {
            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('footer p:nth-child(2)>a[href*=reads]');
            if (p) {
                p.classList.add('MuiButton-containedPrimary');
            }
            let n = ge('footer a[href*=readMore]');
            if (n) {
                n.classList.remove('MuiButton-containedPrimary');
                n.firstChild.innerText = '^_^感谢您的阅读~已经没有下一话了哦~';
            }
        }, 4000)
    }

    if (options.sV[0] && read) {
        let loop = setInterval(() => {
            let iL = ge('[id^=imageLoader]');
            if (iL) {
                clearInterval(loop);
                let imgs = iL.parentNode.children;
                let n = imgs.length - 1;
                let ms = Math.ceil(options.sV[1] / n);
                for (let i = 1; i <= n; i++) {
                    setTimeout(() => {
                        imgs[i].scrollIntoView();
                        if (i == n) {
                            window.scrollTo(0, 0);
                        }
                    }, i * ms)
                }
            }
        }, 100)
    }

    if (options.ion[0] && read) {
        setTimeout(() => {
            new IntersectionObserver((e) => {
                if (e[0].isIntersecting) {
                    setTimeout(() => {
                        let n = gx("//a[span[text()='下一话' or text()='下一話'] and contains(@href,'reads')]");
                        if (n) location.href = n.href;
                    }, options.ion[1])
                }
            }, {
                threshold: 1,
            }).observe(ge('footer a'));
        }, 4000)
    }

})();