Via Css 检验

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

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

  1. // ==UserScript==
  2. // @name Via Css 检验
  3. // @namespace https://viayoo.com/
  4. // @version 2.9
  5. // @license MIT
  6. // @description 用于检验Via的Adblock规则中的Css隐藏规则是否有错误,支持自动运行、菜单操作、WebView版本检测及规则数量统计
  7. // @author Copilot & Grok
  8. // @run-at document-end
  9. // @match *://*/*
  10. // @grant GM_registerMenuCommand
  11. // @grant GM_setValue
  12. // @grant GM_getValue
  13. // @require https://cdn.jsdelivr.net/npm/js-beautify@1.14.0/js/lib/beautify-css.js
  14. // @require https://cdn.jsdelivr.net/npm/css-tree@2.3.1/dist/csstree.min.js
  15. // ==/UserScript==
  16.  
  17. (function() {
  18. 'use strict';
  19.  
  20. function getCssFileUrl() {
  21. const currentHost = window.location.hostname;
  22. return `https://${currentHost}/via_inject_blocker.css`;
  23. }
  24.  
  25. function formatCssWithJsBeautify(rawCss) {
  26. try {
  27. const formatted = css_beautify(rawCss, {
  28. indent_size: 2,
  29. selector_separator_newline: true
  30. });
  31. console.log('格式化后的CSS:', formatted);
  32. return formatted;
  33. } catch (error) {
  34. console.error(`CSS格式化失败:${error.message}`);
  35. return null;
  36. }
  37. }
  38.  
  39. function getWebViewVersion() {
  40. const ua = navigator.userAgent;
  41. console.log('User-Agent:', ua);
  42. const patterns = [
  43. /Chrome\/([\d.]+)/i,
  44. /wv\).*?Version\/([\d.]+)/i,
  45. /Android.*?Version\/([\d.]+)/i
  46. ];
  47.  
  48. for (let pattern of patterns) {
  49. const match = ua.match(pattern);
  50. if (match) {
  51. console.log('匹配到的版本:', match[1]);
  52. return match[1];
  53. }
  54. }
  55. return null;
  56. }
  57.  
  58. function checkPseudoClassSupport(cssContent) {
  59. const pseudoClasses = [{
  60. name: ':hover',
  61. minVersion: 37
  62. },
  63. {
  64. name: ':focus',
  65. minVersion: 37
  66. },
  67. {
  68. name: ':active',
  69. minVersion: 37
  70. },
  71. {
  72. name: ':nth-child',
  73. minVersion: 37
  74. },
  75. {
  76. name: ':not',
  77. minVersion: 37
  78. },
  79. {
  80. name: ':where',
  81. minVersion: 88
  82. },
  83. {
  84. name: ':is',
  85. minVersion: 88
  86. },
  87. {
  88. name: ':has',
  89. minVersion: 105
  90. }
  91. ];
  92. const webviewVersion = getWebViewVersion();
  93. let unsupportedPseudo = [];
  94.  
  95. if (!webviewVersion) {
  96. return "无法检测到WebView或浏览器内核版本";
  97. }
  98.  
  99. const versionNum = parseFloat(webviewVersion);
  100. console.log('检测到的WebView版本:', versionNum);
  101.  
  102. pseudoClasses.forEach(pseudo => {
  103. if (cssContent.includes(pseudo.name)) {
  104. if (versionNum < pseudo.minVersion) {
  105. unsupportedPseudo.push(`${pseudo.name} (需要版本 ${pseudo.minVersion}+)`);
  106. }
  107. }
  108. });
  109.  
  110. return unsupportedPseudo.length > 0 ?
  111. `当前版本(${webviewVersion})不支持以下伪类:${unsupportedPseudo.join(', ')}` :
  112. `当前版本(${webviewVersion})支持所有使用的伪类`;
  113. }
  114.  
  115. function countCssRules(formattedCss) {
  116. if (!formattedCss) return 0;
  117.  
  118. try {
  119. const ast = csstree.parse(formattedCss);
  120. let count = 0;
  121.  
  122. csstree.walk(ast, (node) => {
  123. if (node.type === 'Rule' && node.prelude && node.prelude.type === 'SelectorList') {
  124. const selectors = node.prelude.children.size;
  125. count += selectors;
  126. }
  127. });
  128. console.log('计算得到的规则总数:', count);
  129. return count;
  130. } catch (e) {
  131. console.error('CSS规则计数失败:', e);
  132. return 0;
  133. }
  134. }
  135.  
  136. function getCssPerformance(totalCssRules) {
  137. if (totalCssRules <= 5000) {
  138. return '✅CSS规则数量正常,可以流畅运行';
  139. } else if (totalCssRules <= 7000) {
  140. return '❓CSS规则数量较多,可能会导致设备运行缓慢';
  141. } else if (totalCssRules < 9999) {
  142. return '⚠️CSS规则数量接近上限,可能明显影响设备性能';
  143. } else {
  144. return '🆘CSS规则数量过多,不建议订阅此规则';
  145. }
  146. }
  147.  
  148. function truncateErrorLine(errorLine, maxLength = 150) {
  149. return errorLine.length > maxLength ? errorLine.substring(0, maxLength) + "..." : errorLine;
  150. }
  151.  
  152. async function fetchAndFormatCss() {
  153. const url = getCssFileUrl();
  154. console.log('尝试获取CSS文件:', url);
  155. try {
  156. const response = await fetch(url, {
  157. cache: 'no-store'
  158. });
  159. if (!response.ok) throw new Error(`HTTP状态: ${response.status}`);
  160. const text = await response.text();
  161. console.log('原始CSS内容:', text);
  162. return text;
  163. } catch (error) {
  164. console.error(`获取CSS失败:${error.message}`);
  165. return null;
  166. }
  167. }
  168.  
  169. function translateErrorMessage(englishMessage) {
  170. const translations = {
  171. "Identifier is expected": "需要标识符",
  172. "Unexpected end of input": "输入意外结束",
  173. "Selector is expected": "需要选择器",
  174. "Invalid character": "无效字符",
  175. "Unexpected token": "意外的标记",
  176. '"]" is expected': '需要 "]"',
  177. '"{" is expected': '需要 "{"',
  178. 'Unclosed block': '未闭合的块',
  179. 'Unclosed string': '未闭合的字符串',
  180. 'Property is expected': '需要属性名',
  181. 'Value is expected': '需要属性值',
  182. "Percent sign is expected": "需要百分号 (%)",
  183. 'Attribute selector (=, ~=, ^=, $=, *=, |=) is expected': '需要属性选择器运算符(=、~=、^=、$=、*=、|=)',
  184. 'Semicolon is expected': '需要分号 ";"',
  185. 'Number is expected': '需要数字',
  186. 'Colon is expected': '需要冒号 ":"'
  187. };
  188. return translations[englishMessage] || `${englishMessage}`;
  189. }
  190.  
  191. function validateCss(rawCss, formattedCss, isAutoRun = false) {
  192. if (!formattedCss) return;
  193.  
  194. let hasError = false;
  195. const errors = [];
  196. const lines = formattedCss.split('\n');
  197. const totalCssRules = countCssRules(formattedCss);
  198. const cssPerformance = getCssPerformance(totalCssRules);
  199. const pseudoSupport = checkPseudoClassSupport(rawCss);
  200.  
  201. try {
  202. csstree.parse(formattedCss, {
  203. onParseError(error) {
  204. hasError = true;
  205. const errorLine = lines[error.line - 1] || "无法提取错误行";
  206. const truncatedErrorLine = truncateErrorLine(errorLine);
  207. const translatedMessage = translateErrorMessage(error.message);
  208.  
  209. errors.push(`
  210. CSS 解析错误:
  211. - 位置:第 ${error.line}
  212. - 错误信息:${translatedMessage}
  213. - 错误片段:${truncatedErrorLine}
  214. `.trim());
  215. }
  216. });
  217.  
  218. const resultMessage = `
  219. CSS验证结果:
  220. - 规则总数:${totalCssRules}
  221. - 性能评价:${cssPerformance}
  222. - 伪类支持:${pseudoSupport}
  223. ${hasError ? '\n发现错误:\n' + errors.join('\n\n') : '\n未发现语法错误'}
  224. `.trim();
  225.  
  226. if (isAutoRun && hasError) {
  227. alert(resultMessage);
  228. } else if (!isAutoRun) {
  229. alert(resultMessage);
  230. }
  231. } catch (error) {
  232. const translatedMessage = translateErrorMessage(error.message);
  233. alert(`CSS验证失败:${translatedMessage}`);
  234. }
  235. }
  236.  
  237. async function autoRunCssValidation() {
  238. const rawCss = await fetchAndFormatCss();
  239. if (rawCss) {
  240. const formattedCss = formatCssWithJsBeautify(rawCss);
  241. if (formattedCss) {
  242. validateCss(rawCss, formattedCss, true);
  243. }
  244. }
  245. }
  246.  
  247. function initializeScript() {
  248. const isAutoRunEnabled = GM_getValue("autoRun", true);
  249.  
  250. GM_registerMenuCommand(isAutoRunEnabled ? "关闭自动运行" : "开启自动运行", () => {
  251. GM_setValue("autoRun", !isAutoRunEnabled);
  252. alert(`自动运行已${isAutoRunEnabled ? "关闭" : "开启"}!`);
  253. });
  254.  
  255. GM_registerMenuCommand("验证CSS文件", async () => {
  256. const rawCss = await fetchAndFormatCss();
  257. if (rawCss) {
  258. const formattedCss = formatCssWithJsBeautify(rawCss);
  259. if (formattedCss) {
  260. validateCss(rawCss, formattedCss, false);
  261. }
  262. }
  263. });
  264.  
  265. if (isAutoRunEnabled) {
  266. autoRunCssValidation();
  267. }
  268. }
  269.  
  270. initializeScript();
  271. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址