码峰博客 – 码而思

分享积累从此时此刻开始

篡改猴获取ts视频网站中的.m3u8和加密key

网易云课堂

登录网易云课堂,打开视频页面。视频业的链接以 https://study.163.com/ 开头

1.安装 篡改猴篡改猴下载看这里

2.添加新脚本

3. 输入如下内容

// ==UserScript==
// @name         有道精品课|网易云课堂 - ts下载
// @namespace    http://tampermonkey.net/
// @version      2025-09-14
// @description  try to take over the world!
// @author       You
// @match        https://live.youdao.com/*
// @match        https://study.163.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=deepseek.com
// @grant        none
// ==/UserScript==


(function() {
    'use strict';




        // 交互界面 =======================================================================
        function createStyles() {
            const style = document.createElement('style');
            style.textContent = `

                .container {
                    background: red;
                    padding: 20px;
                    border-radius: 8px;
                    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
                    width: 100%;
                    max-width: 200px;
                    position: fixed;
                    top: 0px;
                    right: 0px;
                }

                .input-group {
                    margin-bottom: 15px;
                }

                label {
                    display: block;
                    margin-bottom: 5px;
                    font-weight: bold;
                }

                input {
                    width: 100%;
                    padding: 8px;
                    border: 1px solid #ddd;
                    border-radius: 4px;
                }


                .user-input {
                    color: #28a745;
                    font-weight: bold;
                }

                button {
                    background-color: #4CAF50;
                    color: white;
                    border: none;
                    padding: 10px 15px;
                    border-radius: 4px;
                    cursor: pointer;
                    width: 100%;
                    font-size: 16px;
                }

                button:hover {
                    background-color: #45a049;
                }
            `;
            document.head.appendChild(style);
        }

        // 创建页面结构
        function createPage() {
            // 创建容器
            const container = document.createElement('div');
            container.className = 'container';


            // 创建输入组 - 提示信息
            const messageGroup = document.createElement('div');
            messageGroup.className = 'input-group';

            const messageLabel = document.createElement('label');
            messageLabel.textContent = '课程序号:';
            messageLabel.htmlFor = 'promptMessage';

            const messageInput = document.createElement('input');
            messageInput.type = 'text';
            messageInput.id = 'promptMessage';
            messageInput.placeholder = '输入课程序号,建议使用2位';

            // 创建按钮
            const button = document.createElement('button');
            button.id = 'demoBtn';
            button.textContent = '下载';

            messageGroup.appendChild(messageLabel);
            messageGroup.appendChild(messageInput);



            container.appendChild(messageGroup);
            container.appendChild(button);

            document.body.appendChild(container);


            // 添加事件监听
            button.addEventListener('click', function() {
                window.localStorage.setItem('promptMessage', messageInput.value);

                saveM3U8(window.contextM3U8)
                saveKEY(window.contextKEY)
            });
        }



        function setIntpuValue(value){
            const messageInput = document.getElementById('promptMessage');
            messageInput.value = value;
        }

        function init() {
            // 创建样式
            createStyles();

            // 创建页面元素
            const elements = createPage();


            // 默认值
            let promptMessageValue = window.localStorage.getItem('promptMessage');
            if(!promptMessageValue){
                promptMessageValue = '01';
            }else{
                let v = parseInt(promptMessageValue)+1;
                // 不足2位,补0
                if(v<10){
                    promptMessageValue = '0'+v;
                }else{
                    promptMessageValue = v;
                }
            }
            setIntpuValue(promptMessageValue);
        }

        //document.addEventListener('DOMContentLoaded', init);
        init()





    // 下载处理 =======================================================================

    // https://jdvodluwytr3t.stu.126.net/nos/ept/hls/2024/05/20/669/638b5f66-39b6-4581-9f41-c744d35df7ae_e7-209.ts
    // https://jdvodluwytr3t.stu.126.net/nos/ept/hls/2024/05/20/669/638b5f66-39b6-4581-9f41-c744d35df7ae_e7.m3u8?..........


    window.fileName = ''// 存储文件的名称
    window.tspath = '' // https://jdvodluwytr3t.stu.126.net/nos/ept/hls/2024/05/20/669/638b5f66-39b6-4581-9f41-c744d35df7ae_e7
    window.contextM3U8 = null;
    window.contextKEY = null;






    const getFileName = function(url){
        const regex = /\/([^\/?]+\.m3u8)(?=\?|$)/;
        const match = url.match(regex);
        const fileName = match ? match[1] : null;

        const temp = url.split('.m3u8');
        const tspath = temp[0];
        window.tspath = tspath
        //console.log('tspath:', tspath);


        //window.fileName = fileName.replace('.m3u8','')
        window.fileName = document.title.replace(' - 网易云课堂','')
        //console.log('文件名称:', window.fileName);
    }


    const saveM3U8 = function(context){

        const fileName = document.getElementById('promptMessage').value +"."+ window.fileName + '.m3u8';
        //console.log('saveM3U8 文件名称:', fileName);



        // 处理ArrayBuffer响应
        try {
            // 替换 638b5f66-39b6-4581-9f41-c744d35df7ae_e7-209.ts 中的 638b5f66-39b6-4581-9f41-c744d35df7ae_e7 为 https://jdvodluwytr3t.stu.126.net/nos/ept/hls/2024/05/20/669/638b5f66-39b6-4581-9f41-c744d35df7ae_e7
            let con = context.responseText
            let temp = window.tspath.split('/');
            let regex = new RegExp(temp[temp.length - 1], 'g');
            con = con.replace(regex, window.tspath)

            const blob = new Blob([con], { type: 'application/octet-stream' });
            const url = URL.createObjectURL(blob);
            const a = document.createElement('a');
            a.href = url;
            a.download = fileName;
            document.body.appendChild(a);
            a.click();
            document.body.removeChild(a);
            URL.revokeObjectURL(url);
            console.log('二进制内容已保存:', fileName);
        } catch (e) {
            console.error('处理二进制响应时出错:', e);
        }
    }

    const saveKEY = function(context){

        const fileName = document.getElementById('promptMessage').value +"."+ window.fileName + '.key';
        //console.log('saveKEY 文件名称:', fileName);


        // 处理ArrayBuffer响应
        try {
            const blob = new Blob([context.response], { type: 'application/octet-stream' });
            const url = URL.createObjectURL(blob);
            const a = document.createElement('a');
            a.href = url;
            a.download = fileName;
            document.body.appendChild(a);
            a.click();
            document.body.removeChild(a);
            URL.revokeObjectURL(url);
            console.log('二进制内容已保存:', fileName);
        } catch (e) {
            console.error('处理二进制响应时出错:', e);
        }
    }



    // 使用正则表达式定义要匹配的URL模式
    const urlPatterns = [
       /.*?\.m3u8.*?/i ,
       /.*?\/key.*?/i ,
    ];

    // 保存原始方法
    const originalOpen = XMLHttpRequest.prototype.open;
    const originalSend = XMLHttpRequest.prototype.send;

    // 重写open方法
    XMLHttpRequest.prototype.open = function(method, url) {
        this._method = method;
        this._url = url;
        return originalOpen.apply(this, arguments);
    };

    // 重写send方法
    XMLHttpRequest.prototype.send = function(body) {
        this._requestBody = body;

        // 检查URL是否匹配任何模式
        const shouldIntercept = urlPatterns.some(pattern => pattern.test(this._url));

        if (shouldIntercept) {
            console.log('拦截到匹配的请求:', this._method, this._url);

            // 获取文件名称
            if(this._url.indexOf('.m3u8')!= -1){
               getFileName(this._url);
            }


            // 添加readystatechange事件监听器
            this.addEventListener('readystatechange', function() {
                console.log('this.readyState', this.readyState);
                if (this.readyState === 4) {
                    console.log('请求完成:', this.status, this._url);

                    if(this._url.indexOf('.m3u8')!= -1){
                        //console.log('响应内容:', this.responseText);
                        //saveM3U8(this)
                        window.contextM3U8 = this;
                    }
                    if(this._url.indexOf('/key')!= -1){
                        //console.log('响应内容:', this.response);
                        //saveKEY(this)
                        window.contextKEY = this;
                    }

                }
            });
        }

        return originalSend.apply(this, arguments);
    };
})();

4.刷新视频页面,就会提示保存 .m3u8 和 key 文件。

有道精品课

登录有道精品课,打开视频页面。视频页的链接以 https://live.youdao.com/ 开头

流程同上,篡改猴的代码也是相同的。但ts文件下载需要配置请求头。在m3u8批量下载工具中配置即可。

拦截特定URL的返回内容的通用方法

方法一:拦截XMLHttpRequest(传统AJAX请求)

// ==UserScript==
// @name         拦截特定URL的返回内容
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  拦截指定URL的响应内容
// @author       You
// @match        *://*.example.com/*  // 请修改为目标网站
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    // 要拦截的URL(可以是完整URL或部分匹配)
    const targetUrl = 'https://api.example.com/data'; // 替换为你的目标URL

    // 保存原始XMLHttpRequest.open方法
    const originalOpen = XMLHttpRequest.prototype.open;
    const originalSend = XMLHttpRequest.prototype.send;

    // 重写open方法
    XMLHttpRequest.prototype.open = function(method, url) {
        // 存储请求信息,供send方法使用
        this._requestMethod = method;
        this._requestUrl = url;

        // 调用原始open方法
        return originalOpen.apply(this, arguments);
    };

    // 重写send方法
    XMLHttpRequest.prototype.send = function(body) {
        // 存储请求体
        this._requestBody = body;

        // 如果是要拦截的URL
        if (this._requestUrl && this._requestUrl.includes(targetUrl)) {
            // 监听readystatechange事件
            this.addEventListener('readystatechange', function() {
                if (this.readyState === 4) { // 请求完成
                    console.log('拦截到请求:', this._requestUrl);
                    console.log('状态码:', this.status);
                    console.log('响应内容:', this.responseText);
                    
                    // 在这里你可以处理响应内容
                    // 例如保存到变量、修改内容等
                    
                    // 如果需要修改响应内容(注意:某些属性可能是只读的)
                    try {
                        // 创建一个可写的响应副本
                        const modifiedResponse = this.responseText;
                        // 对modifiedResponse进行处理...
                        
                        // 如果可能,尝试修改原始响应
                        // Object.defineProperty(this, 'responseText', {
                        //     value: modifiedResponse,
                        //     writable: true
                        // });
                    } catch (e) {
                        console.warn('无法修改响应:', e);
                    }
                }
            });
        }

        // 调用原始send方法
        return originalSend.apply(this, arguments);
    };
})();

方法二:拦截Fetch API(现代请求方式)

// ==UserScript==
// @name         拦截Fetch请求
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  拦截指定URL的Fetch请求
// @author       You
// @match        *://*.example.com/*  // 请修改为目标网站
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    // 要拦截的URL
    const targetUrl = 'https://api.example.com/data'; // 替换为你的目标URL

    // 保存原始fetch方法
    const originalFetch = window.fetch;

    // 重写fetch方法
    window.fetch = function() {
        const fetchArgs = arguments;
        const input = arguments[0];
        let url = '';
        
        // 提取URL
        if (typeof input === 'string') {
            url = input;
        } else if (input instanceof Request) {
            url = input.url;
        }
        
        // 如果是要拦截的URL
        if (url.includes(targetUrl)) {
            console.log('拦截到Fetch请求:', url);
            
            // 调用原始fetch并处理响应
            return originalFetch.apply(this, arguments)
                .then(response => {
                    // 克隆响应以便读取后还能继续使用
                    return response.clone().text().then(text => {
                        console.log('Fetch响应内容:', text);
                        
                        // 在这里处理响应内容
                        // 你可以修改text然后创建新的响应
                        
                        // 返回原始响应或修改后的响应
                        return response;
                    });
                })
                .catch(error => {
                    console.error('Fetch请求错误:', error);
                    throw error;
                });
        }
        
        // 对于不需要拦截的请求,直接调用原始fetch
        return originalFetch.apply(this, arguments);
    };
})();

方法三:增强版通用拦截器

// ==UserScript==
// @name         增强版请求拦截器
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  拦截并处理特定URL的请求和响应
// @author       You
// @match        *://*.example.com/*  // 请修改为目标网站
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_notification
// ==/UserScript==

(function() {
    'use strict';

    // 配置
    const config = {
        targetUrls: [
            'https://api.example.com/data',
            'https://another-api.example.com/info'
            // 添加更多需要拦截的URL
        ],
        logRequests: true,
        saveResponses: false,
        notifyOnIntercept: false
    };

    // 拦截XMLHttpRequest
    if (typeof XMLHttpRequest !== 'undefined') {
        const originalOpen = XMLHttpRequest.prototype.open;
        const originalSend = XMLHttpRequest.prototype.send;

        XMLHttpRequest.prototype.open = function() {
            this._method = arguments[0];
            this._url = arguments[1];
            return originalOpen.apply(this, arguments);
        };

        XMLHttpRequest.prototype.send = function() {
            const url = this._url;
            
            if (config.targetUrls.some(target => url.includes(target))) {
                if (config.logRequests) {
                    console.log(`拦截到XHR请求: ${this._method} ${url}`);
                }
                
                if (config.notifyOnIntercept) {
                    GM_notification({
                        text: `拦截到请求: ${url}`,
                        title: '请求拦截器',
                        timeout: 2000
                    });
                }

                this.addEventListener('readystatechange', function() {
                    if (this.readyState === 4) {
                        console.log(`XHR响应 [${this.status}]:`, url);
                        console.log('响应内容:', this.responseText);
                        
                        if (config.saveResponses) {
                            const timestamp = new Date().toISOString();
                            const key = `response_${timestamp}_${url.substring(url.lastIndexOf('/') + 1)}`;
                            GM_setValue(key, {
                                url: url,
                                status: this.status,
                                response: this.responseText,
                                timestamp: timestamp
                            });
                        }
                    }
                });
            }
            
            return originalSend.apply(this, arguments);
        };
    }

    // 拦截Fetch
    if (typeof window.fetch !== 'undefined') {
        const originalFetch = window.fetch;
        
        window.fetch = function() {
            const input = arguments[0];
            let url = '';
            
            if (typeof input === 'string') {
                url = input;
            } else if (input instanceof Request) {
                url = input.url;
            }
            
            if (config.targetUrls.some(target => url.includes(target))) {
                if (config.logRequests) {
                    console.log(`拦截到Fetch请求: ${url}`);
                }
                
                return originalFetch.apply(this, arguments)
                    .then(response => {
                        return response.clone().text().then(text => {
                            console.log(`Fetch响应 [${response.status}]:`, url);
                            console.log('响应内容:', text);
                            
                            if (config.saveResponses) {
                                const timestamp = new Date().toISOString();
                                const key = `fetch_${timestamp}_${url.substring(url.lastIndexOf('/') + 1)}`;
                                GM_setValue(key, {
                                    url: url,
                                    status: response.status,
                                    response: text,
                                    timestamp: timestamp
                                });
                            }
                            
                            return response;
                        });
                    });
            }
            
            return originalFetch.apply(this, arguments);
        };
    }

    console.log('请求拦截器已启动');
})();
Index