- // ==UserScript==
- // @name 微信读书Weread阅读综合功能版
- // @version 0.3.8
- // @author !Sylas
- // @contributor SimonDW;Li_MIxdown;hubzy;xvusrmqj;LossJ;JackieZheng;das2m;harmonyLife
- // @namespace http://tampermonkey.net/
- // @description 微信读书的阅读字体修改为苍耳今楷,加减宽度,鼠标离开显示隐藏导航栏、功能栏、滚动条,多档滚动速度,自动翻页,仅适配weread.qq.com站点
- // @match https://weread.qq.com/web/reader/*
- // @icon https://weread.qq.com/favicon.ico
- // @grant GM_addStyle
- // @grant unsafeWindow
- // @license MIT
- // ==/UserScript==
-
- GM_addStyle(`
- * {
- font-family: SourceHanSerifCN-Bold !important;
- }
-
- body {
- overflow: hidden;
- }
-
- .readerControls {
- width: 120px; margin-left: calc(50% - 120px); display: flex; flex-wrap: wrap; flex-direction: row;
- }
-
- .readerTopBar, .readerControls {
- opacity: 0; transition: opacity 1s;
- }
-
- .readerControls_item, .readerControls_fontSize {
- margin-right: 10px; color:#6a6c6c; cursor:pointer;
- }
-
- .readerChapterContent { margin-left: 30px !important; margin-right: 30px !important; }
-
- .readerControls:hover, .readerTopBar:hover {
- opacity: 1;
- }
-
- body:hover {
- overflow: auto;
- }
- `);
-
- const ElementUtils = {
- cssGet: (tar_elm, property) => {
- /**
- * 获取 css 属性
- *
- * tar_elm: elementNode
- * property: string
- */
- return window.getComputedStyle(tar_elm).getPropertyValue(property);
- },
- cssSet: (tar_elm, property, value, priority) => {
- /**
- * 设置 css 属性
- *
- * tar_elm: node
- * property: string
- * value: string | number | null
- * priority: string | null
- */
- return tar_elm.style.setProperty(property, value, priority);
- },
- htmlGet: (tar_elm, is_out) => {
- /**
- * 获取 HTML 代码
- *
- * tar_elm: node
- * is_in: bool
- */
- if (is_out) {
- return tar_elm.outerHTML;
- }
- return tar_elm.innerHTML;
- },
- insertHtml: (tar_elm, ins_html, position) => {
- /**
- * 在目标元素指定位置插入 HTML 代码
- *
- * tar_elm: node
- * ins_html: string
- * position: string -> 可选项:beforeBegin、afterBegin、beforeEnd、afterEnd
- */
- position = typeof position === "undefined" ? "beforeEnd" : position;
-
- tar_elm.insertAdjacentHTML(position, ins_html);
- },
- insertElement: (tar_elm, ins_elm, position) => {
- /**
- * 在目标元素指定位置插入元素
- *
- * tar_elm: node
- * ins_elm: node
- * position: string -> 可选项:beforeBegin、afterBegin、beforeEnd、afterEnd
- */
- position = typeof position === "undefined" ? "beforeEnd" : position;
- tar_elm.insertAdjacentElement(position, ins_elm);
- },
- };
-
- let div_body = $css("body");
- let div_controls = $css(".readerControls");
- let div_top_bar = $css(".readerTopBar");
- let btn_width_add = $css("#width-add");
- let btn_width_dev = $css("#width-dev");
- let btn_scroll_on = $css("#scroll-on");
- let btn_scroll_off = $css("#scroll-off");
- let btn_turn_tips = $css("#turn-page-tips");
- let scroll_speed = 0;
-
- (async function () {
- "use strict";
-
- // 最多等待 30 秒页面加载完成
- let book = await waitElement(".wr_canvasContainer canvas", 30000);
- if (!book) {
- alert("书本内容加载失败,请手动刷新页面!");
- return;
- }
-
- div_body = $css("body");
- div_controls = $css(".readerControls");
- div_top_bar = $css(".readerTopBar");
- // 添加功能按键;
- div_controls.insertHtml(`
- <button title="等待翻页" id='turn-page-tips' class='readerControls_item'>等待翻页</button>
- <button title="加宽" id='width-add' class='readerControls_item'>加宽</button>
- <button title="减宽" id='width-dev' class='readerControls_item'>减宽</button>
- <button title="播放X0" id='scroll-on' class='readerControls_item'>播放X0</button>
- <button title="停止播放" id='scroll-off' class='readerControls_item'>停止播放</button>
- `);
- btn_width_add = $css("#width-add");
- btn_width_dev = $css("#width-dev");
- btn_scroll_on = $css("#scroll-on");
- btn_scroll_off = $css("#scroll-off");
- btn_turn_tips = $css("#turn-page-tips");
-
- // 额外功能按钮功能实现
- btn_width_add.onclick = () => changeWidth(true);
- btn_width_dev.onclick = () => changeWidth(false);
- btn_scroll_on.onclick = () => {
- scroll_speed++;
- if (scroll_speed == 1) {
- autoScroll();
- }
- btn_scroll_on.innerHTML = "播放X" + scroll_speed;
- };
- btn_scroll_off.onclick = () => {
- scroll_speed = 0;
- btn_scroll_on.innerHTML = "播放X0";
- };
-
- // // 顶部导航栏优化
- // let last_scroll_top = getScrollTop();
- // let opacity = 1;
- // window.onscroll = () => {
- // let curr_scroll_top = getScrollTop();
- // if (curr_scroll_top < last_scroll_top) {
- // // 上划显示
- // opacity = opacity + 0.05 >= 1 ? 1 : opacity + 0.05;
- // } else {
- // // 上划隐藏
- // opacity = opacity - 0.03 <= 0 ? 0 : opacity - 0.03;
- // }
- // div_top_bar.cssSet("opacity", opacity);
- // div_controls.cssSet("opacity", opacity);
- // last_scroll_top = curr_scroll_top;
- // };
- })();
-
- function changeWidth(isAdd, step) {
- step = typeof step === "undefined" ? 60 : step;
- let div_content = $css(".app_content");
- let width = Number(div_content.cssGet("max-width").replace("px", ""));
-
- if (isAdd) {
- width += step;
- } else {
- width -= step;
- }
- div_content.cssSet("max-width", width + "px");
- div_top_bar.cssSet("max-width", width + "px");
- let resize_event = new Event("resize");
- window.dispatchEvent(resize_event);
- }
-
- // 滑动屏幕,滚至页面底部
- async function autoScroll(step, delay) {
- step = typeof step === "undefined" ? 1 : step;
- delay = typeof delay === "undefined" ? 1000 : delay;
- while (scroll_speed > 0) {
- window.scrollBy(0, step);
- if (isPageBottom()) {
- await nextPage();
- }
- await sleep(delay / scroll_speed / scroll_speed);
- if (isShowInView($css(".readerFooter_ending"))) {
- scroll_speed = 0;
- btn_scroll_on.innerHTML = "播放X0";
- }
- }
- }
-
- async function nextPage(sleep_time) {
- sleep_time = typeof sleep_time === "undefined" ? 6000 : sleep_time;
- let btn_turn = document.querySelector(".readerFooter_button");
- while (true) {
- if (isShowInView(btn_turn) && scroll_speed > 0) {
- console.log(`wait ${sleep_time / 1000} seconds. turn page.`);
- let last_time = sleep_time / 1000;
- while (last_time > 0) {
- btn_turn_tips.innerHTML = `${last_time}s翻页`;
- last_time--;
- await sleep(1000);
- }
- pressKey("right");
- await sleep(3000);
- btn_turn_tips.innerHTML = `等待翻页`;
- break;
- }
- }
- }
-
- /** ------ 常用函数 ------ **/
-
- function initElement(elements) {
- /**
- * 初始化元素属性和函数
- *
- * element: elementNode
- */
- if (!Array.isArray(elements)) {
- elements = [elements];
- }
- for (let element of elements) {
- for (let fn_name in ElementUtils) {
- element[fn_name] = (...args) => {
- return ElementUtils[fn_name](element, ...args);
- };
- }
- }
- }
-
- function $css(query) {
- /**
- * css 选择器,单元素
- *
- * query: string
- */
- let elm = document.querySelector(query);
- if (!elm) {
- return;
- }
- initElement(elm);
- return elm;
- }
-
- function $cssAll(query) {
- /**
- * css 选择器,单元素
- *
- * query: string
- */
- let elms = Array.apply(null, document.querySelectorAll(query));
- if (elms.length === 0) {
- return;
- }
- initElement(elms);
- return elms;
- }
-
- function $xpa(query, node) {
- /**
- * xpath 选择器,单元素
- *
- * query: string
- * node: elementNode
- */
- node = typeof node === "undefined" ? document : node;
- let elm = document.evaluate(query, node).iterateNext();
- if (!elm) {
- return;
- }
- initElement(elm);
- return elm;
- }
-
- function $xpaAll(query, node) {
- /**
- * xpath 选择器,多元素
- *
- * query: string
- * node: elementNode
- */
- node = typeof node === "undefined" ? document : node;
- let elm_list = [];
- let elm_box = document.evaluate(query, document);
- let elm = elm_box.iterateNext();
- if (!elm) {
- return;
- }
- while (elm) {
- elm_list.push(elm);
- elm = elm_box.iterateNext();
- }
- initElement(elm_list);
- return elm_list;
- }
-
- function sleep(ms) {
- ms = typeof ms === "undefined" ? 1000 : ms;
- /**
- * 休眠函数,单位:ms
- * 使用方法:
- * sleep(500).then(() => { Do something after the sleep! });
- * 或者
- * await sleep(500); Do something after the sleep!
- */
- return new Promise((resolve) => setTimeout(resolve, ms));
- }
-
- function isShowInView(element) {
- /**
- * 判断元素是否出现在视窗中
- */
- if (element === null || element === undefined) {
- return false;
- }
- let documentHeight = Math.max(
- document.documentElement.scrollHeight,
- document.documentElement.offsetHeight,
- document.documentElement.clientHeight
- );
- let screenHeight = window.innerHeight || documentHeight;
- let screenWidth = window.innerWidth || document.documentElement.clientWidth;
- let { top, right, bottom, left } = element.getBoundingClientRect();
- return (
- top >= 0 && left >= 0 && right <= screenWidth && bottom <= screenHeight
- );
- }
-
- function isExistElement(css_selector) {
- /**
- * 判断指定元素是否存在页面中
- */
- if ($css(css_selector)) {
- return true;
- }
- return false;
- }
-
- async function waitElement(css_selector, max_wait_ms) {
- /**
- * 等待指定元素出现
- */
- max_wait_ms = typeof max_wait_ms === "undefined" ? 3000 : max_wait_ms;
- let start_time = new Date().getTime();
- let elm = $css(css_selector);
- while (!elm && start_time + max_wait_ms >= new Date().getTime()) {
- await sleep(300);
- elm = $css(css_selector);
- if (elm) {
- break;
- }
- }
- return elm;
- }
-
- function getScrollTop() {
- /**
- * 滚动条在Y轴上已经滚动的距离
- */
- return document.documentElement.scrollTop || document.body.scrollTop;
- }
-
- function getScrollHeight() {
- /**
- * 整个页面的高度
- */
- return document.documentElement.scrollHeight || document.body.scrollHeight;
- }
-
- function getWindowHeight() {
- /**
- * 浏览器可视窗口高度
- */
- return document.documentElement.clientHeight || document.body.clientHeight;
- }
-
- function isPageBottom(deviation_value) {
- /**
- * 判断页面是否滚动到底,默认允许3个像素的误差,大部分情况下总是会差0.3-0.8像素
- */
- deviation_value =
- typeof deviation_value === "undefined" ? 3 : deviation_value;
- if (
- getScrollHeight() - getScrollTop() - getWindowHeight() <
- deviation_value
- ) {
- return true;
- }
- return false;
- }
-
- function pressKeyByCode(code) {
- /**
- * 按键触发,根据传入的按键编号
- *
- * code: Number
- */
- return document.dispatchEvent(
- new KeyboardEvent("keydown", {
- bubbles: true,
- cancelable: true,
- keyCode: code,
- })
- );
- }
-
- function pressKey(name) {
- /**
- * 按键触发,根据传入的按键名
- *
- * name: string
- */
- const KeyNameToCode = {
- back: 8,
- tab: 9,
- clear: 12,
- enter: 13,
- shift: 16,
- ctrl: 17,
- alt: 18,
- capelock: 20,
- esc: 27,
- space: 32,
- pageup: 33,
- pagedown: 34,
- end: 35,
- home: 36,
- left: 37,
- up: 38,
- right: 39,
- down: 40,
- insert: 45,
- delete: 46,
- 0: 48,
- 1: 49,
- 2: 50,
- 3: 51,
- 4: 52,
- 5: 53,
- 6: 54,
- 7: 55,
- 8: 56,
- 9: 57,
- a: 65,
- b: 66,
- c: 67,
- d: 68,
- e: 69,
- f: 70,
- g: 71,
- h: 72,
- i: 73,
- j: 74,
- k: 75,
- l: 76,
- m: 77,
- n: 78,
- o: 79,
- p: 80,
- q: 81,
- r: 82,
- s: 83,
- t: 84,
- u: 85,
- v: 86,
- w: 87,
- x: 88,
- y: 89,
- z: 90,
- f1: 112,
- f2: 113,
- f3: 114,
- f4: 115,
- f5: 116,
- f6: 117,
- f7: 118,
- f8: 119,
- f9: 120,
- f10: 121,
- f11: 122,
- f12: 123,
-
- // 数字小键盘部分
- n0: 96,
- n1: 97,
- n2: 98,
- n3: 99,
- n4: 100,
- n5: 101,
- n6: 102,
- n7: 103,
- n8: 104,
- n9: 105,
- "n*": 106,
- "n+": 107,
- nenter: 108,
- "n-": 109,
- "n.": 110,
- "n/": 111,
-
- numlock: 144,
- ";": 186,
- ":": 186,
- "=": 187,
- "+": 187,
- ",": 188,
- "<": 188,
- "-": 189,
- _: 189,
- ".": 190,
- ">": 190,
- "/": 191,
- "?": 191,
- "`": 192,
- "~": 192,
- "[": 219,
- "{": 219,
- "\\": 220,
- "|": 220,
- "]": 221,
- "}": 221,
- "'": 222,
- '"': 222,
- };
-
- return pressKeyByCode(KeyNameToCode[name.toLowerCase()]);
- }