// ==UserScript==
// @name 笔趣阁外观优化
// @namespace https://gitee.com/linhq1999/OhMyScript
// @version 2.9
// @description 专注阅读
// @author LinHQ
// @match http*://www.shuquge.com/*.html
// @exclude http*://www.shuquge.com/*index.html
// @match http*://www.sywx8.com/*.html
// @match http*://www.biqugetv.com/*.html
// @match http*://www.bqxs520.com/*.html
// @grant GM_addStyle
// @grant GM_xmlhttpRequest
// @inject-into auto
// @license MIT
// ==/UserScript==
'use strict';
/** 配置示例
* "sites": [
* {
* "desc": "shuquge", 网站链接关键字
* "main": "div.reader", 主要部分选择器
* "title": ".reader h1", 标题选择器
* "txt": "#content", 文字部分选择器
* "toc": "dd a", 目录链接选择器
* "tocJump": 12, 跳过前面多少章
* "filter": ["div.header", "div.nav", "div.link"], 带有此选择器的元素将被删除
* "txtfilter": ["shuqu"] 带有此关键字的行将被删除
* }
* ]
*/
(() => {
// 缺省值,一般不用修改
const lineHeight = 1.3, defaultFont = "楷体";
let configs = {
"sites": [
{
"desc": "shuquge",
"main": "div.reader",
"title": ".reader h1",
"txt": "#content",
"toc": "dd a",
"tocJump": 12,
"filter": ["div.header", "div.nav", "div.link"],
"txtfilter": ["shuqu"] /*带有此关键字的行将被删除*/
},
{
"desc": "sywx",
"main": "div#container",
"title": "div>h1",
"toc": "li a",
"tocJump": 0,
"txt": "div#BookText",
"filter": ["div.top", ".link.xb", "#footer"],
"txtfilter": ["最快更新", "松语"]
},
{
"desc": "bqxs",
"main": ".box_con",
"title": "div.content_read h1",
"toc": "#list dd a",
"tocJump": 9,
"txt": "#content",
"filter": [".ywtop", ".header", ".nav", ".bottem1", ".lm", "#page_set", ".bookname~.box_con"],
"txtfilter": []
},
{
"desc": "biqugetv",
"main": ".box_con",
"title": "div.content_read h1",
"toc": "#list dd a",
"tocJump": 0,
"txt": "#content",
"filter": [".ywtop", ".header", ".nav", ".bottem1", ".lm", "#page_set"],
"txtfilter": []
}
],
"states": {
"fontSize": 16,
"lineHeight": 16 * lineHeight,
"toc": false
},
"style": `
body {
background-color: #EAEAEF !important;
}
.bqg.inject.win {
width: 55vw !important;
min-width: 600px;
border: 2px double gray !important;
border-radius: 8px;
}
.bqg.inject.txt {
font-family: ${defaultFont}!important;
background-color: #EAEAEF !important;
padding: 0.5em 1em !important;
margin: 0.5em auto !important;
width: auto !important;
}
.bqg.inject.title {
color: black;
background-color: #EAEAEF;
font-family: ${defaultFont}!important;
cursor: pointer !important;
}
.bqg.inject.title:hover {
color: #0258d8 !important;
}
.hq.inject.toc {
font-family: Arial,微软雅黑,文泉驿微米黑;
width: 275px;
position: fixed;
top: 30px;
padding: 5px;
display: flex;
flex-flow: column;
transition: left 0.5s cubic-bezier(0.35, 1.06, 0.83, 0.99);
background: rgb(246 246 246 / 60%);
border-radius: 8px;
}
.hq.inject ul {
max-height: 280px;
width: 100%;
/*offsetTop 计算需要*/
position:relative;
overflow: auto;
}
.hq.inject ul li {
cursor: pointer;
margin: 2px;
width: 95%;
padding: 1px 4px;
font-size: 12px;
border-radius: 4px;
}
.hq.inject ul li:hover {
background: #0258d8;
color: #f6f6f6;
}
.hq.inject.toc>h3 {
font-size: 1.1rem;
font-weight: bold;
border-radius: 2px;
align-self: center;
cursor: pointer;
margin: 4px 0 8px 0;
}
.hq.inject.toc>h3:hover {
color: #ffa631 !important;
}
`
};
// 查询已经保存的字体信息
let savedStates = localStorage.getItem("bqg_cfg");
// 检查是否存在已有设置且和当前版本相符
let states;
if (savedStates === null) {
states = configs.states;
console.warn("当前状态已保存");
}
else {
let cfg = JSON.parse(savedStates);
let defaultStates = Object.keys(configs.states);
let cfg_ = Object.keys(cfg);
let useSaved = true;
// 检查键是否匹配
if (defaultStates.length == cfg_.length) {
for (let key of Object.keys(cfg)) {
if (!defaultStates.includes(key)) {
useSaved = false;
break;
}
}
}
else {
useSaved = false;
}
if (useSaved) {
states = cfg;
}
else {
states = configs.states;
console.warn("检测到版本变化,状态已重置");
}
}
// 检测当前的网址,应用对应的设置
let tmp = configs.sites.filter(site => document.URL.includes(site.desc));
if (tmp.length == 0) {
console.warn("没有匹配的设置,脚本已终止!");
return;
}
let cfg = tmp[0];
// 完成样式注入
GM_addStyle(configs.style);
let saveStates = () => {
localStorage.setItem("bqg_cfg", JSON.stringify(states));
};
// 上一章
let prevChapter = () => {
var _a;
let prevs = document.querySelectorAll("a");
for (const prev of prevs) {
if ((_a = prev.textContent) === null || _a === void 0 ? void 0 : _a.includes("上一")) {
prev.click();
break;
}
}
};
// 下一章
let nextChapter = () => {
var _a;
let nexts = document.querySelectorAll("a");
for (const next of nexts) {
if ((_a = next.textContent) === null || _a === void 0 ? void 0 : _a.includes("下一")) {
next.click();
break;
}
}
};
// 目录开关
let toggleToc = () => {
let toc = document.querySelector(".hq.inject.toc");
if (parseInt(toc.style.left) < 0) {
toc.style.left = "8px";
states.toc = true;
}
else {
toc.style.left = "-300px";
states.toc = false;
}
// 每一次触发目录操作都保存一次状态
saveStates();
};
// 对可变部分产生影响
let doInject = function () {
var _a, _b;
// 执行元素过滤
cfg.filter.forEach(filter => { var _a; return (_a = document.querySelectorAll(filter)) === null || _a === void 0 ? void 0 : _a.forEach(ele => ele.remove()); });
// 应用已经保存的状态
let textWin = document.querySelector(cfg.txt);
textWin.setAttribute("style", `font-size:${states.fontSize}px;line-height:${states.lineHeight}px`);
textWin.classList.add("bqg", "inject", "txt");
// 执行文字过滤
if (cfg.txtfilter !== undefined) {
textWin.innerText = (_b = (_a = textWin.innerText) === null || _a === void 0 ? void 0 : _a.split("\n\n")) === null || _b === void 0 ? void 0 : _b.filter(line => {
for (const key of cfg.txtfilter) {
if (line.includes(key)) {
return false;
}
}
return true;
}).join("\n\n");
}
let mainWin = document.querySelector(cfg.main);
mainWin.classList.add("bqg", "inject", "win");
let title = document.querySelector(cfg.title);
title.title = "点击显示目录";
title.classList.add("bqg", "inject", "title");
title.onclick = (ev) => {
toggleToc();
// 避免跳到上一章
// 比下面的更为具体,所以有效。
ev.stopPropagation();
};
// 阻止双击事件被捕获(双击会回到顶部)
document.body.ondblclick = (ev) => ev.stopImmediatePropagation();
document.body.onclick = (ev) => {
let root = document.documentElement;
let winHeight = window.innerHeight;
// 下半屏单击下滚,反之上滚
if (ev.clientY > root.clientHeight / 2) {
if (root.scrollTop + winHeight >= root.scrollHeight) {
nextChapter();
}
window.scrollBy({ top: (window.innerHeight - lineHeight) * 1 });
}
else {
if (root.scrollTop === 0) {
prevChapter();
}
window.scrollBy({ top: (window.innerHeight - lineHeight) * -1 });
}
};
document.body.onkeydown = (ev) => {
switch (ev.key) {
case "-":
states.fontSize -= 2;
textWin.style.fontSize = `${states.fontSize}px`;
states.lineHeight = states.fontSize * lineHeight;
textWin.style.lineHeight = `${states.lineHeight}px`;
saveStates();
break;
case "=":
states.fontSize += 2;
textWin.style.fontSize = `${states.fontSize}px`;
states.lineHeight = states.fontSize * lineHeight;
textWin.style.lineHeight = `${states.lineHeight}px`;
saveStates();
break;
case "j":
window.scrollBy({ top: window.innerHeight - states.lineHeight });
break;
case "k":
window.scrollBy({ top: -1 * (window.innerHeight - states.lineHeight) });
break;
case "h":
prevChapter();
break;
case "l":
nextChapter();
break;
case "t":
toggleToc();
break;
default:
break;
}
};
};
// 先调用一次,后面是有变化时才会触发,避免有时无法起作用
doInject();
// 强力覆盖
new MutationObserver((_, ob) => {
doInject();
}).observe(document.body, { childList: true });
// 添加目录
let toc = document.createElement("div");
toc.className = "hq inject toc";
toc.onclick = ev => ev.stopPropagation();
// 已保存状态读取
toc.style.left = (states.toc) ? "8px" : "-300px";
document.body.append(toc);
// 目录状态指示灯
let pointer = document.createElement("h3");
let pointerColors = { "loaded": "#afdd22", "loading": "#ffa631", "unload": "#ed5736" };
pointer.title = "点击以重新加载目录";
pointer.innerHTML = "目<span style='display: inline-block;width: 1em'></span>录";
pointer.style.color = pointerColors.unload;
toc.append(pointer);
// 目录列表
let ul = document.createElement("ul");
toc.append(ul);
// fetchTOC 获取目录信息并重新渲染
let fetchTOC = function (currentBookLink, pointer) {
// 修改指示灯状态
pointer.style.color = pointerColors.loading;
GM_xmlhttpRequest({
url: currentBookLink,
// 直接返回 dom
responseType: "document",
onload: (resp) => {
var _a, _b;
let doc = resp.response;
let tocs = doc.querySelectorAll(cfg.toc);
let data = [];
// 序列化存储准备
for (let link of tocs) {
data.push({ "title": (_a = link.textContent) !== null && _a !== void 0 ? _a : "", "href": (_b = link.href) !== null && _b !== void 0 ? _b : "" });
}
if (cfg.tocJump)
data = data.slice(cfg.tocJump + 1);
// 缓存目录信息
sessionStorage.setItem(currentBookLink, JSON.stringify(data));
renderToc(data, ul);
pointer.style.color = pointerColors.loaded;
},
onerror: (_) => pointer.style.color = pointerColors.unload
});
};
let renderToc = function (toc, ul) {
// 清空旧内容
ul.innerHTML = "";
let current = null;
// 进度计数器
let counter = 1;
for (let lnk of toc) {
let li = document.createElement("li");
li.textContent = lnk.title;
if (current == null && document.URL == lnk.href) {
li.innerHTML = `${lnk.title}<span style="flex: 1;"></span>${(counter / toc.length * 100).toFixed(1)}%`;
current = li;
}
li.onclick = (ev) => {
document.location.href = lnk.href;
ev.stopPropagation();
};
ul.append(li);
counter++;
}
// 滚动到当前位置,并高亮
current === null || current === void 0 ? void 0 : current.setAttribute("style", "display:flex;font-weight:bold;background: #0258d8;color: #f6f6f6;");
ul.scrollTo({ top: (current === null || current === void 0 ? void 0 : current.offsetTop) - 130 });
};
let source = document.URL.split("/");
source.pop();
// 最后加斜杠保险
let currentBook = source.join("/") + "/";
let currentBookToc = sessionStorage.getItem(currentBook);
if (currentBookToc === null) {
fetchTOC(currentBook, pointer);
}
else {
pointer.style.color = pointerColors.loaded;
renderToc(JSON.parse(currentBookToc), ul);
}
// 单击指示灯刷新目录缓存
pointer.onclick = () => fetchTOC(currentBook, pointer);
})();