Greasy Fork is available in English.

微信读书Weread阅读综合功能版

微信读书的阅读字体修改为苍耳今楷,加减宽度,鼠标离开显示隐藏导航栏、功能栏、滚动条,多档滚动速度,自动翻页,仅适配weread.qq.com站点

  1. // ==UserScript==
  2. // @name 微信读书Weread阅读综合功能版
  3. // @version 0.3.8
  4. // @author !Sylas
  5. // @contributor SimonDW;Li_MIxdown;hubzy;xvusrmqj;LossJ;JackieZheng;das2m;harmonyLife
  6. // @namespace http://tampermonkey.net/
  7. // @description 微信读书的阅读字体修改为苍耳今楷,加减宽度,鼠标离开显示隐藏导航栏、功能栏、滚动条,多档滚动速度,自动翻页,仅适配weread.qq.com站点
  8. // @match https://weread.qq.com/web/reader/*
  9. // @icon https://weread.qq.com/favicon.ico
  10. // @grant GM_addStyle
  11. // @grant unsafeWindow
  12. // @license MIT
  13. // ==/UserScript==
  14.  
  15. GM_addStyle(`
  16. * {
  17. font-family: SourceHanSerifCN-Bold !important;
  18. }
  19.  
  20. body {
  21. overflow: hidden;
  22. }
  23.  
  24. .readerControls {
  25. width: 120px; margin-left: calc(50% - 120px); display: flex; flex-wrap: wrap; flex-direction: row;
  26. }
  27.  
  28. .readerTopBar, .readerControls {
  29. opacity: 0; transition: opacity 1s;
  30. }
  31.  
  32. .readerControls_item, .readerControls_fontSize {
  33. margin-right: 10px; color:#6a6c6c; cursor:pointer;
  34. }
  35.  
  36. .readerChapterContent { margin-left: 30px !important; margin-right: 30px !important; }
  37.  
  38. .readerControls:hover, .readerTopBar:hover {
  39. opacity: 1;
  40. }
  41.  
  42. body:hover {
  43. overflow: auto;
  44. }
  45. `);
  46.  
  47. const ElementUtils = {
  48. cssGet: (tar_elm, property) => {
  49. /**
  50. * 获取 css 属性
  51. *
  52. * tar_elm: elementNode
  53. * property: string
  54. */
  55. return window.getComputedStyle(tar_elm).getPropertyValue(property);
  56. },
  57. cssSet: (tar_elm, property, value, priority) => {
  58. /**
  59. * 设置 css 属性
  60. *
  61. * tar_elm: node
  62. * property: string
  63. * value: string | number | null
  64. * priority: string | null
  65. */
  66. return tar_elm.style.setProperty(property, value, priority);
  67. },
  68. htmlGet: (tar_elm, is_out) => {
  69. /**
  70. * 获取 HTML 代码
  71. *
  72. * tar_elm: node
  73. * is_in: bool
  74. */
  75. if (is_out) {
  76. return tar_elm.outerHTML;
  77. }
  78. return tar_elm.innerHTML;
  79. },
  80. insertHtml: (tar_elm, ins_html, position) => {
  81. /**
  82. * 在目标元素指定位置插入 HTML 代码
  83. *
  84. * tar_elm: node
  85. * ins_html: string
  86. * position: string -> 可选项:beforeBegin、afterBegin、beforeEnd、afterEnd
  87. */
  88. position = typeof position === "undefined" ? "beforeEnd" : position;
  89.  
  90. tar_elm.insertAdjacentHTML(position, ins_html);
  91. },
  92. insertElement: (tar_elm, ins_elm, position) => {
  93. /**
  94. * 在目标元素指定位置插入元素
  95. *
  96. * tar_elm: node
  97. * ins_elm: node
  98. * position: string -> 可选项:beforeBegin、afterBegin、beforeEnd、afterEnd
  99. */
  100. position = typeof position === "undefined" ? "beforeEnd" : position;
  101. tar_elm.insertAdjacentElement(position, ins_elm);
  102. },
  103. };
  104.  
  105. let div_body = $css("body");
  106. let div_controls = $css(".readerControls");
  107. let div_top_bar = $css(".readerTopBar");
  108. let btn_width_add = $css("#width-add");
  109. let btn_width_dev = $css("#width-dev");
  110. let btn_scroll_on = $css("#scroll-on");
  111. let btn_scroll_off = $css("#scroll-off");
  112. let btn_turn_tips = $css("#turn-page-tips");
  113. let scroll_speed = 0;
  114.  
  115. (async function () {
  116. "use strict";
  117.  
  118. // 最多等待 30 秒页面加载完成
  119. let book = await waitElement(".wr_canvasContainer canvas", 30000);
  120. if (!book) {
  121. alert("书本内容加载失败,请手动刷新页面!");
  122. return;
  123. }
  124.  
  125. div_body = $css("body");
  126. div_controls = $css(".readerControls");
  127. div_top_bar = $css(".readerTopBar");
  128. // 添加功能按键;
  129. div_controls.insertHtml(`
  130. <button title="等待翻页" id='turn-page-tips' class='readerControls_item'>等待翻页</button>
  131. <button title="加宽" id='width-add' class='readerControls_item'>加宽</button>
  132. <button title="减宽" id='width-dev' class='readerControls_item'>减宽</button>
  133. <button title="播放X0" id='scroll-on' class='readerControls_item'>播放X0</button>
  134. <button title="停止播放" id='scroll-off' class='readerControls_item'>停止播放</button>
  135. `);
  136. btn_width_add = $css("#width-add");
  137. btn_width_dev = $css("#width-dev");
  138. btn_scroll_on = $css("#scroll-on");
  139. btn_scroll_off = $css("#scroll-off");
  140. btn_turn_tips = $css("#turn-page-tips");
  141.  
  142. // 额外功能按钮功能实现
  143. btn_width_add.onclick = () => changeWidth(true);
  144. btn_width_dev.onclick = () => changeWidth(false);
  145. btn_scroll_on.onclick = () => {
  146. scroll_speed++;
  147. if (scroll_speed == 1) {
  148. autoScroll();
  149. }
  150. btn_scroll_on.innerHTML = "播放X" + scroll_speed;
  151. };
  152. btn_scroll_off.onclick = () => {
  153. scroll_speed = 0;
  154. btn_scroll_on.innerHTML = "播放X0";
  155. };
  156.  
  157. // // 顶部导航栏优化
  158. // let last_scroll_top = getScrollTop();
  159. // let opacity = 1;
  160. // window.onscroll = () => {
  161. // let curr_scroll_top = getScrollTop();
  162. // if (curr_scroll_top < last_scroll_top) {
  163. // // 上划显示
  164. // opacity = opacity + 0.05 >= 1 ? 1 : opacity + 0.05;
  165. // } else {
  166. // // 上划隐藏
  167. // opacity = opacity - 0.03 <= 0 ? 0 : opacity - 0.03;
  168. // }
  169. // div_top_bar.cssSet("opacity", opacity);
  170. // div_controls.cssSet("opacity", opacity);
  171. // last_scroll_top = curr_scroll_top;
  172. // };
  173. })();
  174.  
  175. function changeWidth(isAdd, step) {
  176. step = typeof step === "undefined" ? 60 : step;
  177. let div_content = $css(".app_content");
  178. let width = Number(div_content.cssGet("max-width").replace("px", ""));
  179.  
  180. if (isAdd) {
  181. width += step;
  182. } else {
  183. width -= step;
  184. }
  185. div_content.cssSet("max-width", width + "px");
  186. div_top_bar.cssSet("max-width", width + "px");
  187. let resize_event = new Event("resize");
  188. window.dispatchEvent(resize_event);
  189. }
  190.  
  191. // 滑动屏幕,滚至页面底部
  192. async function autoScroll(step, delay) {
  193. step = typeof step === "undefined" ? 1 : step;
  194. delay = typeof delay === "undefined" ? 1000 : delay;
  195. while (scroll_speed > 0) {
  196. window.scrollBy(0, step);
  197. if (isPageBottom()) {
  198. await nextPage();
  199. }
  200. await sleep(delay / scroll_speed / scroll_speed);
  201. if (isShowInView($css(".readerFooter_ending"))) {
  202. scroll_speed = 0;
  203. btn_scroll_on.innerHTML = "播放X0";
  204. }
  205. }
  206. }
  207.  
  208. async function nextPage(sleep_time) {
  209. sleep_time = typeof sleep_time === "undefined" ? 6000 : sleep_time;
  210. let btn_turn = document.querySelector(".readerFooter_button");
  211. while (true) {
  212. if (isShowInView(btn_turn) && scroll_speed > 0) {
  213. console.log(`wait ${sleep_time / 1000} seconds. turn page.`);
  214. let last_time = sleep_time / 1000;
  215. while (last_time > 0) {
  216. btn_turn_tips.innerHTML = `${last_time}s翻页`;
  217. last_time--;
  218. await sleep(1000);
  219. }
  220. pressKey("right");
  221. await sleep(3000);
  222. btn_turn_tips.innerHTML = `等待翻页`;
  223. break;
  224. }
  225. }
  226. }
  227.  
  228. /** ------ 常用函数 ------ **/
  229.  
  230. function initElement(elements) {
  231. /**
  232. * 初始化元素属性和函数
  233. *
  234. * element: elementNode
  235. */
  236. if (!Array.isArray(elements)) {
  237. elements = [elements];
  238. }
  239. for (let element of elements) {
  240. for (let fn_name in ElementUtils) {
  241. element[fn_name] = (...args) => {
  242. return ElementUtils[fn_name](element, ...args);
  243. };
  244. }
  245. }
  246. }
  247.  
  248. function $css(query) {
  249. /**
  250. * css 选择器,单元素
  251. *
  252. * query: string
  253. */
  254. let elm = document.querySelector(query);
  255. if (!elm) {
  256. return;
  257. }
  258. initElement(elm);
  259. return elm;
  260. }
  261.  
  262. function $cssAll(query) {
  263. /**
  264. * css 选择器,单元素
  265. *
  266. * query: string
  267. */
  268. let elms = Array.apply(null, document.querySelectorAll(query));
  269. if (elms.length === 0) {
  270. return;
  271. }
  272. initElement(elms);
  273. return elms;
  274. }
  275.  
  276. function $xpa(query, node) {
  277. /**
  278. * xpath 选择器,单元素
  279. *
  280. * query: string
  281. * node: elementNode
  282. */
  283. node = typeof node === "undefined" ? document : node;
  284. let elm = document.evaluate(query, node).iterateNext();
  285. if (!elm) {
  286. return;
  287. }
  288. initElement(elm);
  289. return elm;
  290. }
  291.  
  292. function $xpaAll(query, node) {
  293. /**
  294. * xpath 选择器,多元素
  295. *
  296. * query: string
  297. * node: elementNode
  298. */
  299. node = typeof node === "undefined" ? document : node;
  300. let elm_list = [];
  301. let elm_box = document.evaluate(query, document);
  302. let elm = elm_box.iterateNext();
  303. if (!elm) {
  304. return;
  305. }
  306. while (elm) {
  307. elm_list.push(elm);
  308. elm = elm_box.iterateNext();
  309. }
  310. initElement(elm_list);
  311. return elm_list;
  312. }
  313.  
  314. function sleep(ms) {
  315. ms = typeof ms === "undefined" ? 1000 : ms;
  316. /**
  317. * 休眠函数,单位:ms
  318. * 使用方法:
  319. * sleep(500).then(() => { Do something after the sleep! });
  320. * 或者
  321. * await sleep(500); Do something after the sleep!
  322. */
  323. return new Promise((resolve) => setTimeout(resolve, ms));
  324. }
  325.  
  326. function isShowInView(element) {
  327. /**
  328. * 判断元素是否出现在视窗中
  329. */
  330. if (element === null || element === undefined) {
  331. return false;
  332. }
  333. let documentHeight = Math.max(
  334. document.documentElement.scrollHeight,
  335. document.documentElement.offsetHeight,
  336. document.documentElement.clientHeight
  337. );
  338. let screenHeight = window.innerHeight || documentHeight;
  339. let screenWidth = window.innerWidth || document.documentElement.clientWidth;
  340. let { top, right, bottom, left } = element.getBoundingClientRect();
  341. return (
  342. top >= 0 && left >= 0 && right <= screenWidth && bottom <= screenHeight
  343. );
  344. }
  345.  
  346. function isExistElement(css_selector) {
  347. /**
  348. * 判断指定元素是否存在页面中
  349. */
  350. if ($css(css_selector)) {
  351. return true;
  352. }
  353. return false;
  354. }
  355.  
  356. async function waitElement(css_selector, max_wait_ms) {
  357. /**
  358. * 等待指定元素出现
  359. */
  360. max_wait_ms = typeof max_wait_ms === "undefined" ? 3000 : max_wait_ms;
  361. let start_time = new Date().getTime();
  362. let elm = $css(css_selector);
  363. while (!elm && start_time + max_wait_ms >= new Date().getTime()) {
  364. await sleep(300);
  365. elm = $css(css_selector);
  366. if (elm) {
  367. break;
  368. }
  369. }
  370. return elm;
  371. }
  372.  
  373. function getScrollTop() {
  374. /**
  375. * 滚动条在Y轴上已经滚动的距离
  376. */
  377. return document.documentElement.scrollTop || document.body.scrollTop;
  378. }
  379.  
  380. function getScrollHeight() {
  381. /**
  382. * 整个页面的高度
  383. */
  384. return document.documentElement.scrollHeight || document.body.scrollHeight;
  385. }
  386.  
  387. function getWindowHeight() {
  388. /**
  389. * 浏览器可视窗口高度
  390. */
  391. return document.documentElement.clientHeight || document.body.clientHeight;
  392. }
  393.  
  394. function isPageBottom(deviation_value) {
  395. /**
  396. * 判断页面是否滚动到底,默认允许3个像素的误差,大部分情况下总是会差0.3-0.8像素
  397. */
  398. deviation_value =
  399. typeof deviation_value === "undefined" ? 3 : deviation_value;
  400. if (
  401. getScrollHeight() - getScrollTop() - getWindowHeight() <
  402. deviation_value
  403. ) {
  404. return true;
  405. }
  406. return false;
  407. }
  408.  
  409. function pressKeyByCode(code) {
  410. /**
  411. * 按键触发,根据传入的按键编号
  412. *
  413. * code: Number
  414. */
  415. return document.dispatchEvent(
  416. new KeyboardEvent("keydown", {
  417. bubbles: true,
  418. cancelable: true,
  419. keyCode: code,
  420. })
  421. );
  422. }
  423.  
  424. function pressKey(name) {
  425. /**
  426. * 按键触发,根据传入的按键名
  427. *
  428. * name: string
  429. */
  430. const KeyNameToCode = {
  431. back: 8,
  432. tab: 9,
  433. clear: 12,
  434. enter: 13,
  435. shift: 16,
  436. ctrl: 17,
  437. alt: 18,
  438. capelock: 20,
  439. esc: 27,
  440. space: 32,
  441. pageup: 33,
  442. pagedown: 34,
  443. end: 35,
  444. home: 36,
  445. left: 37,
  446. up: 38,
  447. right: 39,
  448. down: 40,
  449. insert: 45,
  450. delete: 46,
  451. 0: 48,
  452. 1: 49,
  453. 2: 50,
  454. 3: 51,
  455. 4: 52,
  456. 5: 53,
  457. 6: 54,
  458. 7: 55,
  459. 8: 56,
  460. 9: 57,
  461. a: 65,
  462. b: 66,
  463. c: 67,
  464. d: 68,
  465. e: 69,
  466. f: 70,
  467. g: 71,
  468. h: 72,
  469. i: 73,
  470. j: 74,
  471. k: 75,
  472. l: 76,
  473. m: 77,
  474. n: 78,
  475. o: 79,
  476. p: 80,
  477. q: 81,
  478. r: 82,
  479. s: 83,
  480. t: 84,
  481. u: 85,
  482. v: 86,
  483. w: 87,
  484. x: 88,
  485. y: 89,
  486. z: 90,
  487. f1: 112,
  488. f2: 113,
  489. f3: 114,
  490. f4: 115,
  491. f5: 116,
  492. f6: 117,
  493. f7: 118,
  494. f8: 119,
  495. f9: 120,
  496. f10: 121,
  497. f11: 122,
  498. f12: 123,
  499.  
  500. // 数字小键盘部分
  501. n0: 96,
  502. n1: 97,
  503. n2: 98,
  504. n3: 99,
  505. n4: 100,
  506. n5: 101,
  507. n6: 102,
  508. n7: 103,
  509. n8: 104,
  510. n9: 105,
  511. "n*": 106,
  512. "n+": 107,
  513. nenter: 108,
  514. "n-": 109,
  515. "n.": 110,
  516. "n/": 111,
  517.  
  518. numlock: 144,
  519. ";": 186,
  520. ":": 186,
  521. "=": 187,
  522. "+": 187,
  523. ",": 188,
  524. "<": 188,
  525. "-": 189,
  526. _: 189,
  527. ".": 190,
  528. ">": 190,
  529. "/": 191,
  530. "?": 191,
  531. "`": 192,
  532. "~": 192,
  533. "[": 219,
  534. "{": 219,
  535. "\\": 220,
  536. "|": 220,
  537. "]": 221,
  538. "}": 221,
  539. "'": 222,
  540. '"': 222,
  541. };
  542.  
  543. return pressKeyByCode(KeyNameToCode[name.toLowerCase()]);
  544. }