M3U8 Filter Ad Script

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

Устаревшая версия за 23.10.2024. Перейдите к последней версии.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey, Greasemonkey или Violentmonkey.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey или Violentmonkey.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey или Violentmonkey.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey или Userscripts.

Чтобы установить этот скрипт, сначала вы должны установить расширение браузера, например Tampermonkey.

Чтобы установить этот скрипт, вы должны установить расширение — менеджер скриптов.

(у меня уже есть менеджер скриптов, дайте мне установить скрипт!)

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

(у меня уже есть менеджер стилей, дайте мне установить скрипт!)

// ==UserScript==
// @name         M3U8 Filter Ad Script
// @namespace    http://tampermonkey.net/
// @version      1.0.3
// @description  自用,拦截和过滤 m3u8(解析/采集资源) 的广告切片,同时在console打印过滤的行信息,不会误删。
// @author       ltxlong
// @match        *://*/*
// @grant        unsafeWindow
// @run-at       document-start
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    let ts_name_len = 0; // ts前缀长度

    let ts_name_len_extend = 1; // 容错

    let first_extinf_row = '';

    let the_extinf_judge_row_n = 0;

    let the_same_extinf_name_n = 0;

    let the_extinf_benchmark_n = 5; // 基准

    let prev_ts_name_index = -1; // ts序列号

    let ts_type = 0; // 0:xxxx000数字递增.ts模式0 ;1:xxxxxxxxxx.ts模式1

    let the_ext_x_mode = 0; // 0:ext_x_discontinuity判断模式0 ;1:ext_x_discontinuity判断模式1

    function isM3U8File(url) {
        return /\.m3u8($|\?)/.test(url);
    }

    function extractNumberBeforeTS(str) {
        // 匹配 .ts 前面的数字
        const match = str.match(/(\d+)\.ts/);

        if (match) {
            // 使用 parseInt 去掉前导 0
            return parseInt(match[1], 10);
        }

        return null; // 如果不匹配,返回 null
    }

    function filterLines(lines) {
        let result = [];

        // 先根据第一、二个ts名称来初始化参数
        for (let i = 0; i < lines.length; i++) {

            const line = lines[i];

            if (the_extinf_judge_row_n === 0 && line.startsWith('#EXTINF')) {
                first_extinf_row = line;

                the_extinf_judge_row_n++;
            } else if (the_extinf_judge_row_n === 1 && line.startsWith('#EXTINF')) {
                if (line !== first_extinf_row) {
                    first_extinf_row = '';
                }

                the_extinf_judge_row_n++;
            }

            let the_ts_name_len = line.indexOf('.ts'); // ts前缀长度

            if (the_ts_name_len > 0) {

                ts_name_len = the_ts_name_len;

                let ts_name_index = extractNumberBeforeTS(line);
                if (ts_name_index === null) {
                    ts_type = 1; // ts命名模式

                    console.log('----------------------------识别ts模式1---------------------------');

                    break;
                } else {

                    if (the_extinf_judge_row_n === 1) {

                        prev_ts_name_index = ts_name_index; // ts序列号

                    } else if (the_extinf_judge_row_n === 2) {

                        if (ts_name_index !== prev_ts_name_index + 1) {
                            ts_type = 1; // ts命名模式

                            console.log('----------------------------识别ts模式1---------------------------');

                            break;
                        } else {
                            prev_ts_name_index--;
                        }
                    }
                }

                if (the_extinf_judge_row_n === 2) {
                    console.log('----------------------------识别ts模式0---------------------------')

                    break;
                }
            }

        }

        // 开始遍历过滤
        for (let i = 0; i < lines.length; i++) {

            let ts_index_check = false;

            const line = lines[i];

            if (ts_type === 0) {

                if (line.startsWith('#EXT-X-DISCONTINUITY') && lines[i + 1] && lines[i + 2]) {

                    // 检查当前行是否跟 #EXT-X-PLAYLIST-TYPE相关
                    if (i > 0 && lines[i - 1].startsWith('#EXT-X-PLAYLIST-TYPE')) {
                        result.push(line);

                        continue;
                    } else {
                        let the_ts_name_len = lines[i + 2].indexOf('.ts'); // ts前缀长度

                        if (the_ts_name_len > 0) {

                            // 根据ts名字长度过滤
                            if (the_ts_name_len - ts_name_len > ts_name_len_extend) {
                                // 广告过滤
                                if (lines[i + 3] && lines[i + 3].startsWith('#EXT-X-DISCONTINUITY')) {
                                    // 打印即将过滤的行
                                    console.log('------------------------------------------------------------------');
                                    console.log('过滤规则: #EXT-X-DISCONTINUITY-ts文件名长度-');
                                    console.log('过滤的行:', "\n", line, "\n", lines[i + 1], "\n", lines[i + 2], "\n", lines[i + 3]);
                                    console.log('------------------------------------------------------------------');

                                    i += 3;
                                } else {
                                    // 打印即将过滤的行
                                    console.log('------------------------------------------------------------------');
                                    console.log('过滤规则: #EXT-X-DISCONTINUITY-ts文件名长度');
                                    console.log('过滤的行:', "\n", line, "\n", lines[i + 1], "\n", lines[i + 2]);
                                    console.log('------------------------------------------------------------------');

                                    i += 2;
                                }

                                continue;
                            } else {
                                ts_name_len = the_ts_name_len;
                            }

                            // 根据ts序列号过滤
                            let the_ts_name_index = extractNumberBeforeTS(lines[i + 2]);

                            if (the_ts_name_index !== prev_ts_name_index + 1) {

                                // 广告过滤
                                if (lines[i + 3] && lines[i + 3].startsWith('#EXT-X-DISCONTINUITY')) {
                                    // 打印即将过滤的行
                                    console.log('------------------------------------------------------------------');
                                    console.log('过滤规则: #EXT-X-DISCONTINUITY-ts序列号-');
                                    console.log('过滤的行:', "\n", line, "\n", lines[i + 1], "\n", lines[i + 2], "\n", lines[i + 3]);
                                    console.log('------------------------------------------------------------------');

                                    i += 3;
                                } else {
                                    // 打印即将过滤的行
                                    console.log('------------------------------------------------------------------');
                                    console.log('过滤规则: #EXT-X-DISCONTINUITY-ts序列号');
                                    console.log('过滤的行:', "\n", line, "\n", lines[i + 1], "\n", lines[i + 2]);
                                    console.log('------------------------------------------------------------------');

                                    i += 2;
                                }

                                continue;
                            }
                        }
                    }
                }

                if (line.startsWith('#EXTINF') && lines[i + 1]) {

                    let the_ts_name_len = lines[i + 1].indexOf('.ts'); // ts前缀长度

                    if (the_ts_name_len > 0) {

                        // 根据ts名字长度过滤
                        if (the_ts_name_len - ts_name_len > ts_name_len_extend) {
                            // 广告过滤
                            if (lines[i + 2] && lines[i + 2].startsWith('#EXT-X-DISCONTINUITY')) {
                                // 打印即将过滤的行
                                console.log('------------------------------------------------------------------');
                                console.log('过滤规则: #EXTINF-ts文件名长度-');
                                console.log('过滤的行:', "\n", line, "\n", lines[i + 1], "\n", lines[i + 2]);
                                console.log('------------------------------------------------------------------');

                                i += 2;
                            } else {
                                // 打印即将过滤的行
                                console.log('------------------------------------------------------------------');
                                console.log('过滤规则: #EXTINF-ts文件名长度');
                                console.log('过滤的行:', "\n", line, "\n", lines[i + 1]);
                                console.log('------------------------------------------------------------------');

                                i += 1;
                            }

                            continue;
                        } else {
                            ts_name_len = the_ts_name_len;
                        }

                        // 根据ts序列号过滤
                        let the_ts_name_index = extractNumberBeforeTS(lines[i + 1]);

                        if (the_ts_name_index === prev_ts_name_index + 1) {

                            prev_ts_name_index++;

                        } else {
                            // 广告过滤
                            if (lines[i + 2].startsWith('#EXT-X-DISCONTINUITY')) {
                                // 打印即将过滤的行
                                console.log('------------------------------------------------------------------');
                                console.log('过滤规则: #EXTINF-ts序列号-');
                                console.log('过滤的行:', "\n", line, "\n", lines[i + 1], "\n", lines[i + 2]);
                                console.log('------------------------------------------------------------------');

                                i += 2;
                            } else {
                                // 打印即将过滤的行
                                console.log('------------------------------------------------------------------');
                                console.log('过滤规则: #EXTINF-ts序列号');
                                console.log('过滤的行:', "\n", line, "\n", lines[i + 1]);
                                console.log('------------------------------------------------------------------');

                                i += 1;
                            }

                            continue;
                        }
                    }
                }
            } else {

                if (line.startsWith('#EXTINF')) {
                    if (line === first_extinf_row && the_same_extinf_name_n <= the_extinf_benchmark_n && the_ext_x_mode === 0) {
                        the_same_extinf_name_n++;
                    } else {
                        the_ext_x_mode = 1;
                    }

                    if (the_same_extinf_name_n > the_extinf_benchmark_n) {
                        the_ext_x_mode = 1;
                    }
                }

                if (line.startsWith('#EXT-X-DISCONTINUITY')) {
                    // 检查当前行是否跟 #EXT-X-PLAYLIST-TYPE相关
                    if (i > 0 && lines[i - 1].startsWith('#EXT-X-PLAYLIST-TYPE')) {
                        result.push(line);

                        continue;
                    } else {

                        // 如果第 i+2 行是 .ts 文件,跳过当前行和接下来的两行
                        if (lines[i + 1] && lines[i + 1].startsWith('#EXTINF') && lines[i + 2] && lines[i + 2].indexOf('.ts') > 0) {

                            let the_ext_x_discontinuity_condition_flag = false;

                            if (the_ext_x_mode === 1) {
                                the_ext_x_discontinuity_condition_flag = lines[i + 1] !== first_extinf_row && the_same_extinf_name_n > the_extinf_benchmark_n;
                            }

                            // 进一步检测第 i+3 行是否也是 #EXT-X-DISCONTINUITY
                            if (lines[i + 3] && lines[i + 3].startsWith('#EXT-X-DISCONTINUITY') && the_ext_x_discontinuity_condition_flag) {
                                // 打印即将过滤的行
                                console.log('------------------------------------------------------------------');
                                console.log('过滤规则: #EXT-X-DISCONTINUITY-广告-#EXT-X-DISCONTINUITY过滤');
                                console.log('过滤的行:', "\n", line, "\n", lines[i + 1], "\n", lines[i + 2], "\n", lines[i + 3]);
                                console.log('------------------------------------------------------------------');

                                i += 3; // 跳过当前行和接下来的三行
                            } else {
                                // 打印即将过滤的行
                                console.log('------------------------------------------------------------------');
                                console.log('过滤规则: #EXT-X-DISCONTINUITY-单个标识过滤');
                                console.log('过滤的行:', "\n", line);
                                console.log('------------------------------------------------------------------');
                            }

                            continue;
                        }
                    }
                }
            }

            // 保留不需要过滤的行
            result.push(line);
        }

        return result;
    }
    
    async function safelyProcessM3U8(url, content) {
        try {
            const lines = content.split('\n');
            const newLines = filterLines(lines);

            return newLines.join('\n');
        } catch (e) {
            console.error(`处理 m3u8 文件时出错: ${url}`, e);
            
            return content;
        }
    }

    function hookXHR() {
        const OriginalXHR = unsafeWindow.XMLHttpRequest;
        unsafeWindow.XMLHttpRequest = class extends OriginalXHR {
            constructor() {
                super();

                this.addEventListener('readystatechange', async function () {
                    if (this.readyState === 4 && this.status === 200 && isM3U8File(this.responseURL)) {
                        const modifiedResponse = await safelyProcessM3U8(this.responseURL, this.responseText);
                        Object.defineProperty(this, 'responseText', { value: modifiedResponse });
                        Object.defineProperty(this, 'response', { value: modifiedResponse });
                    }
                }, false);
            }
        };
    }
    
    function initHook() {
        hookXHR();
    }

    initHook();

})();