// ==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
// @downloadURL none
// ==/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 || ''}
${title}
`
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: `
`,
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('', 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 result = data.reader.chapterData;
const contentHtmlStartIndex = html.indexOf('