├── LICENSE ├── README.md └── m3u8_filter_ad.js /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 ltxlong 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 脚本菜单说明: 2 | 3 | ``` 4 | 自动判断模式和暴力拆解模式: 5 | 6 | 自动判断模式:包含了暴力拆解模式,其中的非暴力拆解会真正的删除切片(默认为该模式)(推荐) 7 | 8 | 暴力拆解模式:不会真正的删除切片,只删除标识,兜底的选择 9 | ``` 10 | ``` 11 | 全匹配模式和白名单模式: 12 | 13 | 白名单模式:只有将网站加入白名单,才会开启过滤,即需要单个网站开启过滤 14 | 15 | 全匹配模式:所有的网站都开启过滤(默认为该模式) 16 | ``` 17 | ``` 18 | 弹窗提示: 19 | 20 | 默认关闭弹窗提示,需要的开启 21 | 22 | 弹窗提示广告过滤成功还是失败 23 | ``` 24 | ``` 25 | 菜单提示: 26 | 27 | 【提示:还没有过滤视频切片广告】 或者 【提示:已成功过滤视频切片广告】 28 | 29 | 可以点击提示查看具体详情 30 | 31 | 点击【提示:还没有过滤视频切片广告】会显示解决方法 32 | 33 | 点击【提示:已成功过滤视频切片广告】会显示过滤日志 34 | 35 | 对于【提示:还没有过滤视频切片广告】,有两种情况: 36 | 37 | 1、插件没按正确顺序加载,一般刷新页面就行(如果F5刷新不行,那就ctrl+F5刷新) 38 | 39 | 2、播放视频格式不是m3u8格式,插件过滤不了 40 | ``` 41 | 42 | # 使用注意 43 | 44 | - 如果进入播放页,播放的时候发现进度条只有几秒(新版本应该不会发生了),直接刷新页面就可以了,若还不行,切换到暴力拆解模式试试。 45 | 46 | - 如果播放的时候卡住了,直接刷新页面 或者 有时候快进一下(如:按->右方向键)就好;有时候是网速问题和这脚本无关。 47 | 48 | - 如果播放的时候还有进度条,但突然结束了,快进一下再刷新页面即可。(暴力拆解模式也一样,视频和播放器特性问题) 49 | 50 | - 如果遇到过滤不了的 或者 过滤出错的视频,切换到暴力拆解模式试试,或者 ctrl+F5 刷新试试。 51 | 52 | - 这脚本和其他过滤脚本相比最大的优点是:不会误删! 53 | 54 | 55 | ``` 56 | 对于解析资源(需要点击按钮来解析视频的),请使用全匹配模式 57 | ``` 58 | 59 | 60 | ``` 61 | 如果想要排除某个网站,可以在访问该网站时,点击油猴扩展图标-点击【M3U8 Filter Ad Script】选项-点击排除该网站-刷新页面即可 62 | 63 | 如果想恢复排除的网站:编辑脚本-设置-下滑滚动条,在【包含/排除】-【用户排除】选择删除相应的网站匹配规则即可 64 | 65 | 66 | 如果只想要匹配特定的网站,不建议自己修改代码,建议直接使用白名单模式 67 | ``` 68 | -------------------------------------------------------------------------------- /m3u8_filter_ad.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name M3U8 Filter Ad Script 3 | // @namespace http://tampermonkey.net/ 4 | // @version 1.3.2 5 | // @description 自用,拦截和过滤 m3u8(解析/采集资源) 的切片(插播)广告,同时在console打印过滤的行信息,不会误删。 6 | // @author ltxlong 7 | // @match *://*/* 8 | // @run-at document-start 9 | // @grant unsafeWindow 10 | // @grant GM_getResourceText 11 | // @grant GM_registerMenuCommand 12 | // @grant GM_unregisterMenuCommand 13 | // @grant GM_setValue 14 | // @grant GM_getValue 15 | // @require https://unpkg.com/sweetalert2@11/dist/sweetalert2.min.js 16 | // @resource Swal https://unpkg.com/sweetalert2@11/dist/sweetalert2.min.css 17 | // @resource SwalDark https://unpkg.com/@sweetalert2/theme-dark@5/dark.min.css 18 | // @license MIT 19 | // @downloadURL https://update.greasyfork.org/scripts/512300/M3U8%20Filter%20Ad%20Script.user.js 20 | // @updateURL https://update.greasyfork.org/scripts/512300/M3U8%20Filter%20Ad%20Script.meta.js 21 | // ==/UserScript== 22 | 23 | (function() { 24 | 'use strict'; 25 | 26 | let ts_name_len = 0; // ts前缀长度 27 | 28 | let ts_name_len_extend = 1; // 容错 29 | 30 | let first_extinf_row = ''; 31 | 32 | let the_extinf_judge_row_n = 0; 33 | 34 | let the_same_extinf_name_n = 0; 35 | 36 | let the_extinf_benchmark_n = 5; // 基准 37 | 38 | let prev_ts_name_index = -1; // 上个ts序列号 39 | 40 | let first_ts_name_index = -1; // 首个ts序列号 41 | 42 | let ts_type = 0; // 0:xxxx000数字递增.ts模式0 ;1:xxxxxxxxxx.ts模式1 ;2:***.ts模式2-暴力拆解 43 | 44 | let the_ext_x_mode = 0; // 0:ext_x_discontinuity判断模式0 ;1:ext_x_discontinuity判断模式1 45 | 46 | let the_current_host = unsafeWindow.location.hostname; 47 | 48 | let script_whitelist_mode_flag = false; // 是否启用白名单模式,默认否,默认是匹配所有的网站 49 | 50 | let the_current_host_in_whitelist_flag = false; // 当前域名是否在白名单,默认否 51 | 52 | let show_toast_tip_flag = false; // 是否启用弹窗提示,默认否 53 | 54 | let violent_filter_mode_flag = false; // 是否启用暴力拆解模式,默认否-自动判断模式 55 | 56 | let filter_log_html = ''; 57 | 58 | let filter_done_flag = false; 59 | 60 | function filter_log(...msg) { 61 | 62 | const log_content = msg.join('
'); 63 | 64 | console.log('%c[m3u8_filter_ad]', 'font-weight: bold; color: white; background-color: #70b566b0; padding: 2px; border-radius: 2px;', ...msg); 65 | 66 | filter_log_html += ` 67 |
68 | [m3u8_filter_ad] 69 | ${log_content} 70 |
71 | `; 72 | 73 | } 74 | 75 | let the_swalcss_color = "#ff679a"; 76 | 77 | let swalcss = ` 78 | .swal2-styled{transition: all 0.2s ease;} 79 | .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 } 80 | .swal2-styled.swal2-confirm{border:0;border-radius:.25em;background:initial;background-color:${the_swalcss_color};color:#fff;font-size:1em} 81 | .swal2-styled.swal2-confirm:hover,.swal2-styled.swal2-deny:hover{opacity:0.8;background-image:none!important} 82 | .swal2-styled.swal2-confirm:focus{box-shadow:0 0 0 3px ${the_swalcss_color}80} 83 | .swal2-styled.swal2-deny:focus{box-shadow:0 0 0 3px #dc374180} 84 | .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} 85 | .swal2-timer-progress-bar{width:100%;height:.25em;background:${the_swalcss_color}33 } 86 | .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} 87 | .swal2-progress-steps .swal2-progress-step.swal2-active-progress-step{background:${the_swalcss_color} } 88 | .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}} 89 | .swal2-popup {padding:1.25em 0 1.25em;flex-direction:column} 90 | .swal2-close {position:absolute;top:1px;right:1px;transition: all 0.2s ease;} 91 | div:where(.swal2-container) .swal2-html-container{padding: 1.3em 1.3em 0.3em;} 92 | div:where(.swal2-container) button:where(.swal2-close):hover {color:${the_swalcss_color}!important;font-size:60px!important} 93 | div:where(.swal2-icon) .swal2-icon-content {font-family: sans-serif;} 94 | .swal2-container {z-index: 1145141919810;} 95 | `; 96 | 97 | // 动态添加样式 98 | function add_style(id, css) { 99 | 100 | let try_add_style_n = 0; 101 | 102 | let try_to_add_style = function() { 103 | let the_style_dom = unsafeWindow.document.getElementById(id); 104 | if (the_style_dom) the_style_dom.remove(); 105 | 106 | let the_style = unsafeWindow.document.createElement('style'); 107 | the_style.rel = 'stylesheet'; 108 | the_style.id = id; 109 | the_style.innerHTML = css; 110 | 111 | let the_target_element = unsafeWindow.document.body; 112 | if (the_target_element) { 113 | the_target_element.insertBefore(the_style, the_target_element.firstChild); 114 | } else { 115 | try_add_style_n++; 116 | if (try_add_style_n < 50) { 117 | setTimeout(try_to_add_style, 100); 118 | } 119 | } 120 | }; 121 | 122 | try_to_add_style(); 123 | } 124 | 125 | // 先监听颜色方案变化 126 | unsafeWindow.matchMedia('(prefers-color-scheme: dark)').addListener(function (e) { 127 | if (e.matches) { 128 | // 切换到暗色主题 129 | add_style('swal-pub-style', GM_getResourceText('SwalDark')); 130 | } else { 131 | // 切换到浅色主题 132 | add_style('swal-pub-style', GM_getResourceText('Swal')); 133 | } 134 | 135 | add_style('Panlinker-SweetAlert2-User', swalcss); 136 | }); 137 | 138 | // 再修改主题 139 | if (unsafeWindow.matchMedia && unsafeWindow.matchMedia('(prefers-color-scheme: dark)').matches) { 140 | // 切换到暗色主题 141 | add_style('swal-pub-style', GM_getResourceText('SwalDark')); 142 | } else { 143 | // 切换到浅色主题 144 | add_style('swal-pub-style', GM_getResourceText('Swal')); 145 | } 146 | 147 | add_style('Panlinker-SweetAlert2-User', swalcss); 148 | 149 | // Toast 提示配置 150 | let toast = Swal.mixin({ 151 | toast: true, 152 | position: 'top-end', 153 | showConfirmButton: false, 154 | timer: 3000, 155 | timerProgressBar: true, 156 | showCloseButton: true, 157 | didOpen: function (toast) { 158 | toast.addEventListener('mouseenter', Swal.stopTimer); 159 | toast.addEventListener('mouseleave', Swal.resumeTimer); 160 | } 161 | }); 162 | 163 | // Toast 简易调用 164 | let message = { 165 | success: function (text) { 166 | toast.fire({ title: text, icon: 'success' }); 167 | }, 168 | error: function (text) { 169 | toast.fire({ title: text, icon: 'error' }); 170 | }, 171 | warning: function (text) { 172 | toast.fire({ title: text, icon: 'warning' }); 173 | }, 174 | info: function (text) { 175 | toast.fire({ title: text, icon: 'info' }); 176 | }, 177 | question: function (text) { 178 | toast.fire({ title: text, icon: 'question' }); 179 | } 180 | }; 181 | 182 | function is_m3u8_file(url) { 183 | return /\.m3u8($|\?)/.test(url); 184 | } 185 | 186 | function extract_number_before_ts(str) { 187 | // 匹配 .ts 前面的数字 188 | const match = str.match(/(\d+)\.ts/); 189 | 190 | if (match) { 191 | // 使用 parseInt 去掉前导 0 192 | return parseInt(match[1], 10); 193 | } 194 | 195 | return null; // 如果不匹配,返回 null 196 | } 197 | 198 | function filter_lines(lines) { 199 | let result = []; 200 | 201 | if (violent_filter_mode_flag) { 202 | filter_log('----------------------------暴力拆解模式--------------------------'); 203 | 204 | ts_type = 2; // ts命名模式 205 | } else { 206 | filter_log('----------------------------自动判断模式--------------------------'); 207 | 208 | let the_normal_int_ts_n = 0; 209 | let the_diff_int_ts_n = 0; 210 | 211 | let last_ts_name_len = 0; 212 | 213 | // 初始化参数 214 | for (let i = 0; i < lines.length; i++) { 215 | 216 | const line = lines[i]; 217 | 218 | // 初始化first_extinf_row 219 | if (the_extinf_judge_row_n === 0 && line.startsWith('#EXTINF')) { 220 | first_extinf_row = line; 221 | 222 | the_extinf_judge_row_n++; 223 | } else if (the_extinf_judge_row_n === 1 && line.startsWith('#EXTINF')) { 224 | if (line !== first_extinf_row) { 225 | first_extinf_row = ''; 226 | } 227 | 228 | the_extinf_judge_row_n++; 229 | } 230 | 231 | // 判断ts模式 232 | let the_ts_name_len = line.indexOf('.ts'); // ts前缀长度 233 | 234 | if (the_ts_name_len > 0) { 235 | 236 | if (the_extinf_judge_row_n === 1) { 237 | ts_name_len = the_ts_name_len; 238 | } 239 | 240 | last_ts_name_len = the_ts_name_len; 241 | 242 | let ts_name_index = extract_number_before_ts(line); 243 | if (ts_name_index === null) { 244 | if (the_extinf_judge_row_n === 1) { 245 | ts_type = 1; // ts命名模式 246 | } else if (the_extinf_judge_row_n === 2 && (ts_type === 1 || the_ts_name_len === ts_name_len)) { 247 | ts_type = 1; // ts命名模式 248 | 249 | filter_log('----------------------------识别ts模式1---------------------------'); 250 | 251 | break; 252 | } else { 253 | the_diff_int_ts_n++; 254 | } 255 | } else { 256 | 257 | // 如果序号相隔等于1: 模式0 258 | // 如果序号相隔大于1,或其他:模式2(暴力拆解) 259 | 260 | if (the_normal_int_ts_n === 0) { 261 | // 初始化ts序列号 262 | prev_ts_name_index = ts_name_index; 263 | first_ts_name_index = ts_name_index; 264 | prev_ts_name_index = first_ts_name_index - 1; 265 | } 266 | 267 | if (the_ts_name_len !== ts_name_len) { 268 | 269 | if (the_ts_name_len === last_ts_name_len + 1 && ts_name_index === prev_ts_name_index + 1) { 270 | 271 | if (the_diff_int_ts_n) { 272 | 273 | if (ts_name_index === prev_ts_name_index + 1) { 274 | ts_type = 0; // ts命名模式 275 | prev_ts_name_index = first_ts_name_index - 1; 276 | 277 | filter_log('----------------------------识别ts模式0---------------------------') 278 | 279 | break; 280 | } else { 281 | ts_type = 2; // ts命名模式 282 | 283 | filter_log('----------------------------识别ts模式2---------------------------') 284 | 285 | break; 286 | } 287 | } 288 | 289 | the_normal_int_ts_n++; 290 | prev_ts_name_index = ts_name_index; 291 | 292 | } else { 293 | the_diff_int_ts_n++; 294 | } 295 | } else { 296 | 297 | if (the_diff_int_ts_n) { 298 | 299 | if (ts_name_index === prev_ts_name_index + 1) { 300 | ts_type = 0; // ts命名模式 301 | prev_ts_name_index = first_ts_name_index - 1; 302 | 303 | filter_log('----------------------------识别ts模式0---------------------------') 304 | 305 | break; 306 | } else { 307 | ts_type = 2; // ts命名模式 308 | 309 | filter_log('----------------------------识别ts模式2---------------------------') 310 | 311 | break; 312 | } 313 | } 314 | 315 | the_normal_int_ts_n++; 316 | prev_ts_name_index = ts_name_index; 317 | } 318 | } 319 | } 320 | 321 | if (i === lines.length - 1) { 322 | // 后缀不是ts,而是jpeg等等,或者以上规则判断不了的,或者没有广告切片的:直接暴力拆解过滤 323 | 324 | ts_type = 2; // ts命名模式 325 | 326 | filter_log('----------------------------进入暴力拆解模式---------------------------') 327 | } 328 | } 329 | } 330 | 331 | // 开始遍历过滤 332 | for (let i = 0; i < lines.length; i++) { 333 | 334 | let ts_index_check = false; 335 | 336 | const line = lines[i]; 337 | 338 | if (ts_type === 0) { 339 | 340 | if (line.startsWith('#EXT-X-DISCONTINUITY') && lines[i + 1] && lines[i + 2]) { 341 | 342 | // 检查当前行是否跟 #EXT-X-相关 343 | if (i > 0 && lines[i - 1].startsWith('#EXT-X-')) { 344 | result.push(line); 345 | 346 | continue; 347 | } else { 348 | let the_ts_name_len = lines[i + 2].indexOf('.ts'); // ts前缀长度 349 | 350 | if (the_ts_name_len > 0) { 351 | 352 | // 根据ts名字长度过滤 353 | if (the_ts_name_len - ts_name_len > ts_name_len_extend) { 354 | // 广告过滤 355 | if (lines[i + 3] && lines[i + 3].startsWith('#EXT-X-DISCONTINUITY')) { 356 | // 打印即将过滤的行 357 | filter_log('过滤规则: #EXT-X-DISCONTINUITY-ts文件名长度-'); 358 | filter_log('过滤的行:', "\n", line, "\n", lines[i + 1], "\n", lines[i + 2], "\n", lines[i + 3]); 359 | filter_log('------------------------------------------------------------------'); 360 | 361 | i += 3; 362 | } else { 363 | // 打印即将过滤的行 364 | filter_log('过滤规则: #EXT-X-DISCONTINUITY-ts文件名长度'); 365 | filter_log('过滤的行:', "\n", line, "\n", lines[i + 1], "\n", lines[i + 2]); 366 | filter_log('------------------------------------------------------------------'); 367 | 368 | i += 2; 369 | } 370 | 371 | continue; 372 | } else { 373 | ts_name_len = the_ts_name_len; 374 | } 375 | 376 | // 根据ts序列号过滤 377 | let the_ts_name_index = extract_number_before_ts(lines[i + 2]); 378 | 379 | if (the_ts_name_index !== prev_ts_name_index + 1) { 380 | 381 | // 广告过滤 382 | if (lines[i + 3] && lines[i + 3].startsWith('#EXT-X-DISCONTINUITY')) { 383 | // 打印即将过滤的行 384 | filter_log('过滤规则: #EXT-X-DISCONTINUITY-ts序列号-'); 385 | filter_log('过滤的行:', "\n", line, "\n", lines[i + 1], "\n", lines[i + 2], "\n", lines[i + 3]); 386 | filter_log('------------------------------------------------------------------'); 387 | 388 | i += 3; 389 | } else { 390 | // 打印即将过滤的行 391 | filter_log('过滤规则: #EXT-X-DISCONTINUITY-ts序列号'); 392 | filter_log('过滤的行:', "\n", line, "\n", lines[i + 1], "\n", lines[i + 2]); 393 | filter_log('------------------------------------------------------------------'); 394 | 395 | i += 2; 396 | } 397 | 398 | continue; 399 | } 400 | } 401 | } 402 | } 403 | 404 | if (line.startsWith('#EXTINF') && lines[i + 1]) { 405 | 406 | let the_ts_name_len = lines[i + 1].indexOf('.ts'); // ts前缀长度 407 | 408 | if (the_ts_name_len > 0) { 409 | 410 | // 根据ts名字长度过滤 411 | if (the_ts_name_len - ts_name_len > ts_name_len_extend) { 412 | // 广告过滤 413 | if (lines[i + 2] && lines[i + 2].startsWith('#EXT-X-DISCONTINUITY')) { 414 | // 打印即将过滤的行 415 | filter_log('过滤规则: #EXTINF-ts文件名长度-'); 416 | filter_log('过滤的行:', "\n", line, "\n", lines[i + 1], "\n", lines[i + 2]); 417 | filter_log('------------------------------------------------------------------'); 418 | 419 | i += 2; 420 | } else { 421 | // 打印即将过滤的行 422 | filter_log('过滤规则: #EXTINF-ts文件名长度'); 423 | filter_log('过滤的行:', "\n", line, "\n", lines[i + 1]); 424 | filter_log('------------------------------------------------------------------'); 425 | 426 | i += 1; 427 | } 428 | 429 | continue; 430 | } else { 431 | ts_name_len = the_ts_name_len; 432 | } 433 | 434 | // 根据ts序列号过滤 435 | let the_ts_name_index = extract_number_before_ts(lines[i + 1]); 436 | 437 | if (the_ts_name_index === prev_ts_name_index + 1) { 438 | 439 | prev_ts_name_index++; 440 | 441 | } else { 442 | // 广告过滤 443 | if (lines[i + 2].startsWith('#EXT-X-DISCONTINUITY')) { 444 | // 打印即将过滤的行 445 | filter_log('过滤规则: #EXTINF-ts序列号-'); 446 | filter_log('过滤的行:', "\n", line, "\n", lines[i + 1], "\n", lines[i + 2]); 447 | filter_log('------------------------------------------------------------------'); 448 | 449 | i += 2; 450 | } else { 451 | // 打印即将过滤的行 452 | filter_log('过滤规则: #EXTINF-ts序列号'); 453 | filter_log('过滤的行:', "\n", line, "\n", lines[i + 1]); 454 | filter_log('------------------------------------------------------------------'); 455 | 456 | i += 1; 457 | } 458 | 459 | continue; 460 | } 461 | } 462 | } 463 | } else if (ts_type === 1) { 464 | 465 | if (line.startsWith('#EXTINF')) { 466 | if (line === first_extinf_row && the_same_extinf_name_n <= the_extinf_benchmark_n && the_ext_x_mode === 0) { 467 | the_same_extinf_name_n++; 468 | } else { 469 | the_ext_x_mode = 1; 470 | } 471 | 472 | if (the_same_extinf_name_n > the_extinf_benchmark_n) { 473 | the_ext_x_mode = 1; 474 | } 475 | } 476 | 477 | if (line.startsWith('#EXT-X-DISCONTINUITY')) { 478 | // 检查当前行是否跟 #EXT-X-PLAYLIST-TYPE相关 479 | if (i > 0 && lines[i - 1].startsWith('#EXT-X-PLAYLIST-TYPE')) { 480 | result.push(line); 481 | 482 | continue; 483 | } else { 484 | 485 | // 如果第 i+2 行是 .ts 文件,跳过当前行和接下来的两行 486 | if (lines[i + 1] && lines[i + 1].startsWith('#EXTINF') && lines[i + 2] && lines[i + 2].indexOf('.ts') > 0) { 487 | 488 | let the_ext_x_discontinuity_condition_flag = false; 489 | 490 | if (the_ext_x_mode === 1) { 491 | the_ext_x_discontinuity_condition_flag = lines[i + 1] !== first_extinf_row && the_same_extinf_name_n > the_extinf_benchmark_n; 492 | } 493 | 494 | // 进一步检测第 i+3 行是否也是 #EXT-X-DISCONTINUITY 495 | if (lines[i + 3] && lines[i + 3].startsWith('#EXT-X-DISCONTINUITY') && the_ext_x_discontinuity_condition_flag) { 496 | // 打印即将过滤的行 497 | filter_log('过滤规则: #EXT-X-DISCONTINUITY-广告-#EXT-X-DISCONTINUITY过滤'); 498 | filter_log('过滤的行:', "\n", line, "\n", lines[i + 1], "\n", lines[i + 2], "\n", lines[i + 3]); 499 | filter_log('------------------------------------------------------------------'); 500 | 501 | i += 3; // 跳过当前行和接下来的三行 502 | } else { 503 | // 打印即将过滤的行 504 | filter_log('过滤规则: #EXT-X-DISCONTINUITY-单个标识过滤'); 505 | filter_log('过滤的行:', "\n", line); 506 | filter_log('------------------------------------------------------------------'); 507 | } 508 | 509 | continue; 510 | } 511 | } 512 | } 513 | } else { 514 | 515 | // 暴力拆解 516 | if (line.startsWith('#EXT-X-DISCONTINUITY')) { 517 | // 检查当前行是否跟 #EXT-X-PLAYLIST-TYPE相关 518 | if (i > 0 && lines[i - 1].startsWith('#EXT-X-PLAYLIST-TYPE')) { 519 | result.push(line); 520 | 521 | continue; 522 | } else { 523 | 524 | // 打印即将过滤的行 525 | filter_log('过滤规则: #EXT-X-DISCONTINUITY-单个标识过滤'); 526 | filter_log('过滤的行:', "\n", line); 527 | filter_log('------------------------------------------------------------------'); 528 | 529 | continue; 530 | } 531 | } 532 | } 533 | 534 | // 保留不需要过滤的行 535 | result.push(line); 536 | } 537 | 538 | return result; 539 | } 540 | 541 | async function safely_process_m3u8(url, content) { 542 | try { 543 | const lines = content.split('\n'); 544 | const new_ines = filter_lines(lines); 545 | 546 | return new_ines.join('\n'); 547 | } catch (e) { 548 | filter_log(`处理 m3u8 文件时出错: ${url}`, e); 549 | 550 | return content; 551 | } 552 | } 553 | 554 | // 脚本菜单变量 555 | let menu_item_violent = null; 556 | let menu_item_mode = null; 557 | let menu_item_host_join = null; 558 | let menu_item_toast = null; 559 | let menu_item_filter_tip = null; 560 | 561 | function hookXHR() { 562 | const OriginalXHR = unsafeWindow.XMLHttpRequest; 563 | unsafeWindow.XMLHttpRequest = class extends OriginalXHR { 564 | constructor() { 565 | super(); 566 | 567 | this.addEventListener('readystatechange', async function () { 568 | 569 | if (this.readyState === 4 && this.status === 200 && is_m3u8_file(this.responseURL)) { 570 | 571 | filter_log('----------------------------hookXHR成功---------------------------'); 572 | 573 | const modifiedResponse = await safely_process_m3u8(this.responseURL, this.responseText); 574 | Object.defineProperty(this, 'responseText', { value: modifiedResponse }); 575 | Object.defineProperty(this, 'response', { value: modifiedResponse }); 576 | 577 | if (show_toast_tip_flag) { 578 | message.success('已成功过滤切片广告'); 579 | } 580 | 581 | filter_done_flag = true; 582 | GM_unregisterMenuCommand(menu_item_filter_tip); 583 | check_menu_item_filter_tip(); 584 | } 585 | }, false); 586 | } 587 | }; 588 | } 589 | 590 | function initHook() { 591 | hookXHR(); 592 | } 593 | 594 | // 初始化菜单判断变量 595 | violent_filter_mode_flag = GM_getValue('violent_filter_mode_flag', false); 596 | script_whitelist_mode_flag = GM_getValue('script_whitelist_mode_flag', false); 597 | the_current_host_in_whitelist_flag = GM_getValue(the_current_host, false); 598 | show_toast_tip_flag = GM_getValue('show_toast_tip_flag', false); 599 | 600 | function check_menu_item_violent() { 601 | if (violent_filter_mode_flag) { 602 | menu_item_violent = GM_registerMenuCommand('暴力拆解模式(可点击切换到自动判断过滤模式)', function() { 603 | 604 | GM_setValue('violent_filter_mode_flag', false); 605 | 606 | violent_filter_mode_flag = false; 607 | 608 | message.success('已设置: