Sleazy Fork is available in English.

猫咪av

破解vip视频免费观看

// ==UserScript==
// @name         猫咪av
// @namespace    http://tampermonkey.net/
// @version      1.2
// @description  破解vip视频免费观看
// @author       thunder-sword
// @match        *://*/home
// @match        *://*/page/vip/*
// @match        *://*/page/remen/*
// @require      https://cdn.staticfile.net/crypto-js/4.2.0/crypto-js.min.js#sha256=dppVXeVTurw1ozOPNE3XqhYmDJPOosfbKQcHyQSE58w=
// @license      MIT
// @icon         https://www.google.com/s2/favicons?sz=64&domain=f2c013d5bbbb.com
// @grant        none
// @connect      ut.51jiajiao.top
// ==/UserScript==

//变量作用:负责一些配置
var settings = {
    m3u8Prefix: "https://runmkt.shoubairui.com",        //setSource(config?.m3u8_host_encrypt ?? data?.source?.m3u8_host)(video.tsx)
    apiPrefix: "https://ut.51jiajiao.top",   //axios.interceptors.request.use(useAxios.ts)
}

//变量作用:管理当前pageIndex
var pageIndex=1;
//变量作用:管理当前mediaType
var mediaType=1;

//常量作用:页面端type和mediaType不是一一对应的,需要转换一下(/data/category/base-2.js)
const mediaTypes={
    58: "base-vip-mmtj-",
    59: "base-vip-ycgc-",
    62: "base-vip-zfyx-",
    60: "base-vip-hlaiq-",
    61: "base-vip-sjzy-",
    63: "base-vip-cydm-",
    64: "base-vip-omdp-",
    65: "base-vip-txzq-",
    150: "base-remen-xpsd-",
    151: "base-remen-djjx-",
    152: "base-remen-gccm-",
    153: "base-remen-jpgq-",
    154: "base-remen-zmcs-",
    155: "base-remen-thss-",
    156: "base-remen-spxm-",
    157: "base-remen-avjs-",
}

//常量作用:加解密所需常量
const key = CryptoJS.enc.Utf8.parse("IdTJq0HklpuI6mu8iB%OO@!vd^4K&uXW");
const iv = "$0v@krH7V2";
const mode = CryptoJS.mode.CBC;
const padding = CryptoJS.pad.Pkcs7;
const formatter = CryptoJS.format.OpenSSL;
const secretKey = "D7hGKHnWThaECaQ3ji4XyAF3MfYKJ53M";

//来自util.js
        // this.key = "SWRUSnEwSGtscHVJNm11OGlCJU9PQCF2ZF40SyZ1WFc=";
        // this.iv = "JDB2QGtySDdWMg==";
        // this.sign_key = "JkI2OG1AJXpnMzJfJXUqdkhVbEU0V2tTJjFKNiUleG1VQGZO";
        // this.suffix = 123456;
        // this.statDomain = process.env.REACT_APP_STATIC_STAT_DOMAIN;
        // this.secretKey = "D7hGKHnWThaECaQ3ji4XyAF3MfYKJ53M";

//判断第三方库是否导入成功
if('undefined'===typeof CryptoJS || undefined===CryptoJS.AES){
    showStackToast("导入第三方库crypto-js失败","red");
    throw Error("导入第三方库crypto-js失败,请尝试更换cdn或更改网络环境");
}

function addVidKeyParam2(url) {
        let secret_key = secretKey;
        let currentTime = Math.floor(Date.now() / 1000); // Current time in seconds
        let storedTime = sessionStorage.getItem('timestamp'); // Get the stored timestamp from sessionStorage

        let time; // Declare the time variable

        // If there is a stored time and 5 minutes haven't passed, reuse the stored time
        if (storedTime && currentTime - parseInt(storedTime) < 300) {
            time = parseInt(storedTime) + 300; // Use the stored timestamp and add 300 seconds
        } else {
            // If no stored time or more than 5 minutes have passed, generate a new timestamp
            time = currentTime + 300; // Add 300 seconds to the current time
            sessionStorage.setItem('timestamp', currentTime.toString()); // Store the new timestamp in sessionStorage
        }

        // Convert the time to a hexadecimal string
        let hexTime = time.toString(16);
        console.log('Hex time:', hexTime);

        // The URI for which we're generating the key
        let uri = url;
        console.log('URI:', uri);

        // Concatenate secret_key, uri, and time (decimal) for the MD5 hash
        let paramToAppend = secret_key + uri + time; // Time in decimal
        console.log('Param to append for MD5:', paramToAppend);

        // Calculate the MD5 hash using CryptoJS
        let key = CryptoJS.MD5(paramToAppend).toString();
        console.log('Generated key (MD5):', key);

        // Return the URL with wsSecret (the MD5 hash) and wsTime (in decimal)
        return '?wsSecret=' + key + "&wsTime=" + time; // wsTime is the time in seconds (decimal)
}

function encrypt(plaintext, suffix = "123456") {
	var tmp=CryptoJS.enc.Utf8.parse(plaintext);
    let new_iv = CryptoJS.enc.Utf8.parse(iv + suffix);
	var enc=CryptoJS.AES.encrypt(tmp, key, {
		iv: new_iv,
		mode: mode,
		padding: padding,
        formatter: formatter
	});
	return enc.toString();
}

//let __data = u.decrypt(response.data.data, response.data.suffix);(useAxios.ts)
function decrypt(ciphertext, suffix = "123456") {
    let new_iv = CryptoJS.enc.Utf8.parse(iv + suffix);
	var dec=CryptoJS.AES.decrypt(ciphertext, key, {
		iv: new_iv,
		mode: mode,
		padding: padding,
        formatter: formatter
	});
	var s=dec.toString(CryptoJS.enc.Utf8);
	return JSON.parse(s);
}

function getSignature(data) {
    function objKeySort(arys) {
        let newObj = {};
        let newkey = Object.keys(arys).sort();
        for (var i = 0; i < newkey.length; i++) {
            newObj[newkey[i]] = arys[newkey[i]];
        }
        return newObj;
    }
        data = objKeySort(data);
        let pre_sign = "";
        for (let i in data) {
            pre_sign += i + "=" + data[i] + "&";
        }
        let key = '&B68m@%zg32_%u*vHUlE4WkS&1J6%%xmU@fN';
        pre_sign += key;
        return CryptoJS.MD5(pre_sign).toString();
}

//作用:生成toast,让其在toast_container中,显示在页面中上部,会永久性向页面添加一个id为ths_toast_container的div标签
function showStackToast(message, backcolor='rgb(76, 175, 80)', timeout=3000){
    //没有容器则生成容器
    let box=document.querySelector("body > div#ths_toast_container");
    if(!box){
        box=document.createElement('div');
        box.id="ths_toast_container";
        box.style.cssText = `
    position: fixed;
    top: 10px;
    left: 50%;
    transform: translateX(-50%);
    right: 10px;
    width: 300px;
    height: auto;
    display: flex;
    z-index: 9999;
    flex-direction: column-reverse;`;
        document.body.appendChild(box);
    }
    //创建toast
    const toast = document.createElement('div');
    toast.innerText = message;
    toast.style.cssText = `
    padding: 10px;
    background-color: ${backcolor};
    color: rgb(255, 255, 255);
    border-radius: 10px;
    font-size: 24px;
    font-weight: bold;
    text-align: center;
    box-shadow: rgb(0 0 0 / 30%) 0px 5px 10px;
    opacity: 1;
    transition: opacity 0.3s ease-in-out 0s;
    z-index: 9999;
    margin: 5px;
  `;
    box.appendChild(toast);
    toast.style.opacity = 1;
    if(timeout > 0){
        setTimeout(() => {
            toast.style.opacity = 0;
            setTimeout(() => {
                box.removeChild(toast);
            }, 300);
        }, timeout);
    }
    return toast;
}

class NetworkError extends Error {
  constructor(message) {
    super(message);
    this.name = "NetworkError";
  }
}

//api='https://mmnew.tlxxw.cc/api/list/base';

async function query(url, obj={}){
	let data='';
	try{
		//data=encrypt(JSON.stringify(obj));
        data=JSON.stringify(obj);
	}catch(e){
		console.log("发送包aes加密失败");
		console.log(obj);
		console.log(key);
		console.log(gzip);
		throw e;
	}
	const ret = await fetch(url, {
			method: 'GET',
			headers: {
				'Content-Type': 'application/json'
			}})
		.then(function(response) {
			if(!response.ok) {
				throw new NetworkError(`HTTP error! status: ${response.status}`);
			}
			return response.text();
		}).catch(error => {
			if (error instanceof NetworkError) {
				console.error("Network error: ", error.message);
				throw error;
		} else {
			console.error("Other error: ", error);
			//alert("发生其他错误");
			throw error;
		}
		});
    const tmp=JSON.parse(ret);
    if(!tmp || 0!==tmp.code){
        showStackToast("解析返回包失败!","red");
        console.log(tmp);
        throw Error("解析返回包失败!");
    }
	//解密返回包
	try{
		data=decrypt(tmp.data, tmp.suffix);
	}catch(e){
		console.log("返回包aes解密失败");
		console.log(tmp);
		console.log(key);
		throw e;
	}
	return data;
}

//作用:弹出新窗口播放指定m3u8视频
function playMedia(mediaUrl, width=800, height=600) {
    /* 第三个参数规定了新窗口的大小,不加则会以新窗口的形式出现 */
    var windowHandle = window.open("", "_blank", `width=${width}, height=${height}`);
    windowHandle.document.write(`
        <html>
        <head>
            <script src="https://cdn.staticfile.net/dplayer/1.27.1/DPlayer.min.js"></script>
            <script src="https://cdn.staticfile.net/hls.js/1.5.1/hls.min.js"></script>
        </head>
        <body>
            <div id="dplayer"></div>
            <script>
                //var url = prompt("请输入要播放的视频url");
                var dp = new DPlayer({
                     container: document.getElementById('dplayer'),
                     autoplay: true,
                     theme: '#FADFA3',
                     loop: true,
                     lang: 'zh',
                     screenshot: true,
                     hotkey: true,
                     preload: 'auto',
                     video: {
                         url: "${mediaUrl}",
                         type: 'hls'
                     }
                 });
            </script>
        </body>
        </html>
    `);
}

//await query("https://utt.51jiajiao.top/data/index/home.js?1718510400000");

//插入视频条目执行函数
async function insertList(box, videoList){
    showStackToast("正在破解中...");
    //修改第一个容器内的标识
    box.firstElementChild.firstElementChild.innerText="已破解VIP视频";
    let list=box.querySelector("div:nth-child(2) > div");
    //循环添加事件
    for(let i=0; i < videoList.length && i < list.childNodes.length; i++){
        //先去除绑定
        list.childNodes[i].onclick= () => {
            if(!videoList[i].video_url){
                alert("未找到该视频地址");
                throw Error("未找到该视频地址");
            }
            let video_url=videoList[i].video_url.replaceAll('//', '/')
            let m3u8url=settings['m3u8Prefix']+video_url;
            m3u8url=m3u8url+addVidKeyParam2(video_url);
            showStackToast("视频地址获取成功,将弹窗播放");
            showStackToast(videoList[i].title);
            console.log(m3u8url);
            console.log(videoList[i]);
            playMedia(m3u8url);
        };
    }
    showStackToast("破解完成!");
}

//获取当前时间戳
function getTime(){
    var time = new Date();
    if (time.getHours() < 4){
        time.setHours(0, 0, 0, 0)
    }
    else if (time.getHours() < 8){
        time.setHours(4, 0, 0, 0)
    }
    else if (time.getHours() < 12){
        time.setHours(8, 0, 0, 0)
    }
    else if (time.getHours() < 16){
        time.setHours(12, 0, 0, 0)
    }
    else if (time.getHours() < 20){
        time.setHours(16, 0, 0, 0)
    }
    else if (time.getHours() < 24){
        time.setHours(20, 0, 0, 0)
    }
    else{
        time.setHours(24, 0, 0, 0)
    }

    let ts = time.getTime();
    return ts;
}

//main要执行的内容
async function main_func(){
    let apiUrl=settings["apiPrefix"]+"/data/index/home.js?"+getTime();
    //showStackToast(apiUrl);
    const ret = await query(apiUrl);
    if(!ret || !ret.vip_list || !ret.vip_list.data){
        alert("api访问异常");
        throw Error("api访问异常");
    }
    showStackToast(`成功找到【${ret.vip_list.data.length}】个vip视频`);
    //查找box
    let box = document.querySelector("div.mw1100 > div:nth-child(1)");
    if(!box){
        showStackToast("未找到box!", "red");
        throw Error("未找到box!");
    }
    showStackToast("成功找到box!");
    insertList(box, ret.vip_list.data);
}

///page/vip/和/page/remen/的执行内容
async function func_list(user_id=0){
    let urlType=mediaTypes[mediaType];
    if(undefined === urlType){
        alert("脚本异常,urlType为空");
        throw Error("脚本异常,urlType为空");
    }
    let apiUrl=settings["apiPrefix"]+"/data/list/"+urlType+pageIndex+".js?"+getTime();
    //showStackToast(apiUrl);
    const ret = await query(apiUrl);
    if(!ret || !ret.list || !ret.list.data){
        alert("api访问异常");
        throw Error("api访问异常");
    }
    showStackToast(`成功找到【${ret.list.data.length}】个vip视频`);
    //查找box
    let box = document.querySelector("div.mw1100");
    if(!box){
        showStackToast("未找到box!", "red");
        throw Error("未找到box!");
    }
    showStackToast("成功找到box!");
    insertList(box, ret.list.data);
    //找到上一页、下一页按钮容器
    let pagination=document.querySelector("div.fl.align_center.justify_center.gap20.mb20");
    //清空原有按钮
    //pagination.innerHTML='';
    pagination.removeChild(pagination.lastChild);
    pagination.removeChild(pagination.firstChild);
    pagination.removeChild(pagination.childNodes[1]);
    //找到上一页按钮
    var prePage=pagination.firstChild;
    prePage.textContent="上一页";
    prePage.setAttribute("style", "padding: 5px .6rem;border: 1px solid #bebebd;text-align: center;border-radius: 7px;cursor: pointer;background: #fff;");
    //pagination.appendChild(prePage);
    //绑定事件
    prePage.addEventListener("click", async () => {
        if(pageIndex<=1){
            showStackToast(`当前page为${pageIndex},不能再向上一页`);
            throw Error(`当前page为${pageIndex},不能再向上一页`);
        }
        pageIndex-=1;
        apiUrl=settings["apiPrefix"]+"/data/list/"+urlType+pageIndex+".js?"+getTime();
        //showStackToast(apiUrl);
        const ret = await query(apiUrl);
        if(!ret || !ret.list || !ret.list.data){
            alert("api访问异常");
            throw Error("api访问异常");
        }
        showStackToast(`成功找到【${ret.list.data.length}】个vip视频`);
        insertList(box, ret.list.data);
    });
    //找到下一页按钮
    var nextPage=pagination.lastChild;
    nextPage.textContent="下一页";
    nextPage.setAttribute("style", "padding: 5px .6rem;border: 1px solid #bebebd;text-align: center;border-radius: 7px;cursor: pointer;background: #fff;");
    //pagination.appendChild(nextPage);
    //绑定事件
    nextPage.addEventListener("click", async () => {
        pageIndex+=1;
        apiUrl=settings["apiPrefix"]+"/data/list/"+urlType+pageIndex+".js?"+getTime();
        //showStackToast(apiUrl);
        const ret = await query(apiUrl);
        if(!ret || !ret.list || !ret.list.data){
            alert("api访问异常");
            throw Error("api访问异常");
        }
        showStackToast(`成功找到【${ret.list.data.length}】个vip视频`);
        insertList(box, ret.list.data);
    });
}

// 主函数:检验是否在可执行域,不是则不管
async function mainFunction(){
    //每次执行都重置pageIndex
    pageIndex=1;
    if('/home'===window.location.pathname){
        //执行main相关内容
        showStackToast("当前位于main页面");
        setTimeout(async () => {
            await main_func();
        }, 2000);
    } else if(-1!==window.location.pathname.search("/page/vip/") || -1!==window.location.pathname.search("/page/remen/")){
        //执行video_list相关内容
        showStackToast("当前位于list页面");
        //修改mediaType
        const e=window.location.pathname.match(/(\d+)$/);
        if(!e || e.length < 2){
            alert(`没有找到type,当前url为${window.location}`);
            throw Error(`没有找到type,当前url为${window.location}`);
        }

        if(!e[1] in mediaTypes){
            showStackToast("未找到vip视频标识,将忽略");
            throw Error("未找到vip视频标识,将忽略");
        }

        mediaType=e[1];

        setTimeout(async () => {
            await func_list();
        }, 300);
    }
    //否则什么也不执行
}

setTimeout(async () => {
    'use strict';
    //alert("测试");
    // console.log(encrypt);
    // console.log(decrypt);//decrypt用于方便调试
    // console.log(query);
    // console.log(getSignature);
    // console.log(addVidKeyParam2);
    let previousUrl = '';
    const observer = new MutationObserver(async function(mutations) {
        let nowUrl=window.location.href;
        if (nowUrl !== previousUrl) {
            console.log(`URL changed from ${previousUrl} to ${window.location.href}`);
            previousUrl = nowUrl;
            await mainFunction();
        }
    });
    const config = {subtree: true, childList: true};
    observer.observe(document, config);
}, 500);