Greasy Fork is available in English.

小鹅通 通用m3u8获取

获取某鹅通m3u8内容 重新拼装真实ts地址和解密真实密钥 发送给扩展

// ==UserScript==
// @name         小鹅通 通用m3u8获取
// @namespace    https://94cat.com/
// @version      0.10
// @description  获取某鹅通m3u8内容 重新拼装真实ts地址和解密真实密钥 发送给扩展
// @author       mz
// @match        https://*/*
// @match        http://*/*
// @icon         data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
// @grant        none
// @run-at       document-start
// @license      GPL v3
// ==/UserScript==
(function () {
    'use strict';

    //let URLext = {};
    const URLext = new Map();
    const _indexOf = String.prototype.indexOf;
    String.prototype.indexOf = function () {
        if (arguments[0] == "#EXTM3U") {
            // 先尝试不传入 URLext 有可能m3u8本身包含远程链接 不需要URLext
            parseSendM3U8(this);
            URLext.forEach((item) => {
                parseSendM3U8(this, item);
            });
        }
        return _indexOf.apply(this, arguments);
    }
    String.prototype.indexOf.toString = function () {
        return _indexOf.toString();
    }

    const _JSONparse = JSON.parse;
    JSON.parse = function () {
        let data = _JSONparse.apply(this, arguments);
        findMedia(data);
        return data;
    }
    JSON.parse.toString = function () {
        return _JSONparse.toString();
    }
    async function findMedia(data, raw = undefined, depth = 0) {
        for (let key in data) {
            if (typeof data[key] == "object") {
                if (depth > 25) { continue; }
                if (!raw) { raw = data; }
                findMedia(data[key], raw, ++depth);
                continue;
            }
            if (typeof data[key] == "string" && key == "video_urls" && data[key].slice(-4) == "__ba") {
                let base64 = data[key].replace("__ba", "");
                base64 = base64.replaceAll("@", "1").replaceAll("#", "2").replaceAll("$", "3").replaceAll("%", "4");
                let json = _JSONparse(atob(base64));
                if (!json) { return }
                //console.error(json);
                for (let obj of json) {
                    fetch(obj.url).then(response => response.text())
                        .then(m3u8 => {
                            const lines = m3u8.split('\n');
                            let keyFlag = false;
                            for (let i = 0; i < lines.length; i++) {
                                if (lines[i] == '#EXT-X-ENDLIST') { break; }
                                if (!keyFlag && lines[i].includes("#EXT-X-KEY:METHOD=AES-128,URI=")) {
                                    const match = lines[i].match(/URI="([^"]*)"/);
                                    if (match && match[1]) {
                                        keyFlag = true;
                                        if (window.__user_id) {
                                            getKey(match[1] + "&uid=" + window.__user_id, window.__user_id);
                                        } else if (document.cookie) {
                                            for (let cookie of document.cookie.split(';')) {
                                                cookie = cookie.trim();
                                                if (cookie.substring(0, 10) == "userInfo={") {
                                                    cookie = cookie.slice(9);
                                                    cookie = isJSON(cookie);
                                                    cookie && cookie.user_id && getKey(match[1] + "&uid=" + cookie.user_id, cookie.user_id);
                                                    break;
                                                }
                                            }
                                        }
                                    }
                                    continue;
                                }
                                if (lines[i][0] != "#") {
                                    lines[i] = `${obj.ext.host}/${obj.ext.path}/${lines[i]}&${obj.ext.param}`;
                                }
                            }
                            m3u8 = lines.join('\n');
                            let url = URL.createObjectURL(new Blob([new TextEncoder("utf-8").encode(m3u8)]));
                            window.postMessage({ action: "catCatchAddMedia", url: url, href: location.href, ext: "m3u8" });
                        });
                }
            } else if (data.confusion_m3u8 && data.ext) {
                //console.error(data.ext);
                URLext.set(data.ext.host, data.ext);
                //URLext = data.ext;
            } else if (data.app_id && data.host && data.param && data.path) {
                URLext.set(data.host, data);
                //URLext = data;
            }
        }
    }
    function uid2byte(uid) {
        const byteArray = new Array;
        for (let i = 0; i < uid.length; i++) {
            let temp = uid.charCodeAt(i);
            if (temp >= 65536 && temp <= 1114111) {
                byteArray.push(temp >> 18 & 7 | 240);
                byteArray.push(temp >> 12 & 63 | 128);
                byteArray.push(temp >> 6 & 63 | 128);
                byteArray.push(63 & temp | 128);
            } else if (temp >= 2048 && temp <= 65535) {
                byteArray.push(temp >> 12 & 15 | 224);
                byteArray.push(temp >> 6 & 63 | 128);
                byteArray.push(63 & temp | 128);
            } else if (temp >= 128 && temp <= 2047) {
                byteArray.push(temp >> 6 & 31 | 192);
                byteArray.push(63 & temp | 128);
            } else {
                byteArray.push(255 & temp);
            }
        }
        return byteArray;
    }
    function getKey(url, userId) {
        fetch(url).then(response => response.arrayBuffer())
            .then(buffer => {
                let newKey = [];
                buffer = new Uint8Array(buffer);
                const uidByte = uid2byte(userId);
                for (let i in buffer) {
                    newKey.push(buffer[i] ^ uidByte[i]);
                }
                // console.error(newKey);
                window.postMessage({ action: "catCatchAddKey", key: newKey, href: location.href });
            });
    }

    const _xhrOpen = XMLHttpRequest.prototype.open;
    XMLHttpRequest.prototype.open = function () {
        this.addEventListener("readystatechange", function (event) {
            const response = this.currentTarget ? this.currentTarget.response : this.response;
            const isJson = isJSON(response);
            isJson && findMedia(isJson);
        });
        _xhrOpen.apply(this, arguments);
    }
    XMLHttpRequest.prototype.open.toString = function () {
        return _xhrOpen.toString();
    }

    function isJSON(str) {
        if (typeof str == "object") {
            return str;
        }
        if (typeof str == "string") {
            try {
                return _JSONparse(str);
            } catch (e) { return false; }
        }
        return false;
    }

    function parseSendM3U8(m3u8, URLext = undefined) {
        const lines = m3u8.split('\n');
        m3u8 = ''
        for (let i = 0; i < lines.length; i++) {
            if (lines[i].startsWith("#EXT-X-KEY") && lines[i].includes('URI=""')) {
                continue;
            }
            if (lines[i][0] != "#") {
                if (!URLext && !lines[i].startsWith("http")) {
                    return false;
                }
                if (lines[i].startsWith("http")) {
                    m3u8 += lines[i] + "\n";
                } else {
                    m3u8 += `${URLext.host}/${URLext.path}/${lines[i]}&${URLext.param}\n`;
                }
                continue;
            }
            m3u8 += lines[i] + "\n";
            if (lines[i] == "#EXT-X-ENDLIST") { break; }
        }
        // console.error(m3u8);
        const url = URL.createObjectURL(new Blob([new TextEncoder("utf-8").encode(m3u8)]));
        window.postMessage({ action: "catCatchAddMedia", url: url, href: location.href, ext: "m3u8" });
    }
})();