dybz

增强第一版主小说网站的功能,详情请看脚本页面的介绍(另含去除广告的说明)

  1. // ==UserScript==
  2. // @name dybz
  3. // @description 增强第一版主小说网站的功能,详情请看脚本页面的介绍(另含去除广告的说明)
  4. // @author Essence
  5. // @namespace essence/dybz
  6. // @version 0.16
  7. // @icon https://www.44yydstxt426.com/images/wap_logo.png
  8. // @match https://*/*
  9. // @require https://update.greasyfork.org/scripts/491997/1356799/baseStyles.js
  10. // @require https://update.greasyfork.org/scripts/491971/1356787/libs.js
  11. // @run-at document-end
  12. // @grant GM_setValue
  13. // @grant GM_getValue
  14. // @grant GM_addStyle
  15. // @license MIT
  16. // ==/UserScript==
  17.  
  18. // 站点名。域名经常变动,没有更好的判断方法
  19. // https://diyibanzhu.org/
  20. const SITE_NAME = "歪歪蒂艾斯"
  21.  
  22. const TAG = "[第一版主]"
  23.  
  24. // 持久存储到篡改猴数据中的键
  25. const V_DYBZ_HISTORY = "DYBZ_HISTORY"
  26.  
  27. // =========================== 函数 =========================== //
  28.  
  29. /**
  30. * 创建单手操作面板
  31. * @return {HTMLDivElement}
  32. */
  33. const createOneHandPanel = () => {
  34. // 事件
  35. const gotoNextPage = () => {
  36. const nextLink = document.querySelector("a.curr")?.nextElementSibling ||
  37. document.querySelector("a.next")
  38.  
  39. if (nextLink) {
  40. nextLink.click()
  41. }
  42. }
  43. const gotoTop = () => window.scrollTo({top: 0, behavior: "smooth"})
  44.  
  45. // 创建按钮
  46. const bnNextPage = document.createElement('button')
  47. bnNextPage.textContent = "下页"
  48. bnNextPage.addEventListener('click', gotoNextPage)
  49.  
  50. const bnTop = document.createElement('button')
  51. bnTop.textContent = "顶部"
  52. bnTop.addEventListener('click', gotoTop)
  53.  
  54. // 创建扩展面板
  55. const oneHandPanel = document.createElement('div')
  56. containerToRow(oneHandPanel)
  57.  
  58. // 向扩展面板添加上面的组件
  59. oneHandPanel.append(bnNextPage, bnTop)
  60.  
  61. return oneHandPanel
  62. }
  63.  
  64. /**
  65. * 创建设置面板
  66. * @return {HTMLDivElement}
  67. */
  68. const createSettingsPanel = () => {
  69. // 备份
  70. const bnBackupHistory = document.createElement("button")
  71. bnBackupHistory.textContent = "备份 历史阅读记录"
  72. bnBackupHistory.classList.add("primary", "essenceX")
  73. bnBackupHistory.onclick = () => {
  74. console.log(TAG, "保存历史阅读记录到篡改猴")
  75. saveHistoryBooks()
  76. Toast.success("已备份 历史阅读记录")
  77. }
  78.  
  79. // 恢复
  80. const bnRestoreHistory = document.createElement("button")
  81. bnRestoreHistory.textContent = "恢复 历史阅读记录"
  82. bnRestoreHistory.classList.add("primary", "essenceX")
  83. bnRestoreHistory.onclick = () => {
  84. const values = GM_getValue(V_DYBZ_HISTORY)
  85. console.log(TAG, "恢复的历史阅读记录:", values)
  86. restoreLocalStorageValues(values)
  87. Toast.success("已恢复 历史阅读记录")
  88. }
  89.  
  90. const panel = document.createElement("div")
  91. containerToRow(panel)
  92.  
  93. panel.append(bnBackupHistory, bnRestoreHistory)
  94.  
  95. return panel
  96. }
  97.  
  98. // =========================== 脚本页面 =========================== //
  99.  
  100. /**
  101. * 脚本页面:阅读页面,预加载下一页
  102. */
  103. const prefetchNextPage = () => {
  104. // 依次找到"下一页"的元素:优先“下一页”,其次“下一章”
  105. const nextLink = document.querySelector("a.curr")?.nextElementSibling ||
  106. document.querySelector("a.next")
  107.  
  108. console.log(TAG, "脚本将预加载下一页", nextLink, nextLink?.href)
  109.  
  110. // 存在链接
  111. if (nextLink && nextLink.href) {
  112. // 通过"link prefetching"实现预加载下一页
  113. const link = document.createElement("link")
  114.  
  115. // 注意:最后第一版主网站的章中最后的一页的 URL 的 href 是以"javascript:"开头,其它页是正常的 URL
  116. let href = nextLink.href
  117. if (href.startsWith("javascript:")) {
  118. const params = href.match(/\d+/g)
  119. href = "/" + params.slice(0, -1).join("/") + "_" + params.slice(-1) + ".html"
  120. }
  121.  
  122. link.href = href
  123. link.rel = "prefetch"
  124. document.head.appendChild(link)
  125. }
  126. }
  127.  
  128. /**
  129. * 脚本页面: 阅读页面,单手操作面板
  130. */
  131. const oneHand = () => {
  132. const root = document.querySelector("div#ChapterView div.bd div.page-content")
  133. if (!root) {
  134. console.log(TAG, "'div.page-content'根元素为空")
  135. return
  136. }
  137.  
  138. const panel = document.createElement("div")
  139. containerToRow(panel, "0px")
  140. panel.style.marginTop = "50px"
  141.  
  142. // 注意 `elem.cloneNode(true)`不能复制`事件`,所以要直接创建 2 个,左右各一个
  143. const oneHandPanel1 = createOneHandPanel()
  144. const oneHandPanel2 = createOneHandPanel()
  145.  
  146. // 章节页面元素,存在的话放在中间
  147. const chapterPages = document.querySelector(".chapterPages")
  148.  
  149. if (chapterPages) {
  150. // 修正原样式
  151. chapterPages.style.marginTop = "auto"
  152. chapterPages.style.lineHeight = "auto"
  153.  
  154. panel.append(oneHandPanel1, chapterPages, oneHandPanel2)
  155. } else {
  156. panel.append(oneHandPanel1, oneHandPanel2)
  157. }
  158.  
  159. root.append(panel)
  160. }
  161.  
  162. /**
  163. * 脚本页面:准许复制文本
  164. */
  165. const enableCopyText = () => {
  166. document.querySelector("body").oncontextmenu = null
  167.  
  168. const chapterBody = document.querySelector("body.chapter")
  169. if (chapterBody) {
  170. chapterBody.style.webkitUserSelect = "auto"
  171. }
  172. }
  173.  
  174. /**
  175. * 脚本页面:额外显著表示当前阅读的页
  176. */
  177. const markCurrPage = () => {
  178. let curr = document.querySelector("a.curr")
  179. if (!curr) {
  180. console.log(TAG, "没有获取到阅读页的元素")
  181. return
  182. }
  183.  
  184. // 当前页
  185. curr.style.setProperty("color", "rgba(0, 0, 255, 1)", "important")
  186.  
  187. // 其它页
  188. GM_addStyle(`
  189. .chapterPages a {
  190. color: #666 !important;
  191. }
  192. `)
  193. }
  194.  
  195. /**
  196. * 脚本页面: 添加设置按钮
  197. */
  198. const addSettingsButton = () => {
  199. const settingsLink = document.createElement("a")
  200. settingsLink.textContent = "扩展设置"
  201. settingsLink.onclick = () => {
  202. const panel = createSettingsPanel()
  203. showDialog("扩展设置", panel)
  204. }
  205. settingsLink.style.cursor = "pointer"
  206.  
  207. const nav = document.querySelector("div.nav")
  208. containerToRow(nav)
  209. nav.appendChild(settingsLink)
  210. }
  211.  
  212. /**
  213. * 脚本页面: 保存历史阅读记录到篡改猴
  214. *
  215. * 注释:localstorage 中 bookList 保存书籍 ID列表(以"#"分隔),只保存这个是无效的,还要保存 ID 对应的阅读进度才是完整的历史记录。
  216. * 所以读取、保存整个 localstorage
  217. */
  218. const saveHistoryBooks = () => {
  219. const history = readLocalStorageValues(/^Hm_lvt/)
  220. if (history) {
  221. // 保存到篡改猴
  222. GM_setValue(V_DYBZ_HISTORY, history)
  223. }
  224.  
  225. const values = GM_getValue(V_DYBZ_HISTORY)
  226. console.log(TAG, "已保存的历史阅读记录:", values)
  227. }
  228.  
  229. /**
  230. * 增强作者文本为链接
  231. * @param authorNode 作者的 Node 实例
  232. */
  233. const makeAuthorLink = (authorNode) => {
  234. const author = authorNode.textContent.trim().replace("作者:", "")
  235.  
  236. const link = document.createElement("a")
  237. link.textContent = author
  238. link.href = `/author/${author}`
  239. link.classList.add("author")
  240.  
  241. const parentElem = authorNode.parentElement
  242. authorNode.textContent = "作者:"
  243. parentElem.insertBefore(link, parentElem.childNodes[1])
  244. }
  245.  
  246. /**
  247. * 脚本页面:自动填充网站人机验证
  248. */
  249. const verifyPage = () => {
  250. document.querySelector("input#password").value = "1234"
  251. document.querySelector("div.login a").click()
  252. }
  253.  
  254. /**
  255. * 脚本页面:自动填充 CF 人机验证
  256. */
  257. const cfPage = () => {
  258. const click = (elem) => {
  259. console.log(TAG, "CF 人机验证的选择框", elem)
  260. elem.click()
  261. }
  262.  
  263. // 点击`span.mark`而不是`input[type='checkbox']`更容易通过验证
  264. // 参考 https://stackoverflow.com/a/76575463
  265. waitElem("div#challenge-stage span.mark", click)
  266. }
  267.  
  268.  
  269. (function () {
  270. 'use strict'
  271.  
  272. // Your code here...
  273.  
  274. // CF 验证页面需要放在最前面。避免因为不是目标网站而跳过
  275.  
  276. // 脚本页面:自动填充 CF 人机验证
  277. // 注意 CF 验证是通过 iframe 嵌入实现的,所以在指定 URL 的 iframe 中运行该函数
  278. if (location.href.startsWith("https://challenges.cloudflare.com/cdn-cgi/challenge-platform/")) {
  279. console.log(TAG, "自动填充 CF 人机验证")
  280. cfPage()
  281. }
  282.  
  283. // 不是目标小说网站
  284. if (!document.title.includes(SITE_NAME) &&
  285. !(document.title === "您的阅读足迹" &&
  286. document.querySelector("h1.page-title").textContent === "您的阅读足迹")) {
  287. console.log(TAG, "不是目标小说网站,停止运行。", `网站标题:"${document.title}"`, `网站地址:${location.href}`)
  288. return
  289. }
  290.  
  291. // 在导航栏添加“扩展设置”按钮
  292. if (document.querySelector("div.nav")) {
  293. console.log('在导航栏添加“扩展设置”按钮')
  294. addSettingsButton()
  295. }
  296.  
  297. // 脚本页面:自动填充网站人机验证
  298. if (document.querySelector("div.title")?.textContent?.includes("为防止恶意访问")) {
  299. console.log(TAG, "自动填充网站人机验证")
  300. verifyPage()
  301. }
  302.  
  303. // 脚本页面:阅读页面
  304. if (document.querySelector("div#ChapterView")) {
  305. console.log(TAG, "预加载下一页")
  306. prefetchNextPage()
  307.  
  308. console.log(TAG, "单手操作面板")
  309. oneHand()
  310.  
  311. console.log(TAG, "准许复制文本")
  312. enableCopyText()
  313.  
  314. console.log(TAG, "额外显著标识当前阅读的页")
  315. markCurrPage()
  316. }
  317.  
  318. // 脚本页面:增强作者文本为链接
  319. if (/作者:.+/.test(document.querySelector("p.info")?.firstChild?.textContent?.trim())) {
  320. console.log(TAG, "增强作者文本为链接")
  321. makeAuthorLink(document.querySelector("p.info").firstChild)
  322. }
  323. })()