dybz

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

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         dybz
// @description  增强第一版主小说网站的功能,详情请看脚本页面的介绍(另含去除广告的说明)
// @author       Essence
// @namespace    essence/dybz
// @version      0.16
// @icon         https://www.44yydstxt426.com/images/wap_logo.png
// @match        https://*/*
// @require      https://update.greasyfork.org/scripts/491997/1356799/baseStyles.js
// @require      https://update.greasyfork.org/scripts/491971/1356787/libs.js
// @run-at       document-end
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_addStyle
// @license      MIT
// ==/UserScript==

// 站点名。域名经常变动,没有更好的判断方法
// https://diyibanzhu.org/
const SITE_NAME = "歪歪蒂艾斯"

const TAG = "[第一版主]"

// 持久存储到篡改猴数据中的键
const V_DYBZ_HISTORY = "DYBZ_HISTORY"

// =========================== 函数 =========================== //

/**
 * 创建单手操作面板
 * @return {HTMLDivElement}
 */
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')
  containerToRow(oneHandPanel)

  // 向扩展面板添加上面的组件
  oneHandPanel.append(bnNextPage, bnTop)

  return oneHandPanel
}

/**
 * 创建设置面板
 * @return {HTMLDivElement}
 */
const createSettingsPanel = () => {
  // 备份
  const bnBackupHistory = document.createElement("button")
  bnBackupHistory.textContent = "备份 历史阅读记录"
  bnBackupHistory.classList.add("primary", "essenceX")
  bnBackupHistory.onclick = () => {
    console.log(TAG, "保存历史阅读记录到篡改猴")
    saveHistoryBooks()
    Toast.success("已备份 历史阅读记录")
  }

  // 恢复
  const bnRestoreHistory = document.createElement("button")
  bnRestoreHistory.textContent = "恢复 历史阅读记录"
  bnRestoreHistory.classList.add("primary", "essenceX")
  bnRestoreHistory.onclick = () => {
    const values = GM_getValue(V_DYBZ_HISTORY)
    console.log(TAG, "恢复的历史阅读记录:", values)
    restoreLocalStorageValues(values)
    Toast.success("已恢复 历史阅读记录")
  }

  const panel = document.createElement("div")
  containerToRow(panel)

  panel.append(bnBackupHistory, bnRestoreHistory)

  return panel
}

// =========================== 脚本页面 =========================== //

/**
 * 脚本页面:阅读页面,预加载下一页
 */
const prefetchNextPage = () => {
  // 依次找到"下一页"的元素:优先“下一页”,其次“下一章”
  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 root = document.querySelector("div#ChapterView div.bd div.page-content")
  if (!root) {
    console.log(TAG, "'div.page-content'根元素为空")
    return
  }

  const panel = document.createElement("div")
  containerToRow(panel, "0px")
  panel.style.marginTop = "50px"

  // 注意 `elem.cloneNode(true)`不能复制`事件`,所以要直接创建 2 个,左右各一个
  const oneHandPanel1 = createOneHandPanel()
  const oneHandPanel2 = createOneHandPanel()

  // 章节页面元素,存在的话放在中间
  const chapterPages = document.querySelector(".chapterPages")

  if (chapterPages) {
    // 修正原样式
    chapterPages.style.marginTop = "auto"
    chapterPages.style.lineHeight = "auto"

    panel.append(oneHandPanel1, chapterPages, oneHandPanel2)
  } else {
    panel.append(oneHandPanel1, oneHandPanel2)
  }

  root.append(panel)
}

/**
 * 脚本页面:准许复制文本
 */
const enableCopyText = () => {
  document.querySelector("body").oncontextmenu = null

  const chapterBody = document.querySelector("body.chapter")
  if (chapterBody) {
    chapterBody.style.webkitUserSelect = "auto"
  }
}

/**
 * 脚本页面:额外显著表示当前阅读的页
 */
const markCurrPage = () => {
  let curr = document.querySelector("a.curr")
  if (!curr) {
    console.log(TAG, "没有获取到阅读页的元素")
    return
  }

  // 当前页
  curr.style.setProperty("color", "rgba(0, 0, 255, 1)", "important")

  // 其它页
  GM_addStyle(`
  .chapterPages a {
    color: #666 !important;
  }
  `)
}

/**
 * 脚本页面: 添加设置按钮
 */
const addSettingsButton = () => {
  const settingsLink = document.createElement("a")
  settingsLink.textContent = "扩展设置"
  settingsLink.onclick = () => {
    const panel = createSettingsPanel()
    showDialog("扩展设置", panel)
  }
  settingsLink.style.cursor = "pointer"

  const nav = document.querySelector("div.nav")
  containerToRow(nav)
  nav.appendChild(settingsLink)
}

/**
 * 脚本页面: 保存历史阅读记录到篡改猴
 *
 * 注释:localstorage 中 bookList 保存书籍 ID列表(以"#"分隔),只保存这个是无效的,还要保存 ID 对应的阅读进度才是完整的历史记录。
 * 所以读取、保存整个 localstorage
 */
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)
}

/**
 * 增强作者文本为链接
 * @param authorNode 作者的 Node 实例
 */
const makeAuthorLink = (authorNode) => {
  const author = authorNode.textContent.trim().replace("作者:", "")

  const link = document.createElement("a")
  link.textContent = author
  link.href = `/author/${author}`
  link.classList.add("author")

  const parentElem = authorNode.parentElement
  authorNode.textContent = "作者:"
  parentElem.insertBefore(link, parentElem.childNodes[1])
}

/**
 * 脚本页面:自动填充网站人机验证
 */
const verifyPage = () => {
  document.querySelector("input#password").value = "1234"
  document.querySelector("div.login a").click()
}

/**
 * 脚本页面:自动填充 CF 人机验证
 */
const cfPage = () => {
  const click = (elem) => {
    console.log(TAG, "CF 人机验证的选择框", elem)
    elem.click()
  }

  // 点击`span.mark`而不是`input[type='checkbox']`更容易通过验证
  // 参考 https://stackoverflow.com/a/76575463
  waitElem("div#challenge-stage span.mark", 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) &&
    !(document.title === "您的阅读足迹" &&
      document.querySelector("h1.page-title").textContent === "您的阅读足迹")) {
    console.log(TAG, "不是目标小说网站,停止运行。", `网站标题:"${document.title}"`, `网站地址:${location.href}`)
    return
  }

  // 在导航栏添加“扩展设置”按钮
  if (document.querySelector("div.nav")) {
    console.log('在导航栏添加“扩展设置”按钮')
    addSettingsButton()
  }

  // 脚本页面:自动填充网站人机验证
  if (document.querySelector("div.title")?.textContent?.includes("为防止恶意访问")) {
    console.log(TAG, "自动填充网站人机验证")
    verifyPage()
  }

  // 脚本页面:阅读页面
  if (document.querySelector("div#ChapterView")) {
    console.log(TAG, "预加载下一页")
    prefetchNextPage()

    console.log(TAG, "单手操作面板")
    oneHand()

    console.log(TAG, "准许复制文本")
    enableCopyText()

    console.log(TAG, "额外显著标识当前阅读的页")
    markCurrPage()
  }

  // 脚本页面:增强作者文本为链接
  if (/作者:.+/.test(document.querySelector("p.info")?.firstChild?.textContent?.trim())) {
    console.log(TAG, "增强作者文本为链接")
    makeAuthorLink(document.querySelector("p.info").firstChild)
  }
})()