第一版主网站增强

第一版主网站增强,已增强的功能:1. 自动填充 CF 人机验证;2. 自动填充网站人机验证;3. 预加载下一页;4. 增加单手操作面板;5. 保存历史阅读记录到油猴;

当前为 2024-04-07 提交的版本,查看 最新版本

// ==UserScript==
// @name         第一版主网站增强
// @namespace    https://diyibanzhu.org/better
// @version      0.5
// @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_BOOKLIST = "DYBZ_BOOKLIST"

// =========================== 公用函数 =========================== //

// 等待
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))

// 等待指定元素出现后,执行回调
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
}

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

// 脚本页面:阅读页面,预加载下一页
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 bookList = localStorage.getItem("bookList")
  if (bookList) {
    // 保存到油猴
    GM_setValue(V_DYBZ_BOOKLIST, bookList)
  }

  const bookList2 = GM_getValue(V_DYBZ_BOOKLIST)
  console.log(TAG, "已保存的历史阅读记录:", bookList2)
}

// 脚本页面:自动填充网站人机验证
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()
  }
})()