Greasy Fork is available in English.

微信读书自动阅读

微信读书阅读自动滚动, 触底自动翻页

// ==UserScript==
// @name		微信读书自动阅读
// @name:en		WeChatReadAutoScroll
// @description		微信读书阅读自动滚动, 触底自动翻页
// @description:en	微信读书阅读自动滚动, 触底自动翻页
// @version		1.0.0
// @match		https://weread.qq.com/web/reader/*
// @author		Yiero
// @icon		https://weread.qq.com/favicon.ico
// @namespace		https://github.com/AliubYiero/TamperMonkeyScripts/
// @license		GPL
// @grant		GM_registerMenuCommand
// @grant		GM_unregisterMenuCommand
// @grant		GM_setValue
// @grant		GM_getValue
// @grant		GM_deleteValue
// @grant		GM_listValues
// @run-at		document-idle
// ==/UserScript==
var __defProp = Object.defineProperty;

var __defNormalProp = (obj, key, value2) => key in obj ? __defProp(obj, key, {
    enumerable: true,
    configurable: true,
    writable: true,
    value: value2
}) : obj[key] = value2;

var __publicField = (obj, key, value2) => {
    __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value2);
    return value2;
};

class Info {
    constructor(projectName) {
        __publicField(this, "projectName");
        __publicField(this, "header");
        this.projectName = projectName;
        this.header = `[${projectName}]`;
    }
    log(...msg) {
        (() => {})(...this.contentInfo(...msg));
    }
    info(...msg) {
        console.info(...this.contentInfo(...msg));
    }
    warn(...msg) {
        console.warn(...this.contentInfo(...msg));
    }
    error(...msg) {
        console.error(...this.contentInfo(...msg));
    }
    contentInfo(...msg) {
        return [ this.header, `[${(new Date).toLocaleString("zh-ch")}]`, ...msg ];
    }
}

function MathCeilFloat(number, decimal = 0) {
    decimal = Math.pow(10, decimal);
    return Math.ceil(number * decimal) / decimal;
}

function isReachedPageBottom() {
    const {scrollTop: scrollTop, clientHeight: clientHeight, scrollHeight: scrollHeight} = document.documentElement;
    return scrollTop + clientHeight >= scrollHeight;
}

class ScrollOptionsImpl {
    constructor() {
        __publicField(this, "_delayPerMs", 0);
        __publicField(this, "_frameDuration", 0);
        __publicField(this, "_framePerSecond", 0);
        __publicField(this, "_movementDistancePerSecond", 0);
    }
    get delayPerMs() {
        return this._delayPerMs;
    }
    set delayPerMs(value) {
        if (typeof value === "string") {
            const scrollPageDuration = window.innerHeight / this.movementDistancePerSecond * 1e3;
            print.log(scrollPageDuration, this.movementDistancePerSecond);
            try {
                this._delayPerMs = eval(value.replace("auto", String(scrollPageDuration)));
            } catch (e) {
                this._delayPerMs = scrollPageDuration;
            }
        } else {
            this._delayPerMs = value;
        }
    }
    get movementDistancePerFrame() {
        return MathCeilFloat(this.movementDistancePerSecond / this.framePerSecond, 3);
    }
    get frameDuration() {
        return MathCeilFloat(1e3 / this._frameDuration);
    }
    get framePerSecond() {
        return this._framePerSecond;
    }
    set framePerSecond(value2) {
        this._framePerSecond = value2;
    }
    get movementDistancePerSecond() {
        return this._movementDistancePerSecond;
    }
    set movementDistancePerSecond(value2) {
        this._movementDistancePerSecond = value2;
    }
}

class GlobalAutoScroll {
    constructor(movementDistancePerSecond, delayPerMs = "auto", framePerSecond = 60) {
        __publicField(this, "options", new ScrollOptionsImpl);
        __publicField(this, "timer");
        this.options.movementDistancePerSecond = movementDistancePerSecond;
        this.options.delayPerMs = delayPerMs;
        this.options.framePerSecond = framePerSecond;
    }
    open() {
        if (this.timer) {
            this.close();
        }
        scrollStatusStorage.set("scrolling");
        print.info("等待滚动: ", this.options.delayPerMs);
        this.timer = window.setTimeout((() => {
            this.timer = window.setInterval((() => {
                this.scroll();
                if (isReachedPageBottom()) {
                    this.close();
                }
            }), this.options.frameDuration);
        }), this.options.delayPerMs);
    }
    close() {
        scrollStatusStorage.set("scroll-end");
        clearTimeout(this.timer);
        clearInterval(this.timer);
        this.timer = void 0;
    }
    scroll() {
        const movementDistancePerFrame = this.options.movementDistancePerFrame;
        scrollBy(0, movementDistancePerFrame);
    }
}

class GMConfigMenu {
    constructor(callback) {
        __publicField(this, "menuId", 0);
        __publicField(this, "callback");
        this.callback = callback;
    }
    open(title) {
        if (this.menuId) {
            this.close();
        }
        this.menuId = GM_registerMenuCommand(title, this.callback);
    }
    close() {
        GM_unregisterMenuCommand(this.menuId);
        this.menuId = 0;
    }
}

class LocalStorage {
    constructor(key) {
        __publicField(this, "key");
        this.key = key;
    }
    get(defaultValue) {
        return localStorage.getItem(this.key) || defaultValue || "";
    }
    remove() {
        const oldValue = this.get();
        const newValue = void 0;
        dispatchEvent(new CustomEvent("storageUpdate", {
            detail: {
                key: this.key,
                newValue: newValue,
                oldValue: oldValue
            }
        }));
        localStorage.removeItem(this.key);
    }
    set(value2) {
        const oldValue = this.get();
        const newValue = value2;
        dispatchEvent(new CustomEvent("storageUpdate", {
            detail: {
                key: this.key,
                newValue: newValue,
                oldValue: oldValue
            }
        }));
        localStorage.setItem(this.key, value2);
    }
}

class SessionStorage {
    constructor(key) {
        __publicField(this, "key");
        this.key = key;
    }
    get(defaultValue) {
        return sessionStorage.getItem(this.key) || defaultValue || "";
    }
    remove() {
        const oldValue = this.get();
        const newValue = void 0;
        dispatchEvent(new CustomEvent("storageUpdate", {
            detail: {
                key: this.key,
                newValue: newValue,
                oldValue: oldValue
            }
        }));
        sessionStorage.removeItem(this.key);
    }
    set(value2) {
        const oldValue = this.get();
        const newValue = value2;
        dispatchEvent(new CustomEvent("storageUpdate", {
            detail: {
                key: this.key,
                newValue: newValue,
                oldValue: oldValue
            }
        }));
        sessionStorage.setItem(this.key, value2);
    }
}

new MouseEvent("mouseenter", {
    button: 0,
    bubbles: true,
    cancelable: true,
    clientX: 819,
    clientY: 413
});

new MouseEvent("mousedown", {
    bubbles: true,
    cancelable: true,
    button: 0
});

new MouseEvent("mouseup", {
    bubbles: true,
    cancelable: true,
    button: 0
});

new MouseEvent("click", {
    bubbles: true,
    cancelable: true,
    button: 0
});

function fireKeyEvent(el = document, evtType, keyCode) {
    let evtObj;
    if (document.createEvent) {
        if (window.KeyEvent) {
            evtObj = document.createEvent("KeyEvents");
            evtObj.initKeyEvent(evtType, true, true);
            el.dispatchEvent(evtObj);
            return;
        }
        evtObj = document.createEvent("UIEvents");
        evtObj.initUIEvent(evtType, true, true);
        delete evtObj.keyCode;
        if (typeof evtObj.keyCode === "undefined") {
            Object.defineProperty(evtObj, "keyCode", {
                value: keyCode
            });
        } else {
            evtObj.key = String.fromCharCode(keyCode);
        }
        if (typeof evtObj.ctrlKey === "undefined") {
            Object.defineProperty(evtObj, "ctrlKey", {
                value: true
            });
        } else {
            evtObj.ctrlKey = true;
        }
        el.dispatchEvent(evtObj);
        return;
    }
}

const print = new Info("WeChatReadAutoScroll");

const scrollStatusStorage = new SessionStorage("scrollStatus");

(async () => {
    const scrollSpeedController = new GMConfigMenu((() => {
        const result = prompt("页面滚动速度(px/s)");
        if (!result) {
            return;
        }
        localStorage.setItem("scrollSpeed", result);
    }));
    scrollSpeedController.open("[配置] 滚动速度");
    const scrollDelayController = new GMConfigMenu((() => {
        const result = prompt("滚动开启延时 ( auto | (auto / 2) | number ) (ms)");
        if (!result) {
            return;
        }
        localStorage.setItem("scrollDelay", result);
    }));
    scrollDelayController.open("[配置] 滚动开启延时");
    function scrollSwitch() {
        let globalAutoScroll;
        const scrollOpenController = new GMConfigMenu(scrollOpenMenuCallback);
        function scrollOpenMenuCallback() {
            const scrollSpeedStorage = new LocalStorage("scrollSpeed");
            const scrollDelayStorage = new LocalStorage("scrollDelay");
            globalAutoScroll = new GlobalAutoScroll(+scrollSpeedStorage.get("100"), scrollDelayStorage.get("auto"), 60);
            globalAutoScroll.open();
            window.addEventListener("click", (() => {
                globalAutoScroll.close();
            }), {
                once: true
            });
            scrollOpenController.close();
            scrollCloseController.open("关闭滚动");
        }
        const scrollCloseController = new GMConfigMenu((() => {
            globalAutoScroll.close();
            scrollCloseMenuCallback();
        }));
        function scrollCloseMenuCallback() {
            scrollCloseController.close();
            scrollOpenController.open("开启滚动");
        }
        scrollOpenController.open("开启滚动");
        scrollStatusStorage.set("scroll-start");
        window.addEventListener("storageUpdate", (e => {
            const event = e;
            if (event.detail.key === "scrollStatus" && event.detail.newValue === "scroll-end") {
                scrollCloseMenuCallback();
                if (isReachedPageBottom()) {
                    fireKeyEvent(document, "keydown", 39);
                    scrollOpenMenuCallback();
                }
            }
        }));
    }
    scrollSwitch();
})();