您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
用于检验Via的Adblock规则中的Css隐藏规则是否有错误,支持自动运行、菜单操作、WebView版本检测、规则数量统计及W3C CSS校验
// ==UserScript== // @name Via Css 检验 // @namespace https://viayoo.com/ // @version 3.3 // @license MIT // @description 用于检验Via的Adblock规则中的Css隐藏规则是否有错误,支持自动运行、菜单操作、WebView版本检测、规则数量统计及W3C CSS校验 // @author Copilot & Grok & nobody // @run-at document-end // @match *://*/* // @grant GM_registerMenuCommand // @grant GM_setValue // @grant GM_getValue // @grant GM.xmlHttpRequest // @connect jigsaw.w3.org // @require https://cdn.jsdelivr.net/npm/[email protected]/js/lib/beautify-css.js // @require https://cdn.jsdelivr.net/npm/[email protected]/dist/csstree.min.js // ==/UserScript== (function() { 'use strict'; const adblockPseudoClasses = [ ':contains', ':has-text', ':matches-css', ':matches-css-after', ':matches-css-before', ':matches-path', ':matches-property', ':min-text-length', ':nth-ancestor', ':remove', ':style', ':upward', ':watch-attr', ':xpath', ':-abp-contains', ':-abp-properties', ':if', ':if-not' ]; function getCssFileUrl() { const currentHost = window.location.hostname; return `http://${currentHost}/via_inject_blocker.css`; } function formatCssWithJsBeautify(rawCss) { try { const formatted = css_beautify(rawCss, { indent_size: 2, selector_separator_newline: true }); console.log('格式化后的CSS:', formatted); return formatted; } catch (error) { console.error(`CSS格式化失败:${error.message}`); return null; } } function getWebViewVersion() { const ua = navigator.userAgent; console.log('User-Agent:', ua); const patterns = [ /Chrome\/([\d.]+)/i, /wv\).*?Version\/([\d.]+)/i, /Android.*?Version\/([\d.]+)/i ]; for (let pattern of patterns) { const match = ua.match(pattern); if (match) { console.log('匹配到的版本:', match[1]); return match[1]; } } return null; } function checkPseudoClassSupport(cssContent) { const pseudoClasses = [ { name: ':hover', minVersion: 37 }, { name: ':focus', minVersion: 37 }, { name: ':active', minVersion: 37 }, { name: ':nth-child', minVersion: 37 }, { name: ':not', minVersion: 37 }, { name: ':where', minVersion: 88 }, { name: ':is', minVersion: 88 }, { name: ':has', minVersion: 105 } ]; const webviewVersion = getWebViewVersion(); let unsupportedPseudo = []; if (!webviewVersion) { return "无法检测到WebView或浏览器内核版本"; } const versionNum = parseFloat(webviewVersion); console.log('检测到的WebView版本:', versionNum); pseudoClasses.forEach(pseudo => { if (cssContent.includes(pseudo.name) && versionNum < pseudo.minVersion) { unsupportedPseudo.push(`${pseudo.name} (需要版本 ${pseudo.minVersion}+)`); } }); return unsupportedPseudo.length > 0 ? `当前版本(${webviewVersion})不支持以下伪类:${unsupportedPseudo.join(', ')}` : `当前版本(${webviewVersion})支持所有标准伪类`; } function splitCssAndAdblockRules(formattedCss) { const lines = formattedCss.split('\n'); const standardCss = []; const adblockRules = []; lines.forEach(line => { line = line.trim(); if (!line) return; if (line.startsWith('##') || adblockPseudoClasses.some(pseudo => line.includes(pseudo))) { adblockRules.push(line); } else { standardCss.push(line); } }); return { standardCss: standardCss.join('\n'), adblockRules }; } function countCssRules(formattedCss) { if (!formattedCss) return 0; try { const ast = csstree.parse(formattedCss); let count = 0; csstree.walk(ast, (node) => { if (node.type === 'Rule' && node.prelude && node.prelude.type === 'SelectorList') { const selectors = node.prelude.children.size; count += selectors; } }); console.log('计算得到的标准CSS规则总数:', count); return count; } catch (e) { console.error('标准CSS规则计数失败:', e); return 0; } } function getCssPerformance(totalCssRules) { if (totalCssRules <= 5000) { return '✅CSS规则数量正常,可以流畅运行'; } else if (totalCssRules <= 7000) { return '❓CSS规则数量较多,可能会导致设备运行缓慢'; } else if (totalCssRules < 9999) { return '⚠️CSS规则数量接近上限,可能明显影响设备性能'; } else { return '🆘CSS规则数量过多,建议调整订阅规则'; } } function truncateErrorLine(errorLine, maxLength = 150) { return errorLine.length > maxLength ? errorLine.substring(0, maxLength) + "..." : errorLine; } async function fetchAndFormatCss() { const url = getCssFileUrl(); console.log('尝试获取CSS文件:', url); try { const response = await fetch(url, { cache: 'no-store' }); if (!response.ok) throw new Error(`HTTP状态: ${response.status}`); const text = await response.text(); console.log('原始CSS内容:', text); return text; } catch (error) { console.error(`获取CSS失败:${error.message}`); return null; } } function translateErrorMessage(englishMessage) { const translations = { "Identifier is expected": "需要标识符", "Unexpected end of input": "输入意外结束", "Selector is expected": "需要选择器", "Invalid character": "无效字符", "Unexpected token": "意外的标记", '"]" is expected': '需要 "]"', '"{" is expected': '需要 "{"', 'Unclosed block': '未闭合的块', 'Unclosed string': '未闭合的字符串', 'Property is expected': "需要属性名", 'Value is expected': "需要属性值", "Percent sign is expected": "需要百分号 (%)", 'Attribute selector (=, ~=, ^=, $=, *=, |=) is expected': '需要属性选择器运算符(=、~=、^=、$=、*=、|=)', 'Semicolon is expected': '需要分号 ";"', 'Number is expected': '需要数字', 'Colon is expected': '需要冒号 ":"' }; return translations[englishMessage] || englishMessage; } async function validateCss(rawCss, formattedCss, isAutoRun = false) { if (!formattedCss) return; const { standardCss, adblockRules } = splitCssAndAdblockRules(formattedCss); console.log('标准CSS:', standardCss); console.log('Adguard/Ublock规则:', adblockRules); let hasError = false; const errors = []; const allLines = formattedCss.split('\n'); const totalStandardCssRules = countCssRules(standardCss); const cssPerformance = getCssPerformance(totalStandardCssRules); const pseudoSupport = checkPseudoClassSupport(standardCss); if (standardCss) { try { csstree.parse(standardCss, { onParseError(error) { hasError = true; const standardCssLines = standardCss.split('\n'); const errorLine = standardCssLines[error.line - 1] || "无法提取错误行"; const originalLineIndex = allLines.indexOf(errorLine); const truncatedErrorLine = truncateErrorLine(errorLine); const translatedMessage = translateErrorMessage(error.message); errors.push(` CSS解析错误: - 位置:第 ${originalLineIndex + 1} 行 - 错误信息:${translatedMessage} - 错误片段:${truncatedErrorLine} `.trim()); } }); } catch (error) { hasError = true; const translatedMessage = translateErrorMessage(error.message); errors.push(`标准CSS解析失败:${translatedMessage}`); } } adblockRules.forEach((rule, index) => { const originalLineIndex = allLines.indexOf(rule); let errorMessage = null; const matchedPseudo = adblockPseudoClasses.find(pseudo => rule.includes(pseudo)); if (matchedPseudo) { errorMessage = `非标准伪类 ${matchedPseudo}(AdGuard/uBlock 扩展语法,不支持)`; } else if (rule.startsWith('##') && !rule.match(/^##[\w\s\[\]\.,:()]+$/)) { errorMessage = '无效的 Adblock 元素隐藏规则'; } if (errorMessage) { hasError = true; const truncatedRule = truncateErrorLine(rule); errors.push(` CSS解析错误: - 位置:第 ${originalLineIndex + 1} 行 - 错误信息:${errorMessage} - 错误片段:${truncatedRule} `.trim()); } }); const resultMessage = ` CSS验证结果: - 规则总数:${totalStandardCssRules} (标准CSS) + ${adblockRules.length} (Adguard/Ublock拓展规则) - 性能评价:${cssPerformance} - 伪类支持:${pseudoSupport} ${errors.length > 0 ? '\n发现错误:\n' + errors.join('\n\n') : '\n未发现语法错误'} `.trim(); if (isAutoRun && errors.length > 0) { alert(resultMessage); } else if (!isAutoRun) { alert(resultMessage); } } async function validateCssWithW3C(cssText) { const validatorUrl = "https://jigsaw.w3.org/css-validator/validator"; try { return new Promise((resolve, reject) => { GM.xmlHttpRequest({ method: "POST", url: validatorUrl, data: `text=${encodeURIComponent(cssText)}&profile=css3&output=json`, headers: { "Content-Type": "application/x-www-form-urlencoded", "Accept": "application/json" }, onload: function(response) { try { const result = JSON.parse(response.responseText); console.log("W3C Validator返回的JSON:", result); if (result && result.cssvalidation) { const errors = result.cssvalidation.errors || []; const warnings = result.cssvalidation.warnings || []; if (errors.length > 0) { const errorDetails = errors.map(err => { const line = err.line || "未知行号"; const message = err.message || "未知错误"; const context = err.context || "无上下文"; return `行 ${line}: ${message} (上下文: ${context})`; }).join("\n\n"); alert(`W3C校验发现 ${errors.length} 个CSS错误:\n\n${errorDetails}`); } else if (warnings.length > 0) { const warningDetails = warnings.map(warn => { const line = warn.line || "未知行号"; const message = warn.message || "未知警告"; return `行 ${line}: ${message}`; }).join("\n\n"); alert(`W3C校验未发现错误,但有 ${warnings.length} 个警告:\n\n${warningDetails}`); } else { alert("W3C CSS校验通过,未发现错误或警告!"); } } else { alert("W3C校验服务返回无效结果,请查看控制台!"); } resolve(); } catch (e) { console.error("W3C校验解析失败:", e); alert("W3C校验解析失败,请检查控制台日志!"); reject(e); } }, onerror: function(error) { console.error("W3C校验请求失败:", error); alert(`W3C校验请求失败:${error.statusText || '未知错误'} (状态码: ${error.status || '未知'})`); reject(error); } }); }); } catch (e) { console.error("W3C校验请求失败:", e); alert(`W3C校验请求失败:${e.message},请检查控制台日志!`); } } async function autoRunCssValidation() { const rawCss = await fetchAndFormatCss(); if (rawCss) { const formattedCss = formatCssWithJsBeautify(rawCss); if (formattedCss) { validateCss(rawCss, formattedCss, true); } } } async function checkCssFileWithW3C() { const cssFileUrl = getCssFileUrl(); try { const response = await fetch(cssFileUrl, { method: 'GET', cache: 'no-store' }); if (!response.ok) { alert(`无法加载CSS文件: ${cssFileUrl} (状态码: ${response.status})`); return; } const cssText = await response.text(); if (!cssText.trim()) { alert("CSS文件为空!"); return; } console.log("要校验的CSS内容:", cssText); await validateCssWithW3C(cssText); } catch (err) { console.error("获取CSS文件失败:", err); alert(`获取CSS文件失败:${err.message},请检查控制台日志!`); } } function initializeScript() { const isAutoRunEnabled = GM_getValue("autoRun", true); GM_registerMenuCommand(isAutoRunEnabled ? "关闭自动运行" : "开启自动运行", () => { GM_setValue("autoRun", !isAutoRunEnabled); alert(`自动运行已${isAutoRunEnabled ? "关闭" : "开启"}!`); }); GM_registerMenuCommand("验证CSS文件(本地)", async () => { const rawCss = await fetchAndFormatCss(); if (rawCss) { const formattedCss = formatCssWithJsBeautify(rawCss); if (formattedCss) { validateCss(rawCss, formattedCss, false); } } }); GM_registerMenuCommand("验证CSS文件(W3C)", () => { checkCssFileWithW3C(); }); if (isAutoRunEnabled) { autoRunCssValidation(); } } initializeScript(); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址