返回首頁 

Greasy Fork is available in English.

番茄小说阅读助手

自动滚动页面 + 快捷键翻页

  1. // ==UserScript==// @name 番茄小说阅读助手// @namespace http://tampermonkey.net/// @version 0.1// @description 自动滚动页面 + 快捷键翻页// @author return null;// @match https://fanqienovel.com/reader/*// @icon https://www.google.com/s2/favicons?sz=64&domain=fanqienovel.com// @grant none// @license MIT// ==/UserScript==(function () {'use strict';const utils = {/*** toast,默认 1.5s 后关闭,若传入 duration,则按照 duration 的值关闭,若 duration 为 0,则不关闭,返回一个对象,其中有一个 close 方法,可以手动关闭* @param {*} msg* @param {*} duration*/toast: (msg, duration = 1500) => {// 优先把之前的 toast 关闭const lastToast = document.querySelector('.fanqie-zhushou-toast');if (lastToast) {lastToast.remove();}const toast = document.createElement('div');toast.className = 'fanqie-zhushou-toast';toast.innerHTML = msg;document.body.appendChild(toast);if (duration) {setTimeout(() => {toast.remove();}, duration);}return {close: () => {toast.remove();}}},initConfig: () => {const defaultConfig = {version: '20230730002',/*** 阅读器宽度,支持百分比和 px*/width: '80%',/*** 快捷键*/hotKeys: {/*** 上一章快捷键*/lastChapter: 'ArrowLeft',/*** 下一章快捷键*/nextChapter: 'ArrowRight',/*** 关闭自动滚动快捷键*/closeAutoScroll: 'Escape'},/*** 自动滚动速度,单位毫秒*/autoScrollSpeed: 50}// 优先从 localStorage 中获取配置,没有就用默认配置,判断版本号是否一致,不一致就用默认配置const config = JSON.parse(localStorage.getItem('fanqie-zhushou-config')) || defaultConfig;if (config.version !== defaultConfig.version) {localStorage.setItem('fanqie-zhushou-config', JSON.stringify(defaultConfig));return defaultConfig;}localStorage.setItem('fanqie-zhushou-config', JSON.stringify(config));return config;},refreshConfig: (config) => {localStorage.setItem('fanqie-zhushou-config', JSON.stringify(config));},addToolbarBtn: ({title,svg,onclick}) => {const toolbar = document.querySelector('#app .reader-toolbar > div')const autoScrollBtn = document.createElement('div');autoScrollBtn.className = 'reader-toolbar-item';autoScrollBtn.title = title;autoScrollBtn.innerHTML = `${svg || ''}<div>${title}</div>`if (onclick) {autoScrollBtn.onclick = onclick}toolbar.appendChild(autoScrollBtn);}}// 优先从 localStorage 中获取配置,没有就用默认配置const config = utils.initConfig()const titleNavWidth = '300px'const style = document.createElement('style');const pageWidthStyle = `#app div.muye-reader div.muye-reader-inner {width: calc(${config.width} - ${titleNavWidth});max-width: calc(${config.width} - ${titleNavWidth});}.muye-reader-nav {width: calc(${config.width} - 15px - ${titleNavWidth});max-width: calc(${config.width} - 15px - ${titleNavWidth});}`;style.innerHTML = `${config.width ? pageWidthStyle : ''}.reader-toolbar {left: 85%;}.reader-toolbar > div > div {cursor: pointer;}.reader-toolbar-item.reader-toolbar-item-download {display: none;}.fanqie-zhushou-toast {position: fixed;top: 35px;left: 50%;transform: translate(-50%, -50%);padding: 10px 20px;background-color: rgba(0, 0, 0, 0.5);color: #fff;border-radius: 5px;z-index: 999;}.line-space {height: 52px;width: 100%;}.auto-header-title {position: absolute;top: 50%;transform: translate(0, -50%);font-size: 16px;width: 295px;left: 5px;font-weight: bold;}.auto-header-title h1 {font-size: unset;font-weight: unset;margin: unset;padding-bottom: 5px;}`;document.head.appendChild(style);const lastChapter = () => {const btn = document.querySelector('#app .chapter-btn.last');if (btn) {btn.click();}}const nextChapter = () => {const btn = document.querySelector('#app .chapter-btn.next');if (btn) {btn.click();}}const autoScroll = () => {const autoScrollBtn = document.querySelector('#app .reader-toolbar-item[title="滚动"]');if (autoScrollBtn) {autoScrollBtn.setAttribute('status', 'on');clearInterval(window.autoScrollTimer);window.autoScrollTimer = setInterval(() => {const reader = document.querySelector('#app .muye-reader');reader.scrollBy(0, 1)// 根据页面高度计算进度,保留两位小数const progress = (reader.scrollTop / (reader.scrollHeight - reader.offsetHeight) * 100).toFixed(1);utils.toast(`已开启自动滚动,按 Esc 可退出,当前进度:${progress}%,当前速度:${config.autoScrollSpeed}`, 0);}, config.autoScrollSpeed);}}// 监听键盘方向键document.addEventListener('keydown', (e) => {console.log('keydown', e);if (e.key === config.hotKeys.lastChapter) {lastChapter();} else if (e.key === config.hotKeys.nextChapter) {nextChapter();}const autoScrollBtn = document.querySelector('#app .reader-toolbar-item[title="滚动"]');if (autoScrollBtn) {// esc 主动关闭自动滚动if (e.key === config.hotKeys.closeAutoScroll) {const autoScrollBtn = document.querySelector('#app .reader-toolbar-item[title="滚动"]');autoScrollBtn.setAttribute('status', 'off');clearInterval(window.autoScrollTimer);utils.toast('已关闭自动滚动');}// 如果当前在自动滚动时,按下 + 或者 - 可以调整滚动速度if (e.key === '+' || e.key === '=') {const status = autoScrollBtn.getAttribute('status');if (status === 'on') {config.autoScrollSpeed -= 5;autoScroll()utils.refreshConfig(config);}} else if (e.key === '-') {const status = autoScrollBtn.getAttribute('status');if (status === 'on') {config.autoScrollSpeed += 5;autoScroll()utils.refreshConfig(config);}}}});utils.addToolbarBtn({title: '滚动',svg: `<svg t="1690709388609" class="muyeicon-icon muyeicon-icon-scan reader-toolbar-item-icon" viewBox="0 0 #### ####" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2017" width="200" height="200"><path d="M256.2 736.4h320c88.2 0 160-71.8 160-160v-320c0-88.2-71.8-160-160-160h-320c-88.2 0-160 71.8-160 160v320c0 88.2 71.8 160 160 160z m-96-480c0-52.9 43.1-96 96-96h320c52.9 0 96 43.1 96 96v320c0 52.9-43.1 96-96 96h-320c-52.9 0-96-43.1-96-96v-320zM768.2 815.6H521.5c-12.3-28.3-40.5-48-73.3-48s-61 19.8-73.3 48H128.2c-17.7 0-32 14.3-32 32s14.3 32 32 32h246.7c12.3 28.2 40.5 48 73.3 48s61-19.7 73.3-48h246.7c17.7 0 32-14.3 32-32s-14.3-32-32-32zM879.8 375.1V128.4c0-17.7-14.3-32-32-32s-32 14.3-32 32v246.7c-28.2 12.3-48 40.5-48 73.3s19.7 61 48 73.3v246.7c0 17.7 14.3 32 32 32s32-14.3 32-32V521.7c28.3-12.3 48-40.5 48-73.3s-19.8-61-48-73.3z" p-id="2018"></path></svg>`,onclick: (event) => {const autoScrollBtn = document.querySelector('#app .reader-toolbar-item[title="滚动"]');const status = autoScrollBtn.getAttribute('status');if (status === 'on') {autoScrollBtn.setAttribute('status', 'off');clearInterval(window.autoScrollTimer);utils.toast('已关闭自动滚动');} else {autoScroll()}}})const analysisChapterData = (html) => {if (!html || html.indexOf('window.__INITIAL_STATE__=') === -1) {return null;}const startIndex = html.indexOf('window.__INITIAL_STATE__=')const endIndex = html.indexOf('</script>', startIndex)let jsonStr = html.substring(startIndex + 25, endIndex - 1)// 找到结尾的 ;const lastSemicolonIndex = jsonStr.lastIndexOf(';')jsonStr = jsonStr.substring(0, lastSemicolonIndex)const data = JSON.parse(jsonStr)console.log('analysisChapterData', data)const r###lt = data.reader.chapterData;const contentHtmlStartIndex = html.indexOf('<div class="muye-reader-box-header">');const contentHtmlEndIndex = html.indexOf('<div class="muye-reader-btns">', contentHtmlStartIndex);const $content_html = html.substring(contentHtmlStartIndex, contentHtmlEndIndex);r###lt.$content_html = $content_htmlreturn r###lt};window.$fna = {next_item_id: null,item_loading: false,item_content_caches: {}}window.onload = () => {window.$fna.next_item_id = window.__INITIAL_STATE__.reader.chapterData.nextItemId;window.$fna.item_loading = falseconsole.log('window.$fna.next_item_id', window.$fna.next_item_id)}const preloadNextChapter = async ({ itemId, skipCache }) => {if (window.$fna.item_content_caches[itemId] && skipCache !== true) {return window.$fna.item_content_caches[itemId]}const response = await fetch(`https://fanqienovel.com/reader/${itemId}?enter_from=reader`, {"method": "GET",});const html = await response.text()const chapterData = analysisChapterData(html);window.$fna.item_loading = falsewindow.$fna.item_content_caches[itemId] = chapterDatareturn chapterData}const loadNextChapter = async ({ itemId, skipCache }) => {if (window.$fna.item_loading === true) {return}if (window.$fna.item_content_caches[itemId] && skipCache !== true) {return window.$fna.item_content_caches[itemId]}window.$fna.item_loading = trueconst chapterData = await preloadNextChapter({ itemId, skipCache })preloadNextChapter({ itemId: chapterData.nextItemId, skipCache: true })window.$fna.item_loading = falsereturn chapterData}const reader = document.querySelector('#app .muye-reader');reader.addEventListener('scroll', (event) => {console.log('scroll', event);document.querySelector('.muye-reader-btns').style.display = 'none';const scrollTop = event.target.scrollTop;const scrollHeight = event.target.scrollHeight;const offsetHeight = event.target.offsetHeight;const progress = (scrollTop / (scrollHeight - offsetHeight) * 100).toFixed(4);console.log('progress', progress)if (progress >= 90) {loadNextChapter({ itemId: window.$fna.next_item_id }).then(({ $content_html, nextItemId }) => {const readerBoxElement = document.querySelector('#app .muye-reader .muye-reader-inner .muye-reader-box');readerBoxElement.innerHTML = readerBoxElement.innerHTML + '<div class="line-space"></div>' + $content_html;window.$fna.next_item_id = nextItemId;const titles = document.querySelectorAll('h1.muye-reader-title')// 获取所有标题,赋值到要给字符串数组中const titleArr = []titles.forEach((title) => {titleArr.push(`<h1>${title.innerText}</h1>`)})// 在 #app div 中插入一个 auto-header-title 的 div,用于存放标题// 如果存在就先删掉let autoHeaderTitleElement = document.querySelector('#app div.auto-header-title')autoHeaderTitleElement && autoHeaderTitleElement.remove()autoHeaderTitleElement = document.createElement('div')autoHeaderTitleElement.className = 'auto-header-title'autoHeaderTitleElement.innerHTML = titleArr.join('')document.querySelector('#app div').appendChild(autoHeaderTitleElement)document.querySelectorAll('#app div.auto-header-title h1').forEach((item, index) => {item.onclick = () => {console.log('click', index)// 滚动到对应的标题,稍微往上偏移一点,平滑滚动const titles = document.querySelectorAll('h1.muye-reader-title')titles[index].scrollIntoView({behavior: 'smooth'})}})})}})})();