Via Adblock 规则分析

解析Adblock规则,是否值得在Via浏览器上订阅,评分仅供娱乐,自行斟酌。

  1. // ==UserScript==
  2. // @name Via Adblock 规则分析
  3. // @namespace https://viayoo.com/
  4. // @version 1.19
  5. // @description 解析Adblock规则,是否值得在Via浏览器上订阅,评分仅供娱乐,自行斟酌。
  6. // @author Grok & Via
  7. // @match *://*/*
  8. // @license MIT
  9. // @grant GM_registerMenuCommand
  10. // @grant GM_setValue
  11. // @grant GM_getValue
  12. // @run-at document-start
  13. // ==/UserScript==
  14.  
  15. (function() {
  16. 'use strict';
  17. console.log('Adblock Rule Analyzer 脚本已加载,URL:', location.href);
  18.  
  19. // 使用 GM_getValue 存储自动识别开关,默认关闭
  20. let autoDetectRawText = GM_getValue('autoDetectRawText', false);
  21.  
  22. // 注册(不可用)菜单项
  23. GM_registerMenuCommand("分析当前页面规则", analyzeCurrentPage);
  24. GM_registerMenuCommand("分析自定义链接规则", analyzeCustomLink);
  25. GM_registerMenuCommand(`自动识别纯文本链接解析 (${autoDetectRawText ? '开启' : '关闭'})`, toggleAutoDetect);
  26.  
  27. // 简洁的 toast 调用函数
  28. const toast = msg => window.via?.toast?.(msg);
  29.  
  30. // 检查是否是纯文本页面并直接处理
  31. function handleRawTextPage() {
  32. if (!autoDetectRawText) return false;
  33. const url = location.href;
  34. if (url.match(/\.(txt|list|rules|prop)$/i) || url.includes('raw.githubusercontent.com')) {
  35. console.log('检测到纯文本页面:', url);
  36. toast('正在分析Adblock规则中……')
  37. fetchContent(url);
  38. return true;
  39. }
  40. return false;
  41. }
  42.  
  43. // 切换自动识别开关
  44. function toggleAutoDetect() {
  45. autoDetectRawText = !autoDetectRawText;
  46. GM_setValue('autoDetectRawText', autoDetectRawText);
  47. toast(`自动识别纯文本链接解析已${autoDetectRawText ? '开启' : '关闭'},刷新页面后生效`);
  48. // 更新菜单显示
  49. GM_registerMenuCommand(`自动识别纯文本链接解析 (${autoDetectRawText ? '开启' : '关闭'})`, toggleAutoDetect);
  50. }
  51.  
  52. // 在脚本启动时检查是否需要自动处理
  53. if (handleRawTextPage()) {
  54. return;
  55. }
  56.  
  57. // 通用 fetch 函数
  58. async function fetchContent(url) {
  59. try {
  60. const response = await fetch(url, {
  61. method: 'GET',
  62. credentials: 'omit',
  63. cache: 'no-store'
  64. });
  65. if (!response.ok) {
  66. throw new Error(`网络请求失败,状态码: ${response.status} (${response.statusText})`);
  67. }
  68. const contentType = response.headers.get('Content-Type') || '';
  69. if (!contentType.includes('text/')) {
  70. throw new Error('非文本内容,无法解析 (Content-Type: ' + contentType + ')');
  71. }
  72. const content = await response.text();
  73. console.log('内容获取成功,长度:', content.length);
  74. analyzeContent(content, url);
  75. } catch (e) {
  76. console.error('内容获取失败:', e);
  77. let errorMsg = '无法获取内容:';
  78. if (e.message.includes('Failed to fetch')) {
  79. errorMsg += '网络请求失败,可能是链接不可访问或被浏览器阻止(检查 CORS 或网络连接)。';
  80. } else {
  81. errorMsg += e.message;
  82. }
  83. errorMsg += '\n请确保链接有效且指向 Adblock 规则文件。';
  84. alert(errorMsg);
  85. }
  86. }
  87.  
  88. async function analyzeCurrentPage() {
  89. toast('分析当前页面');
  90. fetchContent(location.href);
  91. }
  92.  
  93. function analyzeCustomLink() {
  94. console.log('分析自定义链接');
  95. const url = prompt('请输入Adblock规则文件的直链(如 https://raw.githubusercontent.com/...)');
  96. if (!url || !url.trim()) {
  97. alert('未输入有效的链接');
  98. return;
  99. }
  100. if (!url.match(/^https?:\/\/.+/)) {
  101. alert('链接格式无效,请输入以 http:// 或 https:// 开头的完整 URL');
  102. return;
  103. }
  104. toast(`解析链接中……`);
  105. fetchContent(url);
  106. }
  107.  
  108. function normalizeNewlines(text) {
  109. return text.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
  110. }
  111.  
  112. function parseHeader(content) {
  113. const header = {
  114. title: '未知标题',
  115. description: '未添加任何描述',
  116. version: '未知版本',
  117. lastModified: '未知时间',
  118. expires: '未给出更新周期',
  119. };
  120. const headerLines = content.split('\n')
  121. .filter(line => line.trim().startsWith('!'))
  122. .map(line => line.trim().substring(1).trim());
  123.  
  124. headerLines.forEach(line => {
  125. if (line.startsWith('Title:')) header.title = line.substring(6).trim();
  126. else if (line.startsWith('Description:')) header.description = line.substring(12).trim();
  127. else if (line.startsWith('Version:')) header.version = line.substring(8).trim();
  128. else if (line.startsWith('TimeUpdated:') || line.startsWith('Last modified:') || line.startsWith('Update Time:')) {
  129. header.lastModified = line.split(':').slice(1).join(':').trim();
  130. } else if (line.startsWith('Expires:')) header.expires = line.substring(8).trim();
  131. });
  132. return header;
  133. }
  134.  
  135. function analyzeContent(content, source) {
  136. if (!content.startsWith('[Adblock') && !content.startsWith('![Adblock')) {
  137. toast(`这不是一个标准的Adblock规则文件(未找到[Adblock开头),来源: ${source}`);
  138. console.log('非Adblock文件,来源:', source);
  139. return;
  140. }
  141. content = normalizeNewlines(content);
  142. const header = parseHeader(content);
  143. const lines = content.split('\n')
  144. .filter(line => line.trim() && !line.trim().startsWith('!') && !line.trim().startsWith('['));
  145.  
  146. const stats = {
  147. cssRules: {
  148. normal: 0,
  149. exception: 0,
  150. hasNotPseudo: 0,
  151. hasSpecialPseudo: 0,
  152. hasSpecialPseudoNotAfter: 0
  153. },
  154. domainRules: {
  155. count: 0,
  156. duplicateRules: 0
  157. },
  158. unsupported: 0,
  159. extendedRules: {
  160. scriptInject: 0,
  161. adguardScript: 0,
  162. htmlFilter: 0,
  163. cssInject: 0,
  164. other: 0
  165. }
  166. };
  167.  
  168. const extendedPatterns = {
  169. scriptInject: /(##|@#+)\+js\(/,
  170. adguardScript: /#@?%#/,
  171. htmlFilter: /\$\$/,
  172. cssInject: /#@?\$#/,
  173. specialPseudo: /:matches-property\b|:style\b|:-abp-properties\b|:-abp-contains\b|:min-text-length\b|:matches-path\b|:contains\b|:has-text\b|:matches-css\b|:matches-css-before\b|:matches-css-after\b|:if\b|:if-not\b|:xpath\b|:nth-ancestor\b|:upward\b|:remove\b/,
  174. other: /\$(\s*)(redirect|rewrite|csp|removeparam|badfilter|empty|generichide|match-case|object|object-subrequest|important|popup|document)|,(\s*)(redirect=|app=|replace=|csp=|denyallow=|permissions=)|:matches-property\b|:style\b|:-abp-properties\b|:-abp-contains\b|:min-text-length\b|:matches-path\b|:contains\b|:has-text\b|:matches-css\b|:matches-css-before\b|:matches-css-after\b|:if\b|:if-not\b|:xpath\b|:nth-ancestor\b|:upward\b|:remove\b|redirect-rule/
  175. };
  176.  
  177. const rulePatternMap = new Map();
  178.  
  179. lines.forEach(line => {
  180. const trimmed = line.trim();
  181.  
  182. if (extendedPatterns.scriptInject.test(trimmed)) {
  183. stats.extendedRules.scriptInject++;
  184. stats.unsupported++;
  185. } else if (extendedPatterns.adguardScript.test(trimmed)) {
  186. stats.extendedRules.adguardScript++;
  187. stats.unsupported++;
  188. } else if (extendedPatterns.htmlFilter.test(trimmed)) {
  189. stats.extendedRules.htmlFilter++;
  190. stats.unsupported++;
  191. } else if (extendedPatterns.cssInject.test(trimmed)) {
  192. stats.extendedRules.cssInject++;
  193. stats.unsupported++;
  194. } else if (extendedPatterns.other.test(trimmed)) {
  195. stats.extendedRules.other++;
  196. stats.unsupported++;
  197. } else if (trimmed.startsWith('##') || trimmed.startsWith('###')) {
  198. stats.cssRules.normal++;
  199. if (/:has|:not/.test(trimmed)) stats.cssRules.hasNotPseudo++;
  200. if (extendedPatterns.specialPseudo.test(trimmed)) stats.cssRules.hasSpecialPseudo++;
  201. } else if (trimmed.startsWith('#@#') || trimmed.startsWith('#@##')) {
  202. stats.cssRules.exception++;
  203. if (/:has|:not/.test(trimmed)) stats.cssRules.hasNotPseudo++;
  204. if (extendedPatterns.specialPseudo.test(trimmed)) stats.cssRules.hasSpecialPseudo++;
  205. } else if (trimmed.startsWith('||')) {
  206. stats.domainRules.count++;
  207. let rulePattern = trimmed;
  208. let domains = [];
  209. const domainMatch = trimmed.match(/[,|$]domain=([^$|,]+)/);
  210. if (domainMatch) {
  211. rulePattern = trimmed.replace(/[,|$]domain=[^$|,]+/, '').replace(/[,|$].*$/, '');
  212. domains = domainMatch[1].split('|');
  213. }
  214. if (rulePatternMap.has(rulePattern)) {
  215. const ruleData = rulePatternMap.get(rulePattern);
  216. ruleData.count++;
  217. stats.domainRules.duplicateRules++;
  218. domains.forEach(domain => ruleData.domains.add(domain));
  219. } else {
  220. rulePatternMap.set(rulePattern, {
  221. domains: new Set(domains),
  222. count: 1
  223. });
  224. }
  225. }
  226.  
  227. // 检测不在合法位置的特殊伪类
  228. if (extendedPatterns.specialPseudo.test(trimmed)) {
  229. if (!trimmed.match(/^(##|###|#@#|#@##|#?#|\$\$)/)) {
  230. stats.cssRules.hasSpecialPseudoNotAfter++;
  231. }
  232. }
  233. });
  234.  
  235. const totalCssRules = stats.cssRules.normal + stats.cssRules.exception;
  236. const totalExtendedRules = stats.extendedRules.scriptInject + stats.extendedRules.adguardScript +
  237. stats.extendedRules.htmlFilter + stats.extendedRules.cssInject + stats.extendedRules.other;
  238.  
  239. let score = 0;
  240. let cssCountScore = Math.max(0, totalCssRules <= 5000 ? 35 : totalCssRules <= 7000 ? 35 - ((totalCssRules - 5000) / 2000) * 10 : totalCssRules <= 9999 ? 25 - ((totalCssRules - 7000) / 2999) * 15 : 10 - ((totalCssRules - 9999) / 5000) * 10);
  241. score += cssCountScore;
  242.  
  243. let cssPseudoScore = stats.cssRules.hasNotPseudo <= 30 ? 15 : stats.cssRules.hasNotPseudo <= 100 ? 10 : stats.cssRules.hasNotPseudo <= 120 ? 5 : 0;
  244. score += cssPseudoScore;
  245.  
  246. let domainCountScore = Math.max(0, stats.domainRules.count <= 100000 ? 30 : stats.domainRules.count <= 200000 ? 30 - ((stats.domainRules.count - 100000) / 100000) * 10 : stats.domainRules.count <= 500000 ? 20 - ((stats.domainRules.count - 200000) / 300000) * 15 : 5 - ((stats.domainRules.count - 500000) / 500000) * 5);
  247. score += domainCountScore;
  248.  
  249. let domainDuplicateScore = Math.max(0, stats.domainRules.duplicateRules <= 100 ? 10 : stats.domainRules.duplicateRules <= 300 ? 10 - ((stats.domainRules.duplicateRules - 50) / 150) * 5 : 5 - ((stats.domainRules.duplicateRules - 200) / 200) * 5);
  250. score += domainDuplicateScore;
  251.  
  252. let extendedScore = totalExtendedRules === 0 ? 10 : totalExtendedRules <= 100 ? 10 - (totalExtendedRules / 100) * 5 : totalExtendedRules <= 300 ? 5 - ((totalExtendedRules - 100) / 200) * 5 : Math.max(-10, 0 - ((totalExtendedRules - 300) / 300) * 10);
  253. score += extendedScore;
  254.  
  255. let specialPseudoPenalty = stats.cssRules.hasSpecialPseudo > 0 ? -40 : 0;
  256. score += specialPseudoPenalty;
  257.  
  258. let specialPseudoNotAfterPenalty = stats.cssRules.hasSpecialPseudoNotAfter > 0 ? -10 : 0;
  259. score += specialPseudoNotAfterPenalty;
  260.  
  261. score = Math.max(1, Math.min(100, Math.round(score)));
  262.  
  263. const cssPerformance = totalCssRules <= 5000 ? '✅CSS规则数量正常,可以流畅运行' : totalCssRules <= 7000 ? '❓CSS规则数量较多,可能会导致设备运行缓慢' : totalCssRules < 9999 ? '⚠️CSS规则数量接近上限,可能明显影响设备性能' : '🆘CSS规则数量过多,不建议订阅此规则';
  264. const domainPerformance = stats.domainRules.count <= 100000 ? '✅域名规则数量正常,可以流畅运行' : stats.domainRules.count <= 200000 ? '❓域名规则数量较多,但仍在可接受范围内' : stats.domainRules.count <= 500000 ? '🆘域名规则数量过多,可能会导致内存溢出 (OOM)' : '‼️域名规则数量极多,强烈不建议使用,可能严重影响性能';
  265.  
  266. const report = `
  267. Adblock规则分析结果(来源: ${source}):
  268. 📜Adblock规则信息:
  269. 标题: ${header.title}
  270. 描述: ${header.description}
  271. 版本: ${header.version}
  272. 最后更新: ${header.lastModified}
  273. 更新周期: ${header.expires}
  274. ---------------------
  275. 💯规则评级: ${score}/100
  276. (评分仅供参考,具体以Via变动为主)
  277. 📊各部分得分:
  278. CSS数量得分: ${Math.round(cssCountScore)}/35
  279. CSS伪类得分: ${cssPseudoScore}/15
  280. 域名数量得分: ${Math.round(domainCountScore)}/30
  281. 重复规则得分: ${Math.round(domainDuplicateScore)}/10
  282. 扩展规则加减分: ${Math.round(extendedScore)} 10)
  283. 特殊伪类惩罚: ${specialPseudoPenalty} (Adguard/uBlock特殊伪类)
  284. 特殊伪类不按语法: ${specialPseudoNotAfterPenalty} (未使用正确语法)
  285. ---------------------
  286. 🛠️总规则数: ${lines.length}
  287. 👋不支持的规则: ${stats.unsupported}
  288. 📋CSS通用隐藏规则:
  289. 常规规则 (##, ###): ${stats.cssRules.normal}
  290. 例外规则 (#@#, #@##): ${stats.cssRules.exception}
  291. 含:has/:not伪类规则: ${stats.cssRules.hasNotPseudo}
  292. Adguard/uBlock特殊伪类: ${stats.cssRules.hasSpecialPseudo}
  293. 特殊伪类未使用正确语法: ${stats.cssRules.hasSpecialPseudoNotAfter}
  294. CSS规则数: ${totalCssRules}
  295. 性能评估: ${cssPerformance}
  296. 🔗域名规则 (||):
  297. 总数: ${stats.domainRules.count}
  298. 重复规则数: ${stats.domainRules.duplicateRules}
  299. 性能评估: ${domainPerformance}
  300. ✋🏼uBlock/AdGuard 独有规则:
  301. 脚本注入 (##+js): ${stats.extendedRules.scriptInject}
  302. AdGuard脚本 (#%#): ${stats.extendedRules.adguardScript}
  303. HTML过滤 ($$): ${stats.extendedRules.htmlFilter}
  304. CSS注入 (#$#): ${stats.extendedRules.cssInject}
  305. 其他扩展规则 ($redirect等): ${stats.extendedRules.other}
  306. 总计: ${totalExtendedRules}
  307. 注:uBlock/AdGuard 独有规则及特殊伪类在传统 Adblock Plus 中不受支持
  308. `;
  309. alert(report);
  310. console.log(report);
  311. }
  312. })();

QingJ © 2025

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