LINUX.DO CloudFlare Challenge Bypass

Automatically redirects to the challenge page when CloudFlare protection fails, improving browsing experience on linux.do

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                 LINUX.DO CloudFlare Challenge Bypass
// @name:zh-CN           LINUX.DO CloudFlare 5秒盾自动跳转
// @namespace            https://github.com/utags
// @homepageURL          https://github.com/utags/userscripts#readme
// @supportURL           https://github.com/utags/userscripts/issues
// @version              0.2.0
// @description          Automatically redirects to the challenge page when CloudFlare protection fails, improving browsing experience on linux.do
// @description:zh-CN    当 CloudFlare 5秒盾检测失败时,自动跳转到 challenge 页面,提升 linux.do 的浏览体验
// @author               Pipecraft
// @license              MIT
// @noframes
// @match                https://linux.do/*
// @icon                 https://www.google.com/s2/favicons?sz=64&domain=linux.do
// @grant                GM_registerMenuCommand
// ==/UserScript==

;(function () {
  'use strict'

  // 配置常量
  const CONFIG = {
    // 需要检测的错误文本
    ERROR_TEXTS: [
      '403 error',
      '该回应是很久以前创建的',
      'reaction was created too long ago',
      '我们无法加载该话题',
    ],
    // 要查找的元素选择器
    DIALOG_SELECTOR: '.dialog-body',
    // 重定向路径
    CHALLENGE_PATH: '/challenge',
    // 调试模式
    DEBUG: false,
    // 菜单文本
    MENU_TEXT: '手动触发 Challenge 跳转',
  }

  /**
   * 日志函数,仅在调试模式下输出
   * @param {...any} args - 要记录的参数
   */
  const log = (...args) => {
    if (CONFIG.DEBUG) {
      console.log('[LINUX.DO Auto Challenge]', ...args)
    }
  }

  /**
   * 检查当前页面是否是 challenge 页面
   * @returns {boolean} - 如果是 challenge 页面则返回 true,否则返回 false
   */
  function isChallengePage() {
    return window.location.pathname.startsWith(CONFIG.CHALLENGE_PATH)
  }

  /**
   * 检查当前页面是否是 CloudFlare challenge 失败页面
   * 只检查带有 dialog-body 类的元素
   * @returns {boolean} - 如果是失败页面则返回 true,否则返回 false
   */
  function isChallengeFailure() {
    // 如果已经在 challenge 页面,不要再次检测
    if (isChallengePage()) {
      return false
    }

    try {
      // 查找页面中的 dialog-body 元素
      const dialogElement = document.querySelector(CONFIG.DIALOG_SELECTOR)
      if (!dialogElement) return false

      // 检查 dialog-body 元素的内容是否包含错误文本
      const text = dialogElement.innerText || ''
      return CONFIG.ERROR_TEXTS.some((errorText) => text.includes(errorText))
    } catch (error) {
      log('检测失败页面时出错:', error)
      return false
    }
  }

  /**
   * 重定向到 challenge URL
   */
  function redirectToChallenge() {
    try {
      // 防止在 challenge 页面重复跳转
      if (isChallengePage()) return

      const redirectUrl = `${CONFIG.CHALLENGE_PATH}?redirect=${encodeURIComponent(window.location.href)}`
      log('重定向到:', redirectUrl)
      window.location.href = redirectUrl
    } catch (error) {
      log('重定向时出错:', error)
    }
  }

  /**
   * 检查并处理 CloudFlare 失败
   * @param {MutationObserver} [observer] - 可选的观察者实例,如果提供则在检测到失败时断开
   */
  function checkAndRedirect(observer) {
    if (isChallengeFailure()) {
      if (observer) observer.disconnect()
      redirectToChallenge()
      return true
    }
    return false
  }

  /**
   * 手动触发 Challenge 跳转
   * 直接跳转到 challenge 页面,或在已经在 challenge 页面时提示用户
   */
  function manualTrigger() {
    log('手动触发 Challenge 跳转')

    if (isChallengePage()) {
      alert('已在 Challenge 页面,无需跳转')
      return
    }

    redirectToChallenge()
  }

  /**
   * 初始化脚本
   */
  function initScript() {
    log('初始化脚本')

    // 如果已经在 challenge 页面,不需要执行脚本
    if (isChallengePage()) {
      log('已在 challenge 页面,不执行脚本')
      return
    }

    // 初始检查
    if (checkAndRedirect()) return

    // 观察 DOM 变化
    try {
      const observer = new MutationObserver((mutations, obs) => {
        checkAndRedirect(obs)
      })

      observer.observe(document.body, {
        childList: true, // 监听子节点变化
        subtree: true, // 监听所有后代节点
        characterData: true, // 监听文本内容变化
      })

      log('DOM 观察器已启动')
    } catch (error) {
      log('启动 DOM 观察器时出错:', error)
    }

    // 注册菜单命令
    try {
      GM_registerMenuCommand(CONFIG.MENU_TEXT, manualTrigger)
      log('菜单命令已注册')
    } catch (error) {
      log('注册菜单命令时出错:', error)
    }
  }

  // 确保 DOM 已加载后执行脚本
  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', initScript)
  } else {
    initScript()
  }
})()