Via Css 检验

用于检验Via的Adblock规则中的Css隐藏规则是否有错误,支持自动运行、菜单操作、WebView版本检测及规则数量统计

当前为 2025-03-24 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Via Css 检验
// @namespace    https://viayoo.com/
// @version      2.9
// @license      MIT
// @description  用于检验Via的Adblock规则中的Css隐藏规则是否有错误,支持自动运行、菜单操作、WebView版本检测及规则数量统计
// @author       Copilot & Grok
// @run-at       document-end
// @match        *://*/*
// @grant        GM_registerMenuCommand
// @grant        GM_setValue
// @grant        GM_getValue
// @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';

    function getCssFileUrl() {
        const currentHost = window.location.hostname;
        return `https://${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)) {
                if (versionNum < pseudo.minVersion) {
                    unsupportedPseudo.push(`${pseudo.name} (需要版本 ${pseudo.minVersion}+)`);
                }
            }
        });

        return unsupportedPseudo.length > 0 ?
            `当前版本(${webviewVersion})不支持以下伪类:${unsupportedPseudo.join(', ')}` :
            `当前版本(${webviewVersion})支持所有使用的伪类`;
    }

    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('计算得到的规则总数:', 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}`;
    }

    function validateCss(rawCss, formattedCss, isAutoRun = false) {
        if (!formattedCss) return;

        let hasError = false;
        const errors = [];
        const lines = formattedCss.split('\n');
        const totalCssRules = countCssRules(formattedCss);
        const cssPerformance = getCssPerformance(totalCssRules);
        const pseudoSupport = checkPseudoClassSupport(rawCss);

        try {
            csstree.parse(formattedCss, {
                onParseError(error) {
                    hasError = true;
                    const errorLine = lines[error.line - 1] || "无法提取错误行";
                    const truncatedErrorLine = truncateErrorLine(errorLine);
                    const translatedMessage = translateErrorMessage(error.message);

                    errors.push(`
CSS 解析错误:
- 位置:第 ${error.line} 行
- 错误信息:${translatedMessage}
- 错误片段:${truncatedErrorLine}
                    `.trim());
                }
            });

            const resultMessage = `
CSS验证结果:
- 规则总数:${totalCssRules}
- 性能评价:${cssPerformance}
- 伪类支持:${pseudoSupport}
${hasError ? '\n发现错误:\n' + errors.join('\n\n') : '\n未发现语法错误'}
            `.trim();

            if (isAutoRun && hasError) {
                alert(resultMessage);
            } else if (!isAutoRun) {
                alert(resultMessage);
            }
        } catch (error) {
            const translatedMessage = translateErrorMessage(error.message);
            alert(`CSS验证失败:${translatedMessage}`);
        }
    }

    async function autoRunCssValidation() {
        const rawCss = await fetchAndFormatCss();
        if (rawCss) {
            const formattedCss = formatCssWithJsBeautify(rawCss);
            if (formattedCss) {
                validateCss(rawCss, formattedCss, true);
            }
        }
    }

    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);
                }
            }
        });

        if (isAutoRunEnabled) {
            autoRunCssValidation();
        }
    }

    initializeScript();
})();