当 CloudFlare 5秒盾检测失败时,自动跳转到 challenge 页面,提升 linux.do 的浏览体验
// ==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()
}
})()