Greasy Fork

Greasy Fork is available in English.

Remove Ad from M3U8

拦截m3u8请求并移除其中插入的广告ts片段

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Remove Ad from M3U8
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  拦截m3u8请求并移除其中插入的广告ts片段
// @author       
// @match        *://*/*
// @grant        none
// ==/UserScript==



(function() {
    'use strict';
    let processingM3U8 = false;

    async function fixAdM3u8Ai(m3u8_url, headers = null){
              let ts = new Date().getTime();
              let option = headers ? {headers: headers} : {};

              function b(s1, s2) {
                let i = 0;
                while (i < s1.length) {
                  if (s1[i] !== s2[i]) {
                    break
                  }
                  i++
                }
                return i;
              }

              function reverseString(str) {
                return str.split('').reverse().join('');
              }

              //log('播放的地址:' + m3u8_url);
              const m3u8_response = await fetch(m3u8_url, option);
              let m3u8 =await m3u8_response.text();
              //log('m3u8处理前:' + m3u8);
              m3u8 = m3u8.trim().split('\n').map(it => it.startsWith('#') ? it : urljoin(m3u8_url, it)).join('\n');
              //log('m3u8处理后:============:' + m3u8);
              // 获取嵌套m3u8地址
              m3u8 = m3u8.replace(/\n\n/gi, '\n'); //删除多余的换行符
              let last_url = m3u8.split('\n').slice(-1)[0];
              if (last_url.length < 5) {
                last_url = m3u8.split('\n').slice(-2)[0];
              }

              if (last_url.includes('.m3u8') && last_url !== m3u8_url) {
                m3u8_url = urljoin(m3u8_url, last_url);
                // console.log('嵌套的m3u8_url:' + m3u8_url);
                const m3u8_nest_response = await fetch(m3u8_url, option);
                m3u8 = await m3u8_nest_response.text();
              }
              //log('----处理有广告的地址----');
              let s = m3u8.trim().split('\n').filter(it => it.trim()).join('\n');
              let ss = s.split('\n');
              //找出第一条播放地址
              //let firststr = ss.find(x => !x.startsWith('#'));
              let firststr = '';
              let maxl = 0;//最大相同字符
              let kk = 0;
              let kkk = 2;
              let secondstr = '';
              for (let i = 0; i < ss.length; i++) {
                let s = ss[i];
                if (!s.startsWith("#")) {
                  if (kk == 0) firststr = s;
                  if (kk == 1) maxl = b(firststr, s);
                  if (kk > 1) {
                    if (maxl > b(firststr, s)) {
                      if (secondstr.length < 5) secondstr = s;
                      kkk = kkk + 2;
                    } else {
                      maxl = b(firststr, s);
                      kkk++;
                    }
                  }
                  kk++;
                  if (kk >= 20) break;
                }
              }
              if (kkk > 30) firststr = secondstr;
              let firststrlen = firststr != null ? firststr.length : null
              //log('字符串长度:' + firststrlen);
              let ml = Math.round(ss.length / 2).toString().length; //取数据的长度的位数
              //log('数据条数的长度:' + ml);
              //找出最后一条播放地址
              let maxc = 0;
              let laststr = ss.toReversed().find((x) => {
                if (!x.startsWith('#')) {
                  let k = b(reverseString(firststr), reverseString(x));
                  maxl = b(firststr, x);
                  maxc++;
                  if (firststrlen - maxl <= ml + k || maxc > 10) {
                    return true;
                  }
                }
                return false;
              });
              // console.log('最后一条切片:' + laststr);
              //log('最小相同字符长度:' + maxl);
              let ad_urls = [];
              for (let i = 0; i < ss.length; i++) {
                let s = ss[i];
                if (!s.startsWith('#')) {
                  if (b(firststr, s) < maxl) {
                    ad_urls.push(s); // 广告地址加入列表
                    ss.splice(i - 1, 2);
                    i = i - 2;
                  } else {
                    ss[i] = urljoin(m3u8_url, s);
                  }
                } else {
                  ss[i] = s.replace(/URI=\"(.*)\"/, 'URI="' + urljoin(m3u8_url, '$1') + '"');
                }
              }
              // console.log('处理的m3u8地址:' + m3u8_url);
              // console.log('----广告地址----');
               console.log(ad_urls);
              m3u8 = ss.join('\n');
              //log('处理完成');
              //console.log('处理耗时:' + (new Date().getTime() - ts).toString());
              return m3u8;
     }


     function resolve (from, to){
      const resolvedUrl = new URL(to, new URL(from, 'resolve://'));
      if (resolvedUrl.protocol === 'resolve:') {
        const { pathname, search, hash } = resolvedUrl;
        return pathname + search + hash;
      }
      return resolvedUrl.href;
    };


     function urljoin (fromPath, nowPath){
       fromPath = fromPath || '';
       nowPath = nowPath || '';
       return resolve(fromPath, nowPath);
    };

    function playm3u8(urlm3,playerElement) {
        if (Hls.isSupported()) {

            var hls = new Hls();
            hls.loadSource(urlm3);
            hls.attachMedia(playerElement);
            //video.play();
            hls.on(Hls.Events.MANIFEST_PARSED, function () {
                playerElement.play();
            });
            hls.on(Hls.Events.ERROR, (event, data) => {
                //console.log("1222");
                console.log(event, data);
                // 监听出错事件
            });
        }
    }


    function replacePlayerSource(playerElement, newSource) {

        // 假设播放器使用 <video> 或类似标签

        if (playerElement) {
            playm3u8(newSource,playerElement)

        } else {

            console.warn('Player element not found.');

        }

    }

    const originalXhrOpen = XMLHttpRequest.prototype.open;

    XMLHttpRequest.prototype.open = function(method, url) {

        if (url.endsWith('.m3u8')) {
            this.addEventListener('load', async function() {

                if (!processingM3U8 && (this.responseType === '' || this.responseType === 'text')) {
                    processingM3U8 = true;
                    const m3u8_content = await fixAdM3u8Ai(url);
                    processingM3U8 = false;

                    // 创建 Blob 并生成 URL

                    const blob = new Blob([m3u8_content], { type: 'text/plain' });
                    const newM3u8Url = URL.createObjectURL(blob);
                    // 替换播放器源
                    const player = document.querySelector('video'); // 根据实际情况调整选择器
                    replacePlayerSource(player,newM3u8Url);

                }

            });

        }

        originalXhrOpen.apply(this, arguments);

    };



})();