M3U8 Filter Ad Script

自用,拦截和过滤 m3u8(解析/采集资源) 的切片(插播)广告,同时在console打印过滤的行信息,不会误删。

  1. // ==UserScript==
  2. // @name M3U8 Filter Ad Script
  3. // @namespace http://tampermonkey.net/
  4. // @version 2.0.1
  5. // @description 自用,拦截和过滤 m3u8(解析/采集资源) 的切片(插播)广告,同时在console打印过滤的行信息,不会误删。
  6. // @author ltxlong
  7. // @match *://*/*
  8. // @exclude *://challenges.cloudflare.com/*
  9. // @exclude *://*.hcaptcha.com/*
  10. // @exclude *://*/*recaptcha*
  11. // @exclude *://api.geetest.com/*
  12. // @exclude *://static.geetest.com/*
  13. // @run-at document-start
  14. // @grant unsafeWindow
  15. // @grant GM_getResourceText
  16. // @grant GM_registerMenuCommand
  17. // @grant GM_unregisterMenuCommand
  18. // @grant GM_setValue
  19. // @grant GM_getValue
  20. // @require https://unpkg.com/sweetalert2@11/dist/sweetalert2.min.js
  21. // @resource Swal https://unpkg.com/sweetalert2@11/dist/sweetalert2.min.css
  22. // @resource SwalDark https://unpkg.com/@sweetalert2/theme-dark@5/dark.min.css
  23. // @license MIT
  24. // ==/UserScript==
  25.  
  26. (function() {
  27. 'use strict';
  28.  
  29. // --- 判断是否为验证页面 ---
  30. function is_verification_page() {
  31. const current_url = unsafeWindow.location.href;
  32. const page_title = unsafeWindow.document.title;
  33.  
  34. const verification_keywords = [
  35. // URL 路径特征
  36. 'challenges.cloudflare.com',
  37. 'geetest.com',
  38. 'captcha',
  39. 'challenge-platform',
  40. // 页面标题关键词
  41. 'just a moment',
  42. 'checking your browser',
  43. 'checking browser security',
  44. 'security check',
  45. 'verify you are human',
  46. 'please verify',
  47. 'human verification',
  48. 'security challenge',
  49. 'are you a human',
  50. 'are you a robot',
  51. 'not a robot',
  52. 'bot detection',
  53. '验证',
  54. '安全防护',
  55. '安全检测',
  56. '安全检查',
  57. '正在检查您的浏览器',
  58. '请稍候',
  59. '我不是机器人'
  60. ];
  61.  
  62. for (const the_keyword of verification_keywords) {
  63. if (current_url.toLowerCase().includes(the_keyword) || (page_title && page_title.toLowerCase().includes(the_keyword))) {
  64. return true;
  65. }
  66. }
  67.  
  68. return false;
  69. }
  70.  
  71. // ======== 脚本执行入口 ========
  72. // 如果是人机校验页面,则立即停止后续所有操作
  73. if (is_verification_page()) {
  74. return;
  75. }
  76. // ======== 检查结束 ========
  77.  
  78. let ts_name_len = 0; // ts前缀长度
  79.  
  80. let ts_name_len_extend = 1; // 容错
  81.  
  82. let first_extinf_row = '';
  83.  
  84. let the_extinf_judge_row_n = 0;
  85.  
  86. let the_same_extinf_name_n = 0;
  87.  
  88. let the_extinf_benchmark_n = 5; // 基准
  89.  
  90. let prev_ts_name_index = -1; // 上个ts序列号
  91.  
  92. let first_ts_name_index = -1; // 首个ts序列号
  93.  
  94. let ts_type = 0; // 0:xxxx000数字递增.ts模式0 ;1:xxxxxxxxxx.ts模式1 ;2:***.ts模式2-暴力拆解
  95.  
  96. let the_ext_x_mode = 0; // 0:ext_x_discontinuity判断模式0 ;1:ext_x_discontinuity判断模式1
  97.  
  98. let the_current_host = unsafeWindow.location.hostname;
  99.  
  100. let script_whitelist_mode_flag = false; // 是否启用白名单模式,默认否,默认是匹配所有的网站
  101.  
  102. let the_current_host_in_whitelist_flag = false; // 当前域名是否在白名单,默认否
  103.  
  104. let show_toast_tip_flag = false; // 是否启用弹窗提示,默认否
  105.  
  106. let violent_filter_mode_flag = false; // 是否启用暴力拆解模式,默认否-自动判断模式
  107.  
  108. let filter_log_html = '';
  109.  
  110. let filter_done_flag = false;
  111.  
  112. function filter_log(...msg) {
  113.  
  114. const log_content = msg.join('</p><p>');
  115.  
  116. console.log('%c[m3u8_filter_ad]', 'font-weight: bold; color: white; background-color: #70b566b0; padding: 2px; border-radius: 2px;', ...msg);
  117.  
  118. filter_log_html += `
  119. <p>
  120. <span style="font-weight: bold; color: white; background-color: #70b566b0; padding: 2px; border-radius: 2px;">[m3u8_filter_ad]</span>
  121. ${log_content}
  122. </p>
  123. `;
  124.  
  125. }
  126.  
  127. let the_swalcss_color = "#ff679a";
  128.  
  129. let swalcss = `
  130. .swal2-styled{transition: all 0.2s ease;}
  131. .swal2-loader{display:none;align-items:center;justify-content:center;width:2.2em;height:2.2em;margin:0 1.875em;-webkit-animation:swal2-rotate-loading 1.5s linear 0s infinite normal;animation:swal2-rotate-loading 1.5s linear 0s infinite normal;border-width:.25em;border-style:solid;border-radius:100%;border-color:${the_swalcss_color} transparent }
  132. .swal2-styled.swal2-confirm{border:0;border-radius:.25em;background:initial;background-color:${the_swalcss_color};color:#fff;font-size:1em}
  133. .swal2-styled.swal2-confirm:hover,.swal2-styled.swal2-deny:hover{opacity:0.8;background-image:none!important}
  134. .swal2-styled.swal2-confirm:focus{box-shadow:0 0 0 3px ${the_swalcss_color}80}
  135. .swal2-styled.swal2-deny:focus{box-shadow:0 0 0 3px #dc374180}
  136. .swal2-timer-progress-bar-container{position:absolute;right:0;bottom:0;left:0;grid-column:auto;overflow:hidden;border-bottom-right-radius:5px;border-bottom-left-radius:5px}
  137. .swal2-timer-progress-bar{width:100%;height:.25em;background:${the_swalcss_color}33 }
  138. .swal2-progress-steps .swal2-progress-step{z-index:20;flex-shrink:0;width:2em;height:2em;border-radius:2em;background:${the_swalcss_color};color:#fff;line-height:2em;text-align:center}
  139. .swal2-progress-steps .swal2-progress-step.swal2-active-progress-step{background:${the_swalcss_color} }
  140. .swal2-progress-steps .swal2-progress-step-line{z-index:10;flex-shrink:0;width:2.5em;height:.4em;margin:0 -1px;background:${the_swalcss_color}}
  141. .swal2-popup {padding:1.25em 0 1.25em;flex-direction:column}
  142. .swal2-close {position:absolute;top:1px;right:1px;transition: all 0.2s ease;}
  143. div:where(.swal2-container) .swal2-html-container{padding: 1.3em 1.3em 0.3em;}
  144. div:where(.swal2-container) button:where(.swal2-close):hover {color:${the_swalcss_color}!important;font-size:60px!important}
  145. div:where(.swal2-icon) .swal2-icon-content {font-family: sans-serif;}
  146. .swal2-container {z-index: 1145141919810;}
  147. `;
  148.  
  149. // 动态添加样式
  150. function add_style(id, css) {
  151.  
  152. let try_add_style_n = 0;
  153.  
  154. let try_to_add_style = function() {
  155. let the_style_dom = unsafeWindow.document.getElementById(id);
  156. if (the_style_dom) the_style_dom.remove();
  157.  
  158. let the_style = unsafeWindow.document.createElement('style');
  159. the_style.rel = 'stylesheet';
  160. the_style.id = id;
  161. the_style.innerHTML = css;
  162.  
  163. let the_target_element = unsafeWindow.document.body;
  164. if (the_target_element) {
  165. the_target_element.insertBefore(the_style, the_target_element.firstChild);
  166. } else {
  167. try_add_style_n++;
  168. if (try_add_style_n < 50) {
  169. setTimeout(try_to_add_style, 100);
  170. }
  171. }
  172. };
  173.  
  174. try_to_add_style();
  175. }
  176.  
  177. // 先监听颜色方案变化
  178. unsafeWindow.matchMedia('(prefers-color-scheme: dark)').addListener(function (e) {
  179. if (e.matches) {
  180. // 切换到暗色主题
  181. add_style('swal-pub-style', GM_getResourceText('SwalDark'));
  182. } else {
  183. // 切换到浅色主题
  184. add_style('swal-pub-style', GM_getResourceText('Swal'));
  185. }
  186.  
  187. add_style('Panlinker-SweetAlert2-User', swalcss);
  188. });
  189.  
  190. // 再修改主题
  191. if (unsafeWindow.matchMedia && unsafeWindow.matchMedia('(prefers-color-scheme: dark)').matches) {
  192. // 切换到暗色主题
  193. add_style('swal-pub-style', GM_getResourceText('SwalDark'));
  194. } else {
  195. // 切换到浅色主题
  196. add_style('swal-pub-style', GM_getResourceText('Swal'));
  197. }
  198.  
  199. add_style('Panlinker-SweetAlert2-User', swalcss);
  200.  
  201. // Toast 提示配置
  202. let toast = Swal.mixin({
  203. toast: true,
  204. position: 'top-end',
  205. showConfirmButton: false,
  206. timer: 3000,
  207. timerProgressBar: true,
  208. showCloseButton: true,
  209. didOpen: function (toast) {
  210. toast.addEventListener('mouseenter', Swal.stopTimer);
  211. toast.addEventListener('mouseleave', Swal.resumeTimer);
  212. }
  213. });
  214.  
  215. // Toast 简易调用
  216. let message = {
  217. success: function (text) {
  218. toast.fire({ title: text, icon: 'success' });
  219. },
  220. error: function (text) {
  221. toast.fire({ title: text, icon: 'error' });
  222. },
  223. warning: function (text) {
  224. toast.fire({ title: text, icon: 'warning' });
  225. },
  226. info: function (text) {
  227. toast.fire({ title: text, icon: 'info' });
  228. },
  229. question: function (text) {
  230. toast.fire({ title: text, icon: 'question' });
  231. }
  232. };
  233.  
  234. function is_m3u8_file(url) {
  235. return /\.m3u8($|\?)/.test(url);
  236. }
  237.  
  238. function extract_number_before_ts(str) {
  239. // 匹配 .ts 前面的数字
  240. const match = str.match(/(\d+)\.ts/);
  241.  
  242. if (match) {
  243. // 使用 parseInt 去掉前导 0
  244. return parseInt(match[1], 10);
  245. }
  246.  
  247. return null; // 如果不匹配,返回 null
  248. }
  249.  
  250. function filter_lines(lines) {
  251. let result = [];
  252.  
  253. if (violent_filter_mode_flag) {
  254. filter_log('----------------------------暴力拆解模式--------------------------');
  255.  
  256. ts_type = 2; // ts命名模式
  257. } else {
  258. filter_log('----------------------------自动判断模式--------------------------');
  259.  
  260. let the_normal_int_ts_n = 0;
  261. let the_diff_int_ts_n = 0;
  262.  
  263. let last_ts_name_len = 0;
  264.  
  265. // 初始化参数
  266. for (let i = 0; i < lines.length; i++) {
  267.  
  268. const line = lines[i];
  269.  
  270. // 初始化first_extinf_row
  271. if (the_extinf_judge_row_n === 0 && line.startsWith('#EXTINF')) {
  272. first_extinf_row = line;
  273.  
  274. the_extinf_judge_row_n++;
  275. } else if (the_extinf_judge_row_n === 1 && line.startsWith('#EXTINF')) {
  276. if (line !== first_extinf_row) {
  277. first_extinf_row = '';
  278. }
  279.  
  280. the_extinf_judge_row_n++;
  281. }
  282.  
  283. // 判断ts模式
  284. let the_ts_name_len = line.indexOf('.ts'); // ts前缀长度
  285.  
  286. if (the_ts_name_len > 0) {
  287.  
  288. if (the_extinf_judge_row_n === 1) {
  289. ts_name_len = the_ts_name_len;
  290. }
  291.  
  292. last_ts_name_len = the_ts_name_len;
  293.  
  294. let ts_name_index = extract_number_before_ts(line);
  295. if (ts_name_index === null) {
  296. if (the_extinf_judge_row_n === 1) {
  297. ts_type = 1; // ts命名模式
  298. } else if (the_extinf_judge_row_n === 2 && (ts_type === 1 || the_ts_name_len === ts_name_len)) {
  299. ts_type = 1; // ts命名模式
  300.  
  301. filter_log('----------------------------识别ts模式1---------------------------');
  302.  
  303. break;
  304. } else {
  305. the_diff_int_ts_n++;
  306. }
  307. } else {
  308.  
  309. // 如果序号相隔等于1: 模式0
  310. // 如果序号相隔大于1,或其他:模式2(暴力拆解)
  311.  
  312. if (the_normal_int_ts_n === 0) {
  313. // 初始化ts序列号
  314. prev_ts_name_index = ts_name_index;
  315. first_ts_name_index = ts_name_index;
  316. prev_ts_name_index = first_ts_name_index - 1;
  317. }
  318.  
  319. if (the_ts_name_len !== ts_name_len) {
  320.  
  321. if (the_ts_name_len === last_ts_name_len + 1 && ts_name_index === prev_ts_name_index + 1) {
  322.  
  323. if (the_diff_int_ts_n) {
  324.  
  325. if (ts_name_index === prev_ts_name_index + 1) {
  326. ts_type = 0; // ts命名模式
  327. prev_ts_name_index = first_ts_name_index - 1;
  328.  
  329. filter_log('----------------------------识别ts模式0---------------------------')
  330.  
  331. break;
  332. } else {
  333. ts_type = 2; // ts命名模式
  334.  
  335. filter_log('----------------------------识别ts模式2---------------------------')
  336.  
  337. break;
  338. }
  339. }
  340.  
  341. the_normal_int_ts_n++;
  342. prev_ts_name_index = ts_name_index;
  343.  
  344. } else {
  345. the_diff_int_ts_n++;
  346. }
  347. } else {
  348.  
  349. if (the_diff_int_ts_n) {
  350.  
  351. if (ts_name_index === prev_ts_name_index + 1) {
  352. ts_type = 0; // ts命名模式
  353. prev_ts_name_index = first_ts_name_index - 1;
  354.  
  355. filter_log('----------------------------识别ts模式0---------------------------')
  356.  
  357. break;
  358. } else {
  359. ts_type = 2; // ts命名模式
  360.  
  361. filter_log('----------------------------识别ts模式2---------------------------')
  362.  
  363. break;
  364. }
  365. }
  366.  
  367. the_normal_int_ts_n++;
  368. prev_ts_name_index = ts_name_index;
  369. }
  370. }
  371. }
  372.  
  373. if (i === lines.length - 1) {
  374. // 后缀不是ts,而是jpeg等等,或者以上规则判断不了的,或者没有广告切片的:直接暴力拆解过滤
  375.  
  376. ts_type = 2; // ts命名模式
  377.  
  378. filter_log('----------------------------进入暴力拆解模式---------------------------')
  379. }
  380. }
  381. }
  382.  
  383. // 开始遍历过滤
  384. for (let i = 0; i < lines.length; i++) {
  385.  
  386. let ts_index_check = false;
  387.  
  388. const line = lines[i];
  389.  
  390. if (ts_type === 0) {
  391.  
  392. if (line.startsWith('#EXT-X-DISCONTINUITY') && lines[i + 1] && lines[i + 2]) {
  393.  
  394. // 检查当前行是否跟 #EXT-X-相关
  395. if (i > 0 && lines[i - 1].startsWith('#EXT-X-')) {
  396. result.push(line);
  397.  
  398. continue;
  399. } else {
  400. let the_ts_name_len = lines[i + 2].indexOf('.ts'); // ts前缀长度
  401.  
  402. if (the_ts_name_len > 0) {
  403.  
  404. // 根据ts名字长度过滤
  405. if (the_ts_name_len - ts_name_len > ts_name_len_extend) {
  406. // 广告过滤
  407. if (lines[i + 3] && lines[i + 3].startsWith('#EXT-X-DISCONTINUITY')) {
  408. // 打印即将过滤的行
  409. filter_log('过滤规则: #EXT-X-DISCONTINUITY-ts文件名长度-');
  410. filter_log('过滤的行:', "\n", line, "\n", lines[i + 1], "\n", lines[i + 2], "\n", lines[i + 3]);
  411. filter_log('------------------------------------------------------------------');
  412.  
  413. i += 3;
  414. } else {
  415. // 打印即将过滤的行
  416. filter_log('过滤规则: #EXT-X-DISCONTINUITY-ts文件名长度');
  417. filter_log('过滤的行:', "\n", line, "\n", lines[i + 1], "\n", lines[i + 2]);
  418. filter_log('------------------------------------------------------------------');
  419.  
  420. i += 2;
  421. }
  422.  
  423. continue;
  424. } else {
  425. ts_name_len = the_ts_name_len;
  426. }
  427.  
  428. // 根据ts序列号过滤
  429. let the_ts_name_index = extract_number_before_ts(lines[i + 2]);
  430.  
  431. if (the_ts_name_index !== prev_ts_name_index + 1) {
  432.  
  433. // 广告过滤
  434. if (lines[i + 3] && lines[i + 3].startsWith('#EXT-X-DISCONTINUITY')) {
  435. // 打印即将过滤的行
  436. filter_log('过滤规则: #EXT-X-DISCONTINUITY-ts序列号-');
  437. filter_log('过滤的行:', "\n", line, "\n", lines[i + 1], "\n", lines[i + 2], "\n", lines[i + 3]);
  438. filter_log('------------------------------------------------------------------');
  439.  
  440. i += 3;
  441. } else {
  442. // 打印即将过滤的行
  443. filter_log('过滤规则: #EXT-X-DISCONTINUITY-ts序列号');
  444. filter_log('过滤的行:', "\n", line, "\n", lines[i + 1], "\n", lines[i + 2]);
  445. filter_log('------------------------------------------------------------------');
  446.  
  447. i += 2;
  448. }
  449.  
  450. continue;
  451. }
  452. }
  453. }
  454. }
  455.  
  456. if (line.startsWith('#EXTINF') && lines[i + 1]) {
  457.  
  458. let the_ts_name_len = lines[i + 1].indexOf('.ts'); // ts前缀长度
  459.  
  460. if (the_ts_name_len > 0) {
  461.  
  462. // 根据ts名字长度过滤
  463. if (the_ts_name_len - ts_name_len > ts_name_len_extend) {
  464. // 广告过滤
  465. if (lines[i + 2] && lines[i + 2].startsWith('#EXT-X-DISCONTINUITY')) {
  466. // 打印即将过滤的行
  467. filter_log('过滤规则: #EXTINF-ts文件名长度-');
  468. filter_log('过滤的行:', "\n", line, "\n", lines[i + 1], "\n", lines[i + 2]);
  469. filter_log('------------------------------------------------------------------');
  470.  
  471. i += 2;
  472. } else {
  473. // 打印即将过滤的行
  474. filter_log('过滤规则: #EXTINF-ts文件名长度');
  475. filter_log('过滤的行:', "\n", line, "\n", lines[i + 1]);
  476. filter_log('------------------------------------------------------------------');
  477.  
  478. i += 1;
  479. }
  480.  
  481. continue;
  482. } else {
  483. ts_name_len = the_ts_name_len;
  484. }
  485.  
  486. // 根据ts序列号过滤
  487. let the_ts_name_index = extract_number_before_ts(lines[i + 1]);
  488.  
  489. if (the_ts_name_index === prev_ts_name_index + 1) {
  490.  
  491. prev_ts_name_index++;
  492.  
  493. } else {
  494. // 广告过滤
  495. if (lines[i + 2].startsWith('#EXT-X-DISCONTINUITY')) {
  496. // 打印即将过滤的行
  497. filter_log('过滤规则: #EXTINF-ts序列号-');
  498. filter_log('过滤的行:', "\n", line, "\n", lines[i + 1], "\n", lines[i + 2]);
  499. filter_log('------------------------------------------------------------------');
  500.  
  501. i += 2;
  502. } else {
  503. // 打印即将过滤的行
  504. filter_log('过滤规则: #EXTINF-ts序列号');
  505. filter_log('过滤的行:', "\n", line, "\n", lines[i + 1]);
  506. filter_log('------------------------------------------------------------------');
  507.  
  508. i += 1;
  509. }
  510.  
  511. continue;
  512. }
  513. }
  514. }
  515. } else if (ts_type === 1) {
  516.  
  517. if (line.startsWith('#EXTINF')) {
  518. if (line === first_extinf_row && the_same_extinf_name_n <= the_extinf_benchmark_n && the_ext_x_mode === 0) {
  519. the_same_extinf_name_n++;
  520. } else {
  521. the_ext_x_mode = 1;
  522. }
  523.  
  524. if (the_same_extinf_name_n > the_extinf_benchmark_n) {
  525. the_ext_x_mode = 1;
  526. }
  527. }
  528.  
  529. if (line.startsWith('#EXT-X-DISCONTINUITY')) {
  530. // 检查当前行是否跟 #EXT-X-PLAYLIST-TYPE相关
  531. if (i > 0 && lines[i - 1].startsWith('#EXT-X-PLAYLIST-TYPE')) {
  532. result.push(line);
  533.  
  534. continue;
  535. } else {
  536.  
  537. // 如果第 i+2 行是 .ts 文件,跳过当前行和接下来的两行
  538. if (lines[i + 1] && lines[i + 1].startsWith('#EXTINF') && lines[i + 2] && lines[i + 2].indexOf('.ts') > 0) {
  539.  
  540. let the_ext_x_discontinuity_condition_flag = false;
  541.  
  542. if (the_ext_x_mode === 1) {
  543. the_ext_x_discontinuity_condition_flag = lines[i + 1] !== first_extinf_row && the_same_extinf_name_n > the_extinf_benchmark_n;
  544. }
  545.  
  546. // 进一步检测第 i+3 行是否也是 #EXT-X-DISCONTINUITY
  547. if (lines[i + 3] && lines[i + 3].startsWith('#EXT-X-DISCONTINUITY') && the_ext_x_discontinuity_condition_flag) {
  548. // 打印即将过滤的行
  549. filter_log('过滤规则: #EXT-X-DISCONTINUITY-广告-#EXT-X-DISCONTINUITY过滤');
  550. filter_log('过滤的行:', "\n", line, "\n", lines[i + 1], "\n", lines[i + 2], "\n", lines[i + 3]);
  551. filter_log('------------------------------------------------------------------');
  552.  
  553. i += 3; // 跳过当前行和接下来的三行
  554. } else {
  555. // 打印即将过滤的行
  556. filter_log('过滤规则: #EXT-X-DISCONTINUITY-单个标识过滤');
  557. filter_log('过滤的行:', "\n", line);
  558. filter_log('------------------------------------------------------------------');
  559. }
  560.  
  561. continue;
  562. }
  563. }
  564. }
  565. } else {
  566.  
  567. // 暴力拆解
  568. if (line.startsWith('#EXT-X-DISCONTINUITY')) {
  569. // 检查当前行是否跟 #EXT-X-PLAYLIST-TYPE相关
  570. if (i > 0 && lines[i - 1].startsWith('#EXT-X-PLAYLIST-TYPE')) {
  571. result.push(line);
  572.  
  573. continue;
  574. } else {
  575.  
  576. // 打印即将过滤的行
  577. filter_log('过滤规则: #EXT-X-DISCONTINUITY-单个标识过滤');
  578. filter_log('过滤的行:', "\n", line);
  579. filter_log('------------------------------------------------------------------');
  580.  
  581. continue;
  582. }
  583. }
  584. }
  585.  
  586. // 保留不需要过滤的行
  587. result.push(line);
  588. }
  589.  
  590. return result;
  591. }
  592.  
  593. async function safely_process_m3u8(url, content) {
  594. try {
  595. const lines = content.split('\n');
  596. const new_ines = filter_lines(lines);
  597.  
  598. return new_ines.join('\n');
  599. } catch (e) {
  600. filter_log(`处理 m3u8 文件时出错: ${url}`, e);
  601.  
  602. return content;
  603. }
  604. }
  605.  
  606. // 脚本菜单变量
  607. let menu_item_violent = null;
  608. let menu_item_mode = null;
  609. let menu_item_host_join = null;
  610. let menu_item_toast = null;
  611. let menu_item_filter_tip = null;
  612.  
  613. function hookXHR() {
  614. const OriginalXHR = unsafeWindow.XMLHttpRequest;
  615. unsafeWindow.XMLHttpRequest = class extends OriginalXHR {
  616. constructor() {
  617. super();
  618.  
  619. this.addEventListener('readystatechange', async function () {
  620.  
  621. if (this.readyState === 4 && this.status === 200 && is_m3u8_file(this.responseURL)) {
  622.  
  623. filter_log('----------------------------hookXHR成功---------------------------');
  624.  
  625. const modifiedResponse = await safely_process_m3u8(this.responseURL, this.responseText);
  626. Object.defineProperty(this, 'responseText', { value: modifiedResponse });
  627. Object.defineProperty(this, 'response', { value: modifiedResponse });
  628.  
  629. if (show_toast_tip_flag) {
  630. message.success('已成功过滤切片广告');
  631. }
  632.  
  633. filter_done_flag = true;
  634. GM_unregisterMenuCommand(menu_item_filter_tip);
  635. check_menu_item_filter_tip();
  636. }
  637. }, false);
  638. }
  639. };
  640. }
  641.  
  642. function initHook() {
  643. hookXHR();
  644. }
  645.  
  646. // 初始化菜单判断变量
  647. violent_filter_mode_flag = GM_getValue('violent_filter_mode_flag', false);
  648. script_whitelist_mode_flag = GM_getValue('script_whitelist_mode_flag', false);
  649. the_current_host_in_whitelist_flag = GM_getValue(the_current_host, false);
  650. show_toast_tip_flag = GM_getValue('show_toast_tip_flag', false);
  651.  
  652. function check_menu_item_violent() {
  653. if (violent_filter_mode_flag) {
  654. menu_item_violent = GM_registerMenuCommand('暴力拆解模式(可点击切换到自动判断过滤模式)', function() {
  655.  
  656. GM_setValue('violent_filter_mode_flag', false);
  657.  
  658. violent_filter_mode_flag = false;
  659.  
  660. message.success('已设置:<br><br>自动判断过滤模式!');
  661.  
  662. unsafeWindow.location.reload();
  663. });
  664. } else {
  665. menu_item_violent = GM_registerMenuCommand('自动判断过滤模式(可点击切换到暴力拆解模式)', function() {
  666.  
  667. GM_setValue('violent_filter_mode_flag', true);
  668.  
  669. violent_filter_mode_flag = true;
  670.  
  671. message.success('已设置:<br><br>暴力拆解模式!');
  672.  
  673. unsafeWindow.location.reload();
  674. });
  675. }
  676. }
  677.  
  678. function check_menu_item_mode() {
  679. if (script_whitelist_mode_flag) {
  680. menu_item_mode = GM_registerMenuCommand('现在是白名单模式(可点击切换到全匹配模式)', function() {
  681.  
  682. GM_setValue('script_whitelist_mode_flag', false);
  683.  
  684. script_whitelist_mode_flag = false;
  685.  
  686. message.success('已设置:<br><br>全匹配模式,即匹配所有网站!');
  687.  
  688. unsafeWindow.location.reload();
  689. });
  690. } else {
  691. menu_item_mode = GM_registerMenuCommand('现在是全匹配模式(可点击切换到白名单模式)', function() {
  692.  
  693. GM_setValue('script_whitelist_mode_flag', true);
  694.  
  695. script_whitelist_mode_flag = true;
  696.  
  697. message.success('已设置:<br><br>白名单模式,即需要单个网站设置加入过滤名单!');
  698.  
  699. unsafeWindow.location.reload();
  700. });
  701. }
  702. }
  703.  
  704. function check_menu_item_host_join() {
  705. if (script_whitelist_mode_flag) {
  706.  
  707. if (the_current_host_in_whitelist_flag) {
  708.  
  709. initHook();
  710.  
  711. if (unsafeWindow.self === unsafeWindow.top) {
  712.  
  713. if (menu_item_host_join === null) {
  714. filter_log('----------------------------脚本加载完成---------------------------');
  715. filter_log('----------------------------还没 hookXHR---------------------------');
  716. }
  717.  
  718. menu_item_host_join = GM_registerMenuCommand('本网站已开启过滤(可点击关闭广告过滤)', function() {
  719.  
  720. GM_setValue(the_current_host, false);
  721.  
  722. the_current_host_in_whitelist_flag = false;
  723.  
  724. message.success('已设置:<br><br>关闭本网站的广告过滤!');
  725.  
  726. unsafeWindow.location.reload();
  727. });
  728.  
  729. }
  730. } else {
  731. if (unsafeWindow.self === unsafeWindow.top) {
  732.  
  733. if (menu_item_host_join === null) {
  734. filter_log('----------------------------还没开启过滤---------------------------');
  735. }
  736.  
  737. menu_item_host_join = GM_registerMenuCommand('本网站已关闭过滤(可点击开启广告过滤)', function() {
  738.  
  739. GM_setValue(the_current_host, true);
  740.  
  741. the_current_host_in_whitelist_flag = true;
  742.  
  743. message.success('已设置:<br><br>开启本网站的广告过滤!');
  744.  
  745. unsafeWindow.location.reload();
  746. });
  747.  
  748. }
  749. }
  750. } else {
  751. if (menu_item_host_join === null) {
  752. filter_log('----------------------------脚本加载完成---------------------------');
  753. filter_log('----------------------------还没 hookXHR---------------------------');
  754.  
  755. initHook();
  756. }
  757. }
  758. }
  759.  
  760. function check_menu_item_toast() {
  761. if (show_toast_tip_flag) {
  762. if (unsafeWindow.self === unsafeWindow.top) {
  763. menu_item_toast = GM_registerMenuCommand('已开启弹窗提示(可点击设置关闭)', function() {
  764.  
  765. GM_setValue('show_toast_tip_flag', false);
  766.  
  767. show_toast_tip_flag = false;
  768.  
  769. message.success('已设置:<br><br>关闭弹窗提示!');
  770.  
  771. add_menu_item_all();
  772. });
  773. }
  774. } else {
  775. if (unsafeWindow.self === unsafeWindow.top) {
  776. menu_item_toast = GM_registerMenuCommand('已关闭弹窗提示(可点击设置开启)', function() {
  777.  
  778. GM_setValue('show_toast_tip_flag', true);
  779.  
  780. show_toast_tip_flag = true;
  781.  
  782. message.success('已设置:<br><br>开启弹窗提示!');
  783.  
  784. add_menu_item_all();
  785. });
  786. }
  787. }
  788. }
  789.  
  790. function register_menu_filter_done_tip() {
  791. menu_item_filter_tip = GM_registerMenuCommand('提示:已成功过滤视频切片广告', function() {
  792. Swal.fire({
  793. title: '过滤日志',
  794. type: 'info',
  795. icon: 'info',
  796. html: filter_log_html,
  797. width: '50%',
  798. showClass: {
  799. popup: ''
  800. },
  801. hideClass: {
  802. popup: ''
  803. },
  804. confirmButtonText: 'OK',
  805. showCloseButton: true
  806. });
  807. });
  808. }
  809.  
  810. function register_menu_filter_undone_tip() {
  811. menu_item_filter_tip = GM_registerMenuCommand('提示:还没有过滤视频切片广告', function() {
  812. Swal.fire({
  813. type: 'info',
  814. icon: 'info',
  815. html: '过滤视频切片广告失败! <br><br> ctrl + F5 刷新试试 <br><br> 如果还不行,那说明:<br><br> 视频格式不是m3u8,无法过滤!',
  816. showClass: {
  817. popup: ''
  818. },
  819. hideClass: {
  820. popup: ''
  821. },
  822. confirmButtonText: 'OK',
  823. showCloseButton: true
  824. });
  825. });
  826. }
  827.  
  828. function check_menu_item_filter_tip() {
  829. if (filter_done_flag) {
  830. register_menu_filter_done_tip();
  831. } else {
  832. if ((script_whitelist_mode_flag && the_current_host_in_whitelist_flag) || !script_whitelist_mode_flag) {
  833. register_menu_filter_undone_tip();
  834. }
  835. }
  836. }
  837.  
  838. function remove_menu_item_all() {
  839. GM_unregisterMenuCommand(menu_item_violent);
  840. GM_unregisterMenuCommand(menu_item_mode);
  841. GM_unregisterMenuCommand(menu_item_host_join);
  842. GM_unregisterMenuCommand(menu_item_toast);
  843. }
  844.  
  845. function add_menu_item_all() {
  846. remove_menu_item_all();
  847.  
  848. check_menu_item_violent();
  849. check_menu_item_mode();
  850. check_menu_item_host_join();
  851. check_menu_item_toast();
  852. }
  853.  
  854. add_menu_item_all();
  855.  
  856. function listen_video_load_meta() {
  857. if ((script_whitelist_mode_flag && the_current_host_in_whitelist_flag) || !script_whitelist_mode_flag) {
  858.  
  859. let try_listen_video_meta_n = 0;
  860.  
  861. let try_to_add_listen_video_meta = function () {
  862. let the_video_dom = unsafeWindow.document.querySelector('video');
  863. if (the_video_dom) {
  864. the_video_dom.addEventListener('loadedmetadata', () => {
  865. setTimeout(function() {
  866. if (!filter_done_flag) {
  867.  
  868. if (show_toast_tip_flag) {
  869.  
  870. if (the_video_dom.src.indexOf('.mp4') > 0) {
  871. message.error('过滤失败!<br><br>播放视频格式是mp4 <br><br>不是m3u8,无法过滤!');
  872. } else {
  873. message.warning('过滤失败!ctrl+F5 刷新试试,<br><br> 若还不行,说明播放视频格式<br>不是m3u8,无法过滤!');
  874. }
  875.  
  876. }
  877.  
  878. GM_unregisterMenuCommand(menu_item_filter_tip);
  879. check_menu_item_filter_tip();
  880. }
  881. }, 1000);
  882. });
  883. } else {
  884. try_listen_video_meta_n++;
  885. if (try_listen_video_meta_n < 50) {
  886. setTimeout(try_to_add_listen_video_meta, 100);
  887. }
  888. }
  889. }
  890.  
  891. try_to_add_listen_video_meta();
  892. }
  893. }
  894.  
  895. listen_video_load_meta();
  896.  
  897. })();