第一版主网站增强,已增强的功能:1. 自动填充 CF 人机验证;2. 自动填充网站人机验证;3. 预加载下一页;4. 增加单手操作面板;5. 保存历史阅读记录到篡改猴;
当前为
// ==UserScript==
// @name 第一版主网站增强
// @namespace https://diyibanzhu.org/better
// @version 0.6
// @description 第一版主网站增强,已增强的功能:1. 自动填充 CF 人机验证;2. 自动填充网站人机验证;3. 预加载下一页;4. 增加单手操作面板;5. 保存历史阅读记录到篡改猴;
// @author Essence
// @match https://*/*
// @run-at document-end
// @grant GM_setValue
// @grant GM_getValue
// @icon https://www.google.com/s2/favicons?sz=64&domain=diyibanzhu.org
// @license MIT
// ==/UserScript==
// 站点名。域名经常变动,没有更好的判断方法
const SITE_NAME = "歪歪蒂艾斯"
const TAG = "[第一版主]"
// 持久存储到篡改猴数据中的键
const V_DYBZ_HISTORY = "DYBZ_HISTORY"
// =========================== 公用函数 =========================== //
// 等待
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))
/**
* 等待指定元素出现后,执行回调
* @param selector 元素选择器。参考`document.querySelector`
* @param callback 需要执行的回调。传递的参数为目标元素
*/
const waitElem = (selector, callback) => {
const observer = new MutationObserver(() => {
const element = document.querySelector(selector)
if (element) {
callback(element)
observer.disconnect()
}
})
observer.observe(document.body, {
childList: true,
subtree: true,
})
}
// 创建单手操作面板
const createOneHandPanel = () => {
// 事件
const gotoNextPage = () => {
const nextLink = document.querySelector("a.curr")?.nextElementSibling || document.querySelector("a.next")
if (nextLink) {
nextLink.click()
}
}
const gotoTop = () => window.scrollTo({top: 0, behavior: "smooth"})
// 创建按钮
const bnNextPage = document.createElement('button')
bnNextPage.textContent = "下一页"
bnNextPage.addEventListener('click', gotoNextPage)
const bnTop = document.createElement('button')
bnTop.textContent = "顶部"
bnTop.addEventListener('click', gotoTop)
// 创建扩展面板
const oneHandPanel = document.createElement('div')
// 增强面板的样式
oneHandPanel.style.display = "flex"
oneHandPanel.style.flexDirection = "row"
oneHandPanel.style.justifyContent = "space-between"
oneHandPanel.style.gap = "8px"
// 向扩展面板添加上面的组件
oneHandPanel.append(bnNextPage, bnTop)
return oneHandPanel
}
/**
* 读取网站存储到 localStorage 的所有值
* @param excludeRegexp 不读取的键的正则。如 /^Hm_lvt/
* @return {string} JSON 文本
*/
const readLocalStorageValues = (excludeRegexp = undefined) => {
const result = {}
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i)
if (excludeRegexp && excludeRegexp.test(key)) {
continue
}
result[key] = localStorage.getItem(key)
}
return JSON.stringify(result)
}
/**
* 恢复数据到网站的 localStorage
* @param {string} json
*/
const restoreLocalStorageValues = (json) => {
const data = JSON.parse(json)
Object.keys(data).forEach(key => localStorage.setItem(key, data[key]))
}
// =========================== 脚本页面 =========================== //
// 脚本页面:阅读页面,预加载下一页
const nextPage = () => {
// 依次找到"下一页"的元素:优先“下一页”,其次“下一章”
const nextLink = document.querySelector("a.curr")?.nextElementSibling || document.querySelector("a.next")
console.log(TAG, "脚本将预加载下一页", nextLink, nextLink?.href)
// 存在链接
if (nextLink && nextLink.href) {
// 通过"link prefetching"实现预加载下一页
const link = document.createElement("link")
// 注意:最后第一版主网站的章中最后的一页的 URL 的 href 是以"javascript:"开头,其它页是正常的 URL
let href = nextLink.href
if (href.startsWith("javascript:")) {
const params = href.match(/\d+/g)
href = "/" + params.slice(0, -1).join("/") + "_" + params.slice(-1) + ".html"
}
link.href = href
link.rel = "prefetch"
document.head.appendChild(link)
}
}
// 脚本页面: 阅读页面,单手操作面板
const oneHand = () => {
// 操作章节页面元素的父元素,插入扩展面板和章节页面
const chapterPages = document.querySelector(".chapterPages")
if (!chapterPages) {
console.log(TAG, "chapterPages 元素为空")
return
}
// 修正原样式
chapterPages.style.marginTop = "auto"
chapterPages.style.lineHeight = "auto"
const root = chapterPages.parentElement
if (!root) {
console.log(TAG, "父元素为空")
return
}
// root 父元素的样式
root.style.display = "flex"
root.style.justifyContent = "space-between"
root.style.flexDirection = "row"
root.style.marginTop = "30px"
// 注意 `elem.cloneNode(true)`不能复制`事件`,所以要直接创建
const oneHandPanel1 = createOneHandPanel()
const oneHandPanel2 = createOneHandPanel()
root.append(oneHandPanel1, chapterPages, oneHandPanel2)
}
// 脚本页面: 保存历史阅读记录到篡改猴
const saveHistoryBooks = () => {
const history = readLocalStorageValues(/^Hm_lvt/)
if (history) {
// 保存到篡改猴
GM_setValue(V_DYBZ_HISTORY, history)
}
const values = GM_getValue(V_DYBZ_HISTORY)
console.log(TAG, "已保存的历史阅读记录:", values)
}
// 脚本页面:自动填充网站人机验证
const verifyPage = () => {
document.querySelector("input#password").value = "1234"
document.querySelector("div.login a").click()
}
// 脚本页面:自动填充 CF 人机验证
const cfPage = () => {
waitElem("div#challenge-stage input[type='checkbox']", (elem) => {
console.log(TAG, "CF 人机验证的选择框", elem)
elem.click()
})
}
(function () {
'use strict'
// Your code here...
// CF 验证页面需要放在最前面。避免因为不是目标网站而跳过
// 脚本页面:自动填充 CF 人机验证
// 注意 CF 验证是通过 iframe 嵌入实现的,所以在指定 URL 的 iframe 中运行该函数
if (location.href.startsWith("https://challenges.cloudflare.com/cdn-cgi/challenge-platform/")) {
console.log(TAG, "自动填充 CF 人机验证")
cfPage()
}
// 不是目标小说网站
if (!document.title.includes(SITE_NAME)) {
console.log(TAG, "不是目标小说网站,停止运行:", document.title, location.href)
return
}
// 脚本页面:预加载下一页
if (document.querySelector("h1.page-title")?.textContent?.trim()) {
console.log(TAG, "预加载下一页")
nextPage()
console.log(TAG, "单手操作面板")
oneHand()
console.log(TAG, "保存历史阅读记录到篡改猴")
saveHistoryBooks()
}
// 脚本页面:自动填充网站人机验证
if (document.querySelector("div.title")?.textContent?.includes("为防止恶意访问")) {
console.log(TAG, "自动填充网站人机验证")
verifyPage()
}
})()