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