Kimi Send Action

将当前页面的网址发送到指定的服务器,服务器地址信息需要再代码中修改以适合自己使用!目前已经支持视频地址获取站点:四色AV(335pai.com),黄色仓库(hsck.net,huangsecangku.net,hscangku.com),Exhentai(e-hentai.org,exhentai.org),天天动漫(ttdm0.me),天天动漫普通(ttdm1.com),飞快TV(feikuai.tv,feikuai.in),白嫖者联盟(百思派电影网,baipiaozhe.ai,ai.baipiaozhe.com),雪落影视(v.xl01.eu.cc,xl01.eu.cc),网飞猫(ncat.app),XVIDEOS,Pornhub,皮皮妖(pipiyao.cc),青空次元(www.sorani.net),666导航站(666.cd),EduStream Online(17.rip),

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

// ==UserScript==
// @name         Kimi Send Action
// @namespace    https://github.com/RANSAA
// @version      0.0.4
// @description  将当前页面的网址发送到指定的服务器,服务器地址信息需要再代码中修改以适合自己使用!目前已经支持视频地址获取站点:四色AV(335pai.com),黄色仓库(hsck.net,huangsecangku.net,hscangku.com),Exhentai(e-hentai.org,exhentai.org),天天动漫(ttdm0.me),天天动漫普通(ttdm1.com),飞快TV(feikuai.tv,feikuai.in),白嫖者联盟(百思派电影网,baipiaozhe.ai,ai.baipiaozhe.com),雪落影视(v.xl01.eu.cc,xl01.eu.cc),网飞猫(ncat.app),XVIDEOS,Pornhub,皮皮妖(pipiyao.cc),青空次元(www.sorani.net),666导航站(666.cd),EduStream Online(17.rip),
// @author       sayaDev
// @license      MIT License
// @icon         data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACwAAAAsCAYAAAAehFoBAAAAAXNSR0IArs4c6QAAA6NJREFUWEftmGuITGEYx//nMnN2Z3Z2ZixRrAxWbu26hZZQRFq2FsumUOKDJJckknKJkg8oJHzwxZ1si9xDi9xvuazb7Npdlw/YnfucmTln9D6nsazLNOqIep8vp5ne553/+/v/36eZETod+JzEf1QCF2yyW5ywyYDBCXPCrQjwSPBI8EiYnQFO+F8ibJcFFLeXUdRGxv1PCVx8Fzdd3uB2MhwWAXc+JuCL/fjN95dzeFaBgrUDbBCFFo0xHTj4WsXKu2HThL8qd0GRBCy9FcYhr/rD5/xUsFsR8LDMBabVG9BQG9DR1SHB4xBpgzlXgzjbaA7tPxLMbDk22kHiNj+OYuuTCLQksL3YTnbte62SYKdVwIFRDvRyS7S2MaRjxuUA6oI6OtpEXCnJJVvPvY2j3GOFRRRQF9RQdiGAz2qS3NtRbMfYjlZIAih2hW1kWERkRpg1vyh3w2oAhZ4EnjZr2PtSxZFalV6zuj7RiXy7sYi9xwREtCR6Hm1GlxyJBKeKxSm135nGOOZeDWL9QBtmFijf9afWZxQJ1jSigwW7h9thk78JMYAPER0jTvrR2y2hcozhwuSLATrQozIX0Vl9L4xL7xNfBe+qiWLdgwhOj8tFX7dEMRt5yo+aKS6wi/2sWcP4s34s7puNhX2yaM+MBbMmRrrco2Cqx4p+eYZVrI7UxvDKr2FFUTa9fu7T6NnNIUEWgco3MYpSinDh8WY0qUlsGmxDRVcF78I6hlT5UF/hpnuy6m6Y3MuxCHg22ZW54AW9szC9m4L3ER2TLgRoAyakusSJTnaRMnrYq2JZoSE4nPh+/JxqiGPb0xbB+QebaN2GQTbM6N4iuKHCTe8vuhHCsboYAaqdZhwiI8LLi7Ixv5dhzbxrIZxsiNHpb5c66fm4ScPs6iBulTppTf9KHz5GdRIUZ6PPq0LV8JXwrwSnJkJVfQzzr4cwsbOVLmHGkWibJZI4RpWVqiVpNqZqzf0w9jxXyT52AEbYG9Apn4x16fkA/LFkWsE7h9lRkm+lbVmuPQ6J6GYsmDWM7GDBtmI7XNYWoQkd2FkTxcZHEYNsnoz9o3JIdOpgLApbnkTRJUdE9QTDgdaE34Z1DK3y0YU7MdaBglxjLDKX2Ohj43LJzRDdldaV9hcHa+7hlOD1a/ik/vxPojxFgFsR6SL+STFHbTJQH9TTtqcVnHaHv7yACzYbOCfMCbciwCPBI8EjYXYGOGFO+PcE+Bw2OyH/HeEvGbr2fLNAmjYAAAAASUVORK5CYII=


// @match        http://*/*
// @match        *://*/*

// @require 	https://update.greasyfork.org/scripts/494214/1432041/TKBaseSDK.js

// @grant       unsafeWindow
// @grant       GM_openInTab
// @grant       GM_xmlhttpRequest
// @grant       GM_setValue
// @grant       GM_getValue


// @connect     self
// @connect     localhost
// @connect     127.0.0.1
// @connect     *


// @compatible   chrome
// @compatible   edge
// @compatible   firefox
// @compatible   opera
// @compatible   safari



// @homepageURL  https://greasyfork.org/scripts/586584
// @supportURL   https://greasyfork.org/scripts/586584/feedback
// ==/UserScript==
// @noframes


/**
 * 脚本更新与下载:发布到 Greasy Fork可以不写他可以自动生成,如果放在自己的站点如GitHub上则需要明确指明
 * //@updateURL     后面是用于比对脚本版的的头部UserScript数据,托管油猴站点默认是 xxxx/xx.meta.js
 * //downloadURL    后面是完整的脚本下载地址,托管油猴站点默认是 xxxxx/xx.user.js
 * 
 * 1. 托管油猴站点的更新与下载标记示例
 * // @downloadURL https://update.greasyfork.org/scripts/586584/Kimi%20Send%20Action.user.js
 * // @updateURL https://update.greasyfork.org/scripts/586584/Kimi%20Send%20Action.meta.js
 * 
 * 2. 自定义站点的更新与下载标记示例,
 * // @updateURL https://raw.githubusercontent.com/xxx/xxx/main/test.user.js
 * // @downloadURL https://raw.githubusercontent.com/xxx/xxx/main/test.meta.js
 **/









/**
 * 目前支持的参数:
 * pageURL: 当前页面的url地址
 * title: 当前网页的标题
 * videoURL: pageURL页面中的视频地址,如果没有获取到视频地址则它的值与pageURL相同。
 **/



//四色AV视频服务器地址  2026-7-17
//自定义的视频访问资源,置空将不会被替换
//替换为当前没有问题或者高速的域名。
let fourColorAVCustomDomian="https://d39msc2hr8spz5.cloudfront.net";
//fourColorAVCustomDomian="";

//无法访问的域名,置空将不会替换
//当前请求有问题的域名,或者缓慢 - 这域名是站点动态变化的域名
let fourColorAVNoFechDomain="https://strem1.wqikkp.cn";
//fourColorAVNoFechDomain="";



// -------------------------------发送相关服务配置-------------------------------
/**
 * 配置信息,可更具需求更改配置
 **/
const SERVER_CONFIG = {
    host: "127.0.0.1",          //服务器地址
    port: "8080",               //服务器端口,直接使用”“不指定端口
    method: "POST",             //请求方式
    scheme: "http",             //协议类型
    taskAdd: "task/add",        //yt-dlp 任务添加API Query Path路劲地址
};


/**
 * 站点类型,不同的站点类型对应的Button不同
 **/
const WebType = {
    ytdlp: 0,
    exhentai: 1
};
//当前站点类型样式,默认为yt-dlp样式
let currentWebType = WebType.ytdlp


/**
 * 将参数组装成JSON格式
 * 获取当前需要的所有属性
 * pageURL: 当前页面的url地址
 * title: 当前网页的标题
 * videoURL: pageURL页面中的视频地址,如果没有获取到视频地址则它的值与pageURL相同。
 **/
function loadParameterJSON(){
    // //示例
    // var json = {
    //  jsonrpc:'2.0',
    //  method:'aria2.addUri',
    //  id:url,
    //  params:[
    //   [url],
    //  ]
    // }
    // return JSON.stringify(json)


    //网页地址
    let pageURL = window.location.href;
    //标题
    let title = filterVideoTitle();
    //视频链接
    let videoURL = filterVideoLink();
    

    let json = {
        "pageURL": pageURL,
        "title": title,
        "videoURL":videoURL
    };
    return JSON.stringify(json)
}
// -------------------------------发送相关服务配置-------------------------------









/**
 * 用于接收window.top.postMessage从:top或iframe document中解析到的视频地址
 * 说明:
 * 1. 它的具体数据会由后面动态获取,所以它的值有可能为空
 * 2. 如果页面切换(比如视频集数切换),时需要将它的值置空;防止视频切换了新视频地址还没解析到时使用旧的视频地址。
 * 3. 它的优先级高于videoURLFromPerformanceObserver,只有它的值不存在才会使用videoURLFromPerformanceObserver。
 **/
let videoURLFromPostMessage = "";


/**
 * 用于接收PerformanceObserver监听到Performance Resource Timing数据时发送过来的数据。
 * 说明:
 * 1. 它的具体数据会由后面动态获取,所以它的值有可能为空
 * 2. 如果页面切换(比如视频集数切换),时需要将它的值置空;防止视频切换了新视频地址还没解析到时使用旧的视频地址。
 * 3. 它的优先级低于videoURLFromPostMessage,只有videoURLFromPostMessage的值不存在时才会使用它的值。
 **/
let videoURLFromPerformanceObserver = "";







//自动执行
(function() {
    'use strict';
    //默认为yt-dlp样式
    currentWebType = WebType.ytdlp

    // enableKimiSendActionPlugin();

    setup();

})();




/**
 * 根据是否在iframe中来处理不同的逻辑
 * 1. UI必须是在非iframe中创建
 * 2. 有些解析操作需要运行在iframe中
 **/
function setup()
{   
    //添加监听用于接收数据
    addMainDocumentEventListener();

    //在主document中运行
    if(window.self === window.top){
        //setupUI
        enableKimiSendActionPlugin();

        //
        instancesHomeDocumentData();
    }else{//在iframe中运行     
        //
        instancesIFrameData();
    }
}




/**
 * 1. 初始化需要再主document中运行的逻辑
 * 2. 与instancesIFrameData中的逻辑是想对应的
 **/
function instancesHomeDocumentData()
{
    if(window.self !== window.top){
        return;
    }
    let title = window.document.title;



    /**
     * 功能:在main document中添加监听当前document的location.href发生变化的通知
     * 说明:
     * 1. 一般用于单页应用,即站点在当页切换资源且href变化,但是页面不刷新也不前进
     * 2. 该监听中用于变量重置和清理操作。
     * 
     **/
    addLocationHrefChangeEventListener();
}





/**
 * 1. 初始化需要再主iframe中运行的逻辑
 * 2. 与iinstancesHomeDocumentData中的逻辑是想对应的
 * 3. 警告⚠️⚠️:这是在iframe中运行的操作,一般不要进行title筛选,因为iframe的title不明确;如果进行赛选容易造成无法识别的bug
 **/
function instancesIFrameData()
{
    if(window.self === window.top){
        return;
    }
    //⚠️警告⚠️:在iframe中不要使用title来筛选不同的站点



    // 等待3秒执行 - 等待页面初始化
    setTimeout(() => {
        parseGeneralVideoURL();
    }, 2000);

}







// -------------------------------根据条件判断是否启用这个插件-------------------------------
//根据条件判断是否启动这个插件
function enableKimiSendActionPlugin()
{
    //启用标志
    let isEnable = false;

    let host = window.location.host
    let title = window.document.title;


    // 支持的主机列表
    let supportedHosts = [
        "e-hentai.org",
        "exhentai.org",
        "beeg.com",
        "hsck.net",
        "huangsecangku.net",
        "hscangku.com",
        "feikuai.tv",
        "feikuai.in",
        "baipiaozhe",   //ai.baipiaozhe.com, baipiaozhe.ai, 白嫖者联盟,百思派电影网
        "baipiao.ai",
        "xl01.eu.cc",
        "sorani.net",
        "666.cd",           //666导航站
        "cloudfront.net",   //666导航站-重定向 d199amw2tsk47k.cloudfront.net
        "17.rip",           //17.RIP
    ]
    //只要包含当前域名就启用
    // const matched = supportedHosts.includes(host);
    const matched = supportedHosts.some(item =>
        host === item ||
        host.endsWith("." + item) ||
        host.includes(item) && !item.includes(".")
    );
    if (matched) {
        isEnable = true;
    }



    //检测标题是否含有特殊的字符,如果有就启用
    if (title.includes("快活影院") || 
        title.includes("四色AV") || 
        title.includes("黄色仓库") || 
        title.includes("天天动漫") || 
        title.includes("飞快TV") || 
        title.includes("白嫖者联盟") || 
        title.includes("雪落影视") || 
        title.includes("网飞猫") || 
        title.includes("Pornhub") || 
        title.includes("XVIDEOS") || 
        title.includes("皮皮妖") || 
        title.includes("皮皮妖") || 
        title.includes("EduStream Online") ||       //17.RIP
        title.includes("AV") 
    ) {
        isEnable = true;
    }


    //初始化
    if (isEnable) {
        TKBaseSDK.initToast();
        addSendServerButton();
    }
} 
// -------------------------------根据条件判断是否启用这个插件-------------------------------









// -------------------------------复写TKBaseSDK.js中的方法-------------------------------
/**
 * 创建一个自定义的Button并返回。 --> TKBaseSDK.createListItemButton()方法内部有一个SVG对象,该方法没有
 * text: 显示的文字
 * return:button
 **/ 
function createListItemButton(text){
    // 创建一个包含按钮的DIV元素
    let itemButton = document.createElement("TKButtonItem");
    itemButton.setAttribute("class", "TKButtonListItemStyle");

    // // 根据文字创建SVG
    // let itemSVG = createSVGElement(text);
    // itemSVG.setAttribute("class", "TKButtonListItemStyle");

    // 将itemSVG添加到itemButton中
    // itemButton.appendChild(itemSVG);

    return itemButton
}
// -------------------------------复写TKBaseSDK.js中的方法-------------------------------




// -------------------------------Setup UI-------------------------------
//根据条件创建不同的UI
function addSendServerButton(){
    //添加style
    TKBaseSDK.addButtonStyle();


    //根据域名添加不同的按钮
    let host = window.location.host
    let exhentai = [
        "e-hentai.org",
        "exhentai.org",
        ]

    //
    if (exhentai.includes(host)) {
        currentWebType= WebType.exhentai
        setupExhentaiButton()
    }else{
        currentWebType= WebType.ytdlp
        setupDefaultYtDlpButton()
    }

}

/**
 * 安装默认的yt-dlp按钮
 **/
function setupDefaultYtDlpButton(){
    //创建Send URL按钮
    // let sendURL = TKBaseSDK.createListItemButton("Send URL");
    let sendURL = createListItemButton("");
    sendURL.appendChild(createIMGElement("Send URL"));
    //定义的是事件被触发后要做的事情
    sendURL.addEventListener("click", function() {
        console.log("currentWebType:",currentWebType)
        sendLocationURLAction();
    });

    //创建Copy URL按钮
    // let copyURL = TKBaseSDK.createListItemButton("Copy URL");
    let copyURL = createListItemButton("");
    copyURL.appendChild(createIMGElement("Copy URL"));
    //定义的是事件被触发后要做的事情
    copyURL.addEventListener("click", function() {
        copyLocationURLAction();
    });

    //创建Copy M3u8按钮
    // let copyM3u8 = TKBaseSDK.createListItemButton("Copy M3U8");
    let copyM3u8 = createListItemButton("");
    copyM3u8.appendChild(createIMGElement("Copy M3U8"));
    //定义的是事件被触发后要做的事情
    copyM3u8.addEventListener("click", function() {
        copyM3u8LinkAction();
    });

    //创建Copy yt-dlp-n按钮
    // let copyYtDlpN = TKBaseSDK.createListItemButton("Copy yt-dlp-n");
    let copyYtDlpN = createListItemButton("");
    copyYtDlpN.appendChild(createIMGElement("Copy yt-dlp-n"));
    //定义的是事件被触发后要做的事情
    copyYtDlpN.addEventListener("click", function() {        
        copy_yt_dlp_n_M3U8LinkAction();
    });

    //创建Copy yt-dlp按钮
    // let copyYtDlp = TKBaseSDK.createListItemButton("Copy yt-dlp");
    let copyYtDlp = createListItemButton("");
    copyYtDlp.appendChild(createIMGElement("Copy yt-dlp"));
    //定义的是事件被触发后要做的事情
    copyYtDlp.addEventListener("click", function() {
        copy_yt_dlp_M3U8LinkAction();
    });


    //创建Copy Magnet按钮
    // let copyMagnet = TKBaseSDK.createListItemButton("Copy Magnet");
    let copyMagnet = createListItemButton("");
    copyMagnet.appendChild(createIMGElement("Copy Magnet"));
    copyMagnet.addEventListener("click", function() {
        copyMagnetLinksAction();
    });



    let list = document.createElement("TKButtonList");
    list.className = "TKButtonListStyle";
    list.appendChild(sendURL);
    //list.appendChild(copyURL);
    list.appendChild(copyM3u8);
    list.appendChild(copyYtDlpN);
    list.appendChild(copyYtDlp);
    //list.appendChild(copyMagnet);
    document.body.appendChild(list);
}

/**
 * 安装exhentai站点对应的按钮
 **/
function setupExhentaiButton(){
    //创建Send URL按钮
    // let sendURL = TKBaseSDK.createListItemButton("Send URL");
    let sendURL = createListItemButton("");
    sendURL.appendChild(createIMGElement("Send URL"));
    //定义的是事件被触发后要做的事情
    sendURL.addEventListener("click", function() {
        console.log("currentWebType:",currentWebType)
        sendLocationURLAction();
    });


    //创建Copy hentai按钮
    // let copyHentai = TKBaseSDK.createListItemButton("Copy yt-dlp-n");
    let copyHentai = createListItemButton("");
    copyHentai.appendChild(createIMGElement("Copy hentai"));
    //定义的是事件被触发后要做的事情
    copyHentai.addEventListener("click", function() {        
        copy_hentai_Action();
    });


    //创建Copy hentai按钮
    // let copyHentai = TKBaseSDK.createListItemButton("Copy yt-dlp-n");
    let copyHentaiN = createListItemButton("");
    copyHentaiN.appendChild(createIMGElement("Copy hentai-n"));
    //定义的是事件被触发后要做的事情
    copyHentaiN.addEventListener("click", function() {        
        copy_hentai_AtlasName_Action();
    });


    //创建Copy copyHentaiName按钮
    // let copyHentaiName = TKBaseSDK.createListItemButton("Copy yt-dlp-n");
    let copyHentaiName = createListItemButton("");
    copyHentaiName.appendChild(createIMGElement("Copy Title"));
    //定义的是事件被触发后要做的事情
    copyHentaiName.addEventListener("click", function() {        
        copy_hentai_name_Action();
    });


    let list = document.createElement("TKButtonList");
    list.className = "TKButtonListStyle";
    // list.appendChild(sendURL);
    list.appendChild(copyHentaiN);
    // list.appendChild(copyHentai);
    // list.appendChild(copyHentaiName);
    document.body.appendChild(list);
}


// -------------------------------Setup UI-------------------------------




// -------------------------------Action-------------------------------

/**
 * 发送数据当前页面的URL到指定服务器
 */
function sendLocationURLAction()
{
    console.log(`Send Server`);


    //服务器的地址
    var serverUrl = SERVER_CONFIG.scheme + "://" + SERVER_CONFIG.host + ":" + SERVER_CONFIG.port + "/" + SERVER_CONFIG.taskAdd;
    if (SERVER_CONFIG.port === "" ) {
        serverUrl = SERVER_CONFIG.scheme + "://" + SERVER_CONFIG.host +  "/" + SERVER_CONFIG.taskAdd;
    }


    //发送的参数数据
    let data = loadParameterJSON();
    console.log(`serverUrl: ${serverUrl}`);
    console.log(`parameter: ${data}`);


    //GM_xmlhttpRequest方式请求
    GM_xmlhttpRequest({
        method: SERVER_CONFIG.method,
        url: serverUrl,
        headers: {
            'Content-Type': 'application/json; charset=utf-8',
        },
        data: data,
        onload: function(response) {
            console.log(response);
            console.log(`readyState:${response.readyState}`);
            console.log(`status:${response.status}`);
            console.log(`statusText:${response.statusText}`);
            console.log(`responseHeaders:\n${response.responseHeaders}`);
            console.log(`responseText:${response.responseText}`);
            if (response.status === 200) {
                console.log(`Send URL Success: ${url}`);
                TKBaseSDK.showToast("当前URL地址发送成功!",1);
            } else {
                console.log(`Send URL Error: ${url} statusText: ${response.statusText}`);
                TKBaseSDK.showToast("当前URL地址发送失败!",0);
            }
        },
        onerror: function(response) {
            // 请求发生错误时执行
            console.error("Request failed:", response);
            let msg = `发送失败,Send Server服务地址:${response.finalUrl}`;
            console.log(msg);
            TKBaseSDK.showToastWtihTime(msg, 0, 4000);
        }
    });
}

/**
 * 拷贝当前网页地址
 **/
function copyLocationURLAction(){
    //当前网页地址
    let url = window.location.href;
	TKBaseSDK.copyToClipBoard(url);
    TKBaseSDK.showToast("复制成功!",1);
}


/**
 * 获取并拷贝m3u8链接
 **/
function copyM3u8LinkAction(){
    let m3u8URL = filterVideoLink();
    if (m3u8URL === "" || m3u8URL === window.location.href) {
        TKBaseSDK.showToast("复制失败,没有找到M3U8链接!",0);
        TKBaseSDK.copyToClipBoard("");
    }else{
        TKBaseSDK.showToast("复制成功!",1);
        //拷贝到剪切板
        TKBaseSDK.copyToClipBoard(m3u8URL);
    }
}




/**
 * 拷贝四色AV中的标题与m3u8链接,并且组装yt-dlp-n命令
 * 格式: yt-dlp-n "视频标题" "m3u8视频地址"
 **/
function copy_yt_dlp_n_M3U8LinkAction(){
    let videoURL = filterVideoLink();
    let title = filterVideoTitle();

    if (videoURL === "" || title === "") {
        TKBaseSDK.showToast("yt-dlp-n下载命令复制失败,没有找到视频链接!",0);
        TKBaseSDK.copyToClipBoard("");
    }else{
        TKBaseSDK.showToast("yt-dlp-n下载命令复制成功!",1);

        //拷贝到剪切板
        let cmd = "yt-dlp " + "\"" + title + "\"" + " \"" + videoURL + "\"" + "";
        TKBaseSDK.copyToClipBoard(cmd);
    }
}




/**
 * 拷贝当前网页链接, 并组装yt-dlp命令
 * 格式:yt-dlp "网页地址"
 **/
function copy_yt_dlp_M3U8LinkAction(){
    let videoURL = filterVideoLink();

    TKBaseSDK.showToast("yt-dlp下载命令复制成功!",1);
    //拷贝到剪切板
    let cmd = "yt-dlp " + "\"" + videoURL + "\"" + "";
    TKBaseSDK.copyToClipBoard(cmd);

}



/**
 * 功能:拷贝当前页面的所有magnet磁力链接
 **/
function copyMagnetLinksAction(){
    const magnetLinks = getMagnetLinks();
    // 转换成换行分隔的字符串
    const magnetLinksString = magnetLinks.join('\n');
    if (magnetLinksString === "" ) {
        TKBaseSDK.showToast("Copy Mangnet失败,没有找到磁力链接!",0);
        TKBaseSDK.copyToClipBoard("");
    }else{
        TKBaseSDK.showToast("Copy Mangnet成功!",1);
        //拷贝到剪切板
        TKBaseSDK.copyToClipBoard(magnetLinksString);
    }
}


/**
 * 功能:拷贝e-hentai.org,exhentai.org站点的hentai下载链接 与 当前图集的名称
 **/
function copy_hentai_AtlasName_Action(){
    //当前网页地址
    let url = window.location.href;
    console.log("url:",url)

    //当前页面图集的标题
    let title = window.document.title;
    // 移除末尾的 "- E-Hentai Galleries"
    title = title.replace(/\s*-\s*E-Hentai Galleries$/i, '').trim();

    // 处理文件名中的特殊字符
    title = sanitizeFileName(title);
    console.log("title:",title)

    //当前图集图片数量
    let picCount = hentai_getAlbumPicCount()
    console.log("picCount:",picCount)

    //拷贝到剪切板
    let cmd = "hentai -u " + "\"" + url + "\"" + " -n " + "\"" + title + "\"" + " -s "+ "-r ";

    if (picCount != null){
        cmd = "hentai -u " + "\"" + url + "\"" + " -n " + "\"" + title + "\"" + " -t " + picCount +  " -s "+ "-r ";
    }

    TKBaseSDK.copyToClipBoard(cmd);
    TKBaseSDK.showToast("hentai-n下载命令复制成功!",1);
}

/**
 * 功能:拷贝e-hentai.org,exhentai.org站点的hentai下载链接
 **/
function copy_hentai_Action(){
       //当前网页地址
    let url = window.location.href;
    TKBaseSDK.showToast("hentai下载命令复制成功!",1);
    //拷贝到剪切板
    let cmd = "hentai -u " + "\"" + url + "\"" + " -s "+ "-r ";
    TKBaseSDK.copyToClipBoard(cmd);
}


/**
 *  获取e-hentai.org,exhentai.org站点当前图集的名称
 **/
function copy_hentai_name_Action(){
    let title = window.document.title;

    // 移除末尾的 "- E-Hentai Galleries"
    title = title.replace(/\s*-\s*E-Hentai Galleries$/i, '').trim();

    // 处理文件名中的特殊字符
    title = sanitizeFileName(title);

    TKBaseSDK.copyToClipBoard(title);
    // TKBaseSDK.showToast("当前图集名称复制成功!",1);
    // let msg = "图集名称[" + title + "]复制成功!"
    let msg = "图集名称:" + title 
    TKBaseSDK.showToast(msg,1);
}

// -------------------------------Action-------------------------------






// -------------------------------通用标题与视频地址-------------------------------
/**
 * 过滤获取当前视频指定的名称
 **/ 
function filterVideoTitle(){
    // 默认使用网站标题作为title
    var title = document.title;
    let host = window.location.host


    //匹配四色AV站点标题
    if (title.includes("快活影院") || title.includes("四色AV")) {
        if (_title_ = getFourColorAVTitle()) {
            title = _title_;
        }
    }

    //匹配黄色仓库
    if (title.includes("黄色仓库") ) {
        if (_title_ = getHuangSeCangKuTitle() ) {
            title = _title_;
        }
    }

    //匹配天天动漫
    if (title.includes("天天动漫") ) {
        if (_title_ = getTtdmTitle() ) {
            title = _title_;
        }
    }

    //飞快TV
    if (title.includes("飞快TV") ) {
        if (_title_ = getFriKuaiTVTitle() ) {
            title = _title_;
        }
    }

    //白嫖者联盟 - baipiaozhe.ai - ai.baipiaozhe.com
    if (title.includes("白嫖者联盟") ) {
        if (_title_ = getBaiPiaoZheTitle() ) {
            title = _title_;
        }
    }

    //雪落影视
    if (title.includes("雪落影视") ) {
        if (_title_ = getXueLuoYinShiTitle() ) {
            title = _title_;
        }
    }

    //网飞猫
    if (title.includes("网飞猫") ) {
        if (_title_ = getWangFeiMaoTitle() ) {
            title = _title_;
        }
    }

    //皮皮妖
    if (title.includes("皮皮妖") ) {
        if (_title_ = getPiPiYaoTitle() ) {
            title = _title_;
        }
    }

    //青空次元
    if (title.includes("青空次元") ) {
        if (_title_ = getSoraniTitle() ) {
            title = _title_;
        }
    }


    // 支持的主机列表
    let macthHosts = [
        "666.cd",           //666导航站
        "cloudfront.net",   //666导航站-重定向 d199amw2tsk47k.cloudfront.net
    ]
    //只要包含当前域名就启用
    const matched = macthHosts.some(item =>
        host === item ||
        host.endsWith("." + item) ||
        host.includes(item) && !item.includes(".")
    );
    if (matched) {
        //666导航站
        if (_title_ = get666CdTitle() ) {
            title = _title_;
        }
    }



    //17.RIP
    if (title.includes("EduStream Online") || host === "17.rip" ) {
        if (_title_ = get17RipTitle() ) {
            title = _title_;
        }
    }








    //匹配其它站点...

    //去掉字符串两端的空格和不可见字符(例如空格、换行、制表符等)
    title = title.trim();
    // 处理文件名中的特殊字符
    title = sanitizeFileName(title);
    return title;
}


/**
 * 过滤并获取视频地址,如果没有获取到视频地址则直接使用window.location.href
 **/ 
function filterVideoLink(){
    var title = document.title;
    let host = window.location.host

    // 默认使用当前页面地址
    var videoURL = window.location.href;


    //获取四色AV的视频地址
    if (title.includes("快活影院") || title.includes("四色AV")) {
        if (_url_ = getFourColorAVVideoURL()) {
            videoURL = _url_;
        }
    }

    // 黄色仓库
    if (title.includes("黄色仓库") ) {
        if (_url_ = getHuangSeCangKuVideoURL() ) {
            videoURL = _url_;
        }
    }

    // 天天动漫
    if (title.includes("天天动漫") ) {
        if (_url_ = getTtdmVideoURL() ) {
            videoURL = _url_;
        }
    }

    // 飞快TV
    if (title.includes("飞快TV") ) {
        if (_url_ = getFeiKuaiTVVideoURL() ) {
            videoURL = _url_;
        }
    }

    //白嫖者联盟 - baipiaozhe.ai - ai.baipiaozhe.com
    if (title.includes("白嫖者联盟") ) {
        if (_url_ = getBaiPiaoZheVideoURL() ) {
            videoURL = _url_;
        }
    }

    // 雪落影视
    if (title.includes("雪落影视") ) {
        if (_url_ = getXueLuoYinShiVideoURL() ) {
            videoURL = _url_;
        }
    }

    // 网飞猫
    if (title.includes("网飞猫") ) {
        if (_url_ = getWangFeiMaoVideoURL() ) {
            videoURL = _url_;
        }
    }

    // 网飞猫
    if (title.includes("皮皮妖") ) {
        if (_url_ = getPiPiYaoVideoURL() ) {
            videoURL = _url_;
        }
    }

    // 青空次元
    if (title.includes("青空次元") ) {
        if (_url_ = getSoraniVideoURL() ) {
            videoURL = _url_;
        }
    }



    // 支持的主机列表
    let macthHosts = [
        "666.cd",           //666导航站
        "cloudfront.net",   //666导航站-重定向 d199amw2tsk47k.cloudfront.net
    ]
    //只要包含当前域名就启用
    const matched = macthHosts.some(item =>
        host === item ||
        host.endsWith("." + item) ||
        host.includes(item) && !item.includes(".")
    );
    if (matched) {
        //666导航站
        if (_url_ = get666CdVideoURL() ) {
            videoURL = _url_;
        }
    }



    //17.RIP
    if (title.includes("EduStream Online") || host === "17.rip" ) {
        if (_url_ = get17RipVideoURL() ) {
            videoURL = _url_;
        }
    }





    //其它站点...

    return videoURL;
}
// -------------------------------通用标题与视频地址-------------------------------










// -------------------------------解析视频地址和标题-------------------------------

/**
 * 获取四色AV中视频的标题
 **/
function getFourColorAVTitle(){
    var title = ""

    // 获取包含 h1 元素的 div.main 元素
    const mainDiv = document.querySelector('.wrap > .main');
    // 确认 main 元素存在
    if (mainDiv) {
        // 在 mainDiv 内查找 h1 元素
        const h1Element = mainDiv.querySelector('h1');
        // 确认 h1 元素存在
        if (h1Element) {
            // 获取 h1 元素的文本内容
            const h1Content = h1Element.textContent.trim();
            console.log('四色AV站点:查找h1 内容为:', h1Content);
             title = h1Content
        } else {
            console.warn('四色AV站点:查找视频标题时未找到 h1 元素!');
        }
    } else {
        console.warn('四色AV站点:查找视频标题时未找到 .main 元素!');
    }
    
    return title;
}

/**
 * 获取四色AV中的m3u8链接地址
 **/
function getFourColorAVVideoURL(){
    // // Define a regular expression to match the playUrl variable pattern
    // const playUrlPattern = /var playUrl\s*=\s*"([^"]+)"/;

    // // Get all script tags on the page
    // const scripts = Array.from(document.getElementsByTagName('script'));

    // // Look for the script containing the playUrl variable
    // const matchedScript = scripts.find(script => playUrlPattern.test(script.textContent));

    // var m3u8Url = ""

    // // If found, extract the URL
    // if (matchedScript) {
    //     const match = matchedScript.textContent.match(playUrlPattern);
    //     m3u8Url = match && match[1];
    //     //将域名占位符转换正真正的主机 - 已弃用:不能写死
    //     m3u8Url = m3u8Url.replace(/\+\@movivecom\@\+/g, "qfvgzy.com");
    //     console.log('四色AV站点:m3u8 URL:', m3u8Url);
    // } else {
    //     console.warn('四色AV站点:m3u8 URL could not be found.');
    // }


    let videoURL = "";

    // 获取地址的新方法
    // 判断变量playUrl是否存在, 直接获取playUrl变量的值。
    if (typeof playUrl !== "undefined") {
        videoURL = playUrl;
    }
    videoURL = videoURL.trim();

    //将无法访问的域名替换成指定的域名
    videoURL = videoURL.replace(fourColorAVNoFechDomain, fourColorAVCustomDomian);



    // 解析并获取通用视频地址
    if (!videoURL) {
        videoURL = getGeneralVideoURL();
    }

    if (!videoURL) {
        //需要设置一下延迟才能将showToast显示出来
        setTimeout(() => {
            TKBaseSDK.showToast("\n视频地址还没解析到,请稍等几秒再试!\n\n",0);
            TKBaseSDK.copyToClipBoard("");
        }, 0);
    }


    console.log('四色AV站点:Video URL:', videoURL);
    return videoURL;
}



/**
 * 获取黄色仓库标题
 **/
function getHuangSeCangKuTitle(){
    var title = ""

    /**
     * trim():去掉字符串两端的空格和不可见字符(例如空格、换行、制表符等)
     * if (text) 自动判断 null, undefined, ""
     **/
    const quearyTitle = document.querySelector(".stui-warp-content h3.title")?.textContent?.trim();
    if (quearyTitle) {//查询到的表示有效
        title = quearyTitle;
    }
    console.log('黄色仓库站点:Video Title:', title);

    return title;
}

/**
 * 获取黄色仓库的视频地址
 * 站点1:huangsecangku.net
 * 站点2:hsck.net
 * 站点3:hsck.app
 **/
function getHuangSeCangKuVideoURL(){
    let videoURL = "";

    // 判断变量player_aaaa.url是否存在,并且直接根据player_aaaa.url的值来获取视频地址
    if (typeof player_aaaa !== "undefined" && player_aaaa.url !== undefined) {
        videoURL = player_aaaa.url;
    }
    videoURL = videoURL.trim();



    // 解析并获取通用视频地址
    if (!videoURL) {
        videoURL = getGeneralVideoURL();
    }

    if (!videoURL) {
        //需要设置一下延迟才能将showToast显示出来
        setTimeout(() => {
            TKBaseSDK.showToast("\n视频地址还没解析到,请稍等几秒再试!\n\n",0);
            TKBaseSDK.copyToClipBoard("");
        }, 0);
    }


    console.log('黄色仓库站点:Video URL:', videoURL);
    return videoURL;
}





/**
 * 获取天天动漫的视频标题
 * 站点:https://www.ttdm0.me/
 **/ 
function getTtdmTitle()
{
    /**
     * .com类型的天天动漫,
     * 如:http://ttdm1.com ; 这个站点就是常规的AV站点广告很多,该站点不是很推荐
     **/
    const isCom = new URL(location.href).hostname.endsWith(".com");
    if (isCom) {
        let title =  document.title;
        title = title.replace(" - 天天动漫", "");
        title = title.replace("天天动漫", "");
        console.log('.com天天动漫站点:Video Title:', title);
        return title
    }


    /**
     * .me类型的天天动漫站点 - 推荐:并且有正规和不正规的动漫
     * 例如:https://www.ttdm0.me/
     **/
    const element = document.querySelector('.module-play-list-link.active');
    var title = element.getAttribute('title');

    //替换掉多余的字符
    title = title.replace("播放", "");
    console.log('.me天天动漫站点:Video Title:', title);

    return title;
}

/**
 * 天天动漫获取视频地址
 **/
function getTtdmVideoURL()
{
    /**
     * .com类型的天天动漫,
     * 如:http://ttdm1.com ; 这个站点就是常规的AV站点广告很多,该站点不是很推荐
     **/
    const isCom = new URL(location.href).hostname.endsWith(".com");
    if (isCom) {
        //该站点使用DPlayer播放器,直接调用对应的DPlayer的通用解析方法即可。
        let videoURL = getDPlayerVideoURL();
        console.log('天天动漫.me类型站点:Video URL:', videoURL);
        return videoURL;
    }




    /**
     * .me类型的天天动漫站点 - 推荐:并且有正规和不正规的动漫
     * 例如:https://www.ttdm0.me/
     **/
    let videoURL = "";


    // 解析并获取通用视频地址
    if (!videoURL) {
        videoURL = getGeneralVideoURL();
    }

    if (!videoURL) {
        //需要设置一下延迟才能将showToast显示出来
        setTimeout(() => {
            TKBaseSDK.showToast("\n视频地址还没解析到,请稍等几秒再试!\n\n",0);
            TKBaseSDK.copyToClipBoard("");
        }, 0);
    }


    console.log('天天动漫.me类型站点:Video URL:', videoURL);
    return videoURL;
}





/**
 * 飞快TV - 获取标题
 **/
function getFriKuaiTVTitle()
{
    let title =  document.title;
    title = title.replace("- 飞快TV", "");
    title = title.replace("飞快TV", "");
    title = title.replace("正在播放", "");
    title = title.replace("正片", "");
    title = title.replace("高清无广告", "");
    title = title.replace(" ", "");

    console.log('飞快TV:Video Title:', title);
    return title;
}

/**
 * 飞快TV - 获取视频地址
 **/
function getFeiKuaiTVVideoURL()
{
    let videoURL = "";


    // 解析并获取通用视频地址
    if (!videoURL) {
        videoURL = getGeneralVideoURL();
    }

    if (!videoURL) {
        //需要设置一下延迟才能将showToast显示出来
        setTimeout(() => {
            TKBaseSDK.showToast("\n视频地址还没解析到,请稍等几秒再试!\n\n",0);
            TKBaseSDK.copyToClipBoard("");
        }, 0);
    }

    console.log('飞快TV:Video URL:', videoURL);
    return videoURL;
}





/**
 * 白嫖者联盟(baipiaozhe.ai,ai.baipiaozhe.com) -  获取标题 
 **/
function getBaiPiaoZheTitle()
{
    let title = "";

    //标题
    if (_title_ = document.querySelector('h1.truncate.text-lg.font-semibold').innerText) {
        title = _title_;
    }

    //集数
    if (_title_ = document.querySelector('button.movie-episode-quick-tab.h-10.shrink-0.snap-center.rounded-md.text-base.font-black.transition.movie-button-primary').title) {
        if (_title_ != "正片") {
            title += _title_;
        }
    }

    console.log('白嫖者联盟(baipiaozhe.ai - ai.baipiaozhe.com):Video Title:', title);
    return title;
}

/**
 * 白嫖者联盟(baipiaozhe.ai,ai.baipiaozhe.com) -  获取视频地址
 * ⚠️注意⚠️: 解析到的数据不一定正确,可能是解析方法失效了,也可能站点视频资源不能直接解析,比如视频地址来自专门的解析接口
 * 比如不正确视频地址:blob:https://ai.baipiaozhe.com/5d287cfa-5fe0-4f33-8ff1-e1b30d39fe6b
 **/
function getBaiPiaoZheVideoURL()
{
    let videoURL = getArtPlayerVideoURL();


    // 解析并获取通用视频地址
    if (!videoURL) {
        videoURL = getGeneralVideoURL();
    }

    if (!videoURL) {
        //需要设置一下延迟才能将showToast显示出来
        setTimeout(() => {
            TKBaseSDK.showToast("\n视频地址还没解析到,请稍等几秒再试!\n\n",0);
            TKBaseSDK.copyToClipBoard("");
        }, 0);
    }


    console.log('白嫖者联盟(baipiaozhe.ai - ai.baipiaozhe.com):', videoURL);
    return videoURL;
}





/**
 * 雪落影视 - 获取视频标题
 **/
function getXueLuoYinShiTitle()
{
    let title =  document.title;
    title = title.replace(" - 雪落影视 | 沉浸式影视平台", "");
    title = title.replace("沉浸式影视平台", "");
    title = title.replace("雪落影视", "");
    title = title.replace("在线观看", "");
    title = title.replace("|", "");
    title = title.replace(" ", "");

    console.log('雪落影视:Video Title:', title);
    return title;
}

/**
 * 雪落影视 - 获取视频地址
 **/
function getXueLuoYinShiVideoURL()
{
    let videoURL = getArtPlayerVideoURL();



    // 解析并获取通用视频地址
    if (!videoURL) {
        videoURL = getGeneralVideoURL();
    }

    if (!videoURL) {
        //需要设置一下延迟才能将showToast显示出来
        setTimeout(() => {
            TKBaseSDK.showToast("\n视频地址还没解析到,请稍等几秒再试!\n\n",0);
            TKBaseSDK.copyToClipBoard("");
        }, 0);
    }


    console.log('雪落影视:Video URL:', videoURL);
    return videoURL;
}






/**
 * 网飞猫 - 获取视频标题
 **/
function getWangFeiMaoTitle()
{
    let title =  document.title;
    title = title.replace("-网飞猫", "");
    title = title.replace("动漫", "");

    //集数
    if (_title_ = document.querySelector('a.episode-item.episode-item-active').innerText) {
        title += _title_;
    }


    console.log('网飞猫:Video Title:', title);
    return title;
}

/**
 * 网飞猫 - 获取视频地址
 **/
function getWangFeiMaoVideoURL()
{
    let videoURL = "";

    /**
     * 通过匹配script中的playSource变量的src属性来获取视频地址
     **/
    const reg = /playSource\s*=\s*\{[\s\S]*?src\s*:\s*["']([^"']+)["']/;
    const scripts = document.querySelectorAll('script[type="module"]');

    for (const script of scripts) {
        const match = script.textContent.match(reg);
        if (match) {
            console.log(match[1]);
            videoURL = match[1];
            break;
        }
    }



    // 解析并获取通用视频地址
    if (!videoURL) {
        videoURL = getGeneralVideoURL();
    }

    if (!videoURL) {
        //需要设置一下延迟才能将showToast显示出来
        setTimeout(() => {
            TKBaseSDK.showToast("\n视频地址还没解析到,请稍等几秒再试!\n\n",0);
            TKBaseSDK.copyToClipBoard("");
        }, 0);
    }


    console.log('网飞猫:Video URL:', videoURL);
    return videoURL;
}





/**
 * 皮皮妖 - 获取视频标题
 **/
function getPiPiYaoTitle()
{
    let title =  document.title;
    title = title.replace(" - 皮皮妖", "");
    title = title.replace(" - 高清资源", "");
    title = title.replace("皮皮妖", "");
    title = title.replace("高清资源", "");
    title = title.replace("在线播放", "");
    title = title.replace("高清", "");

    console.log('皮皮妖:Video Title:', title);
    return title;
}

/**
 * 皮皮妖 - 获取视频地址
 **/
function getPiPiYaoVideoURL()
{
    let videoURL = "";

    //站点解析方案
    if (_videoURL_ = document.querySelector('div.video-wrapper > video')?.src) {
        if (isVideoURL(_videoURL_)) {
            videoURL = _videoURL_;
        }
    }

    // 解析并获取通用视频地址
    if (!videoURL) {
        videoURL = getGeneralVideoURL();
    }

    if (!videoURL) {
        //需要设置一下延迟才能将showToast显示出来
        setTimeout(() => {
            TKBaseSDK.showToast("\n视频地址还没解析到,请稍等几秒再试!\n\n",0);
            TKBaseSDK.copyToClipBoard("");
        }, 0);
    }


    console.log('皮皮妖:Video URL:', videoURL);
    return videoURL;
}



/**
 * 青空次元 - 获取视频标题
 **/
function getSoraniTitle()
{
    let title =  document.title;
    title = title.replace(" - 青空次元", "");
    title = title.replace("青空次元", "");
    title = title.replace("全集1080P动漫番剧免费在线观看", "");
    title = title.replace("免费在线观看", "");

    //集数
    if (_title_ = document.querySelector('a.episode-order-chip.episode-order-chip--active')?.title ) {
        title += _title_;
    }

    console.log('青空次元:Video Title:', title);
    return title;
}

/**
 * 青空次元 - 获取视频地址
 * 注意:
 * 1. 青空次元(www.sorani.net)获取的视频地址不能直接播放,需要附带referer: https://www.sorani.net/ 才能认证成功
 **/
function getSoraniVideoURL()
{
    let videoURL = "";

    // 解析并获取通用视频地址
    if (!videoURL) {
        videoURL = getGeneralVideoURL();
    }

    if (!videoURL) {
        //需要设置一下延迟才能将showToast显示出来
        setTimeout(() => {
            TKBaseSDK.showToast("\n视频地址还没解析到,请稍等几秒再试!\n\n",0);
            TKBaseSDK.copyToClipBoard("");
        }, 0);
    }


    console.log('青空次元:Video URL:', videoURL);
    return videoURL;
}




/**
 * 666导航站(666.cd) - 获取视频标题
 **/
function get666CdTitle()
{
    let title =  document.title;


    //这个站点获取不到视频地址,现在直接使用视频链接的名称
    let videoURL = get666CdVideoURL();
    if (videoURL) {
        let name = videoURL.split("/").pop().replace(/\.[^/.]+$/, "");
        if (name) {
            title = name;
        }

        // let url = "https://d3z5kg7imojxe.cloudfront.net/video/2022.07.12/gccp/LY-MD-055/LY-MD-055.m3u8";
        // let name = url.split("/").pop().replace(/\.[^/.]+$/, "");
        // console.log(name);

    }


    console.log('666导航站:Video Title:', title);
    return title;
}

/**
 * 666导航站(666.cd) - 获取视频地址
 **/
function get666CdVideoURL()
{
    let videoURL = "";

    // 解析并获取通用视频地址
    if (!videoURL) {
        videoURL = getGeneralVideoURL();
    }

    if (!videoURL) {
        //需要设置一下延迟才能将showToast显示出来
        setTimeout(() => {
            TKBaseSDK.showToast("\n视频地址还没解析到,请稍等几秒再试!\n\n",0);
            TKBaseSDK.copyToClipBoard("");
        }, 0);
    }


    console.log('666导航站:Video URL:', videoURL);
    return videoURL;
}




/**
 * 17.rip - 获取视频标题
 **/
function get17RipTitle()
{
    let title =  document.title;


    //标题
    //document.querySelector(".header-left > h1.h4")?.textContent.trim();
    //document.querySelector(".header-left > h1.h4")?.innerText.trim();
    //textContent:获取 DOM 中所有文本(不考虑 CSS 隐藏)
    //innerText:获取用户实际看到的文本(会考虑样式)
    //对于标题这种场景,二者都可以。通常推荐 textContent.trim()。
    if (_title_ = document.querySelector(".header-left > h1.h4")?.innerText.trim() ) {
        title = _title_;
    }

    console.log('17.RIP:Video Title:', title);
    return title;

}

/**
 * 17.rip - 获取视频地址
 **/
function get17RipVideoURL()
{
    let videoURL = "";

    // 解析并获取通用视频地址
    if (!videoURL) {
        videoURL = getGeneralVideoURL();
    }

    if (!videoURL) {
        //需要设置一下延迟才能将showToast显示出来
        setTimeout(() => {
            TKBaseSDK.showToast("\n视频地址还没解析到,请稍等几秒再试!\n\n",0);
            TKBaseSDK.copyToClipBoard("");
        }, 0);
    }


    console.log('17.RIP:Video URL:', videoURL);
    return videoURL;
}




// -------------------------------解析视频地址和标题-------------------------------


























// -------------------------------通用视频地址解析-------------------------------

/**
 * 功能:执行通用的视频解析方法 - 依次尝试不同的解析方法
 * 
 * ⚠️注意⚠️:
 * 1. 这种整理的通用解析方法一般只在iframe中执行即可(top中也可以),top中推荐使用getGeneralVideoURL执行获取。
 * 2. 这种解析方法不能确保一定能解析到视频地址。
 * 3. addPerformanceResourceTimingEventListener()监听操作不要在该方法中执行,一般在该方法之前添加一次即可。
 **/
function parseGeneralVideoURL()
{
    let videoURL = "";


    // DPlayer
    if (_videoURL_ = getDPlayerVideoURL()) {
        videoURL = _videoURL_;
    }

    // ArtPlayer
    if (!videoURL) {
        if (_videoURL_ = getArtPlayerVideoURL()) {
            videoURL = _videoURL_;
        }
    }

    // MuiPlayer
    if (!videoURL) {
        if (_videoURL_ = getMuiPlayerVideoURL()) {
            videoURL = _videoURL_;
        }
    }

    // BibiPlayer
    if (!videoURL) {
        if (_videoURL_ = getBibiPlayerVideoURL()) {
            videoURL = _videoURL_;
        }
    }

    // Performance Resource Timing
    if (!videoURL) {
        if (_videoURL_ = getPerformanceResourceTimingVideoURL()) {
            videoURL = _videoURL_;
        }
    }
    
    return videoURL;
}

/**
 * 功能:获取通视频链接如果没有将会执行parseGeneralVideoURL通用解析方法来获取视频地址,一般用于UI点击获取地址时执行。
 **/
function getGeneralVideoURL()
{
    let videoURL = "";

    // 1. 获取接收到的Performance Resource Timing记录中的数据
    if (videoURLFromPostMessage) {
        videoURL = videoURLFromPostMessage;
    }

    //2. 获取接收到的Performance Resource Timing Observer 监听到的数据
    if (!videoURLFromPostMessage && videoURLFromPerformanceObserver ) {
        videoURL = videoURLFromPerformanceObserver;
    }

    //3. 再次执行通用解析方法
    if (!videoURL) {
        videoURL = parseGeneralVideoURL();
    }


    /**
     * 发送通知触发通用视频解析方法 - 发送了通知后等待片刻即可再次后取通用视频地址
     * 说明:
     * 直接调用getGeneralVideoURL方法只会执行当前document中的解析方法;
     * 而发送通通知则会触发所有注册了对应通知的document中的解析方法 - 当前main和iframe document都注册了通知
     **/
    if (!videoURL) {
        postParseGeneralVideoURL();
    }

    return videoURL;
}





/**
 * 通用的使用了 DPlayer 播放器站点的视频地址解析
 * 注意:
 * 1. DPlayer播放器一般会被嵌入到iframe标签中;所以油猴脚本就需要能在iframe中运行即不能使用// @noframes
 * 2. 当然DPlayer也能在最顶层的document中;所有也需要再top document中运行
 * 3. 如果是在iframe中解析到的视频链接就需要通过window.top.postMessage将其发动top document中,因为如果网页中存在iframe就会造成跨域问题,并且iframe之间也有跨域问题。
 *
 * ⚠️注意⚠️: 解析到的数据不一定正确,可能是解析方法失效了,也可能是站点视频资源不能直接解析。比如:视频地址来自专门的解析接口。
 **/
function getDPlayerVideoURL()
{
    console.log(winPage(), "使用DPlayer通用解析方法,开始解释视频地址.........");
    let videoURL = ""

    /**
     * 解析:
     * <video class="dplayer-video dplayer-video-current">
     * 
     * ⚠️注意⚠️:该操作不一定能解析到数据
     **/
    videoURL = document.querySelector('video.dplayer-video.dplayer-video-current > source')?.src;


    /**
     * 当解析到的链接类型形如:blob:https://ai.baipiaozhe.com/df5e7ddd 的样式说明当前播放器使用了blob地址而不是真实播放地址 
     * 就可以现将videoURL置空,然后下一步尝试使用Performance Resource Timing获取视频地址。
     **/
    if ( videoURL && videoURL.startsWith("blob:") ) {
        console.log(winPage(), "当前站点使用了DPlayer播放器,解析到了blob地址,需要使用Performance Resource Timing解析:", videoURL);
        //解析到的blob地址就置空
        videoURL = "";
    }


    // 尝试获取Performance Resource Timing记录
    if (!videoURL) {
        videoURL = getPerformanceResourceTimingVideoURL();
    }


    //需要将获取的数据通过postMessage发送出去,因为如皋当前是在iframe中运行的top document中是获取不到的
    if (videoURL) {
        if (typeof postUrlToTopDocument === "function") {
            postUrlToTopDocument("getDPlayerVideoURL",videoURL);
        }
    }

    console.log(winPage(), '使用DPlayer通用解析方法,解析到的Video URL:', videoURL);
    console.log("");
    return videoURL;
}


/**
 * 通用的使用了 ArtPlayer 播放器站点的视频地址解析
 * 注意:
 * 1. ArtPlayer 播放器一般会被嵌入到iframe标签中;所以油猴脚本就需要能在iframe中运行即不能使用// @noframes
 * 2. 当然 ArtPlayer 也能在最顶层的document中;所有也需要再top document中运行
 * 3. 如果是在iframe中解析到的视频链接就需要通过window.top.postMessage将其发动top document中,因为如果网页中存在iframe就会造成跨域问题,并且iframe之间也有跨域问题。
 *
 * ⚠️注意⚠️: 解析到的数据不一定正确,可能是解析方法失效了,也可能是站点视频资源不能直接解析。比如:视频地址来自专门的解析接口。
 **/
function getArtPlayerVideoURL()
{
    console.log(winPage(), "当前站点使用了ArtPlayer播放器,开始解释视频地址.........");
    let videoURL = ""

    /**
     * 解析:
     * <div class="art-video-player">
     * <video class="art-video hoverZoomLink">
     * <video class="art-video">
     * 
     * ⚠️注意⚠️:该操作不一定能解析到数据
     **/
    videoURL = document.querySelector('video.art-video')?.src;


    /**
     * 当解析到的链接类型形如:blob:https://ai.baipiaozhe.com/df5e7ddd 的样式说明当前播放器使用了blob地址而不是真实播放地址 
     * 就可以现将videoURL置空,然后下一步尝试使用Performance Resource Timing获取视频地址。
     **/
    if ( videoURL && videoURL.startsWith("blob:") ) {
        console.log(winPage(), "当前站点使用了ArtPlayer播放器,解析到了blob地址,需要使用Performance Resource Timing解析:", videoURL);
        //解析到的blob地址就置空
        videoURL = "";
    }


    // 尝试获取Performance Resource Timing记录
    if (!videoURL) {
        videoURL = getPerformanceResourceTimingVideoURL();
    }


    //需要将获取的数据通过postMessage发送出去,因为如皋当前是在iframe中运行的top document中是获取不到的
    if (videoURL) {
        if (typeof postUrlToTopDocument === "function") {
            postUrlToTopDocument("getArtPlayerVideoURL",videoURL);
        }
    }

    console.log(winPage(), '当前站点使用了ArtPlayer播放器,解析到的Video URL:', videoURL);
    console.log("");
    return videoURL;
}



/**
 * 通用的使用了 MuiPlayer 播放器站点的视频地址解析
 * 注意:
 * 1. MuiPlayer 播放器一般会被嵌入到iframe标签中;所以油猴脚本就需要能在iframe中运行即不能使用// @noframes
 * 2. 当然 MuiPlayer 也能在最顶层的document中;所有也需要再top document中运行
 * 3. 如果是在iframe中解析到的视频链接就需要通过window.top.postMessage将其发动top document中,因为如果网页中存在iframe就会造成跨域问题,并且iframe之间也有跨域问题。
 *
 * ⚠️注意⚠️: 
 * 1. 解析到的数据不一定正确,可能是解析方法失效了,也可能是站点视频资源不能直接解析。比如:视频地址来自专门的解析接口。
 * 2. 
 **/
function getMuiPlayerVideoURL()
{
    console.log("使用MuiPlayer通用解析方法,开始解释视频地址.........");
    // let videoURL = "";


    // 通过查询class,id的标识来查询获取链接,其中带#的表示是一个ID
    const player = document.querySelector(
        "#mui-player, .m-player, .player-wrapper, #mplayer-media-wrapper"
    );
    videoURL = player?.querySelector("video")?.src;

    // //需要检测视频地址的合法性 - 需要根据不同的播放器进行调节
    // if (_videoURL_ && isVideoURL(_videoURL_)) {
    //     videoURL = _videoURL_
    // }



     /**
     * 当解析到的链接类型形如:blob:https://ai.baipiaozhe.com/df5e7ddd 的样式说明当前播放器使用了blob地址而不是真实播放地址 
     * 就可以现将videoURL置空,然后下一步尝试使用Performance Resource Timing获取视频地址。
     **/
    if ( videoURL && videoURL.startsWith("blob:") ) {
        console.log(winPage(), "当前站点使用了MuiPlayer播放器,解析到了blob地址,需要使用Performance Resource Timing解析:", videoURL);
        //解析到的blob地址就置空
        videoURL = "";
    }


    // 尝试获取Performance Resource Timing记录
    if (!videoURL) {
        videoURL = getPerformanceResourceTimingVideoURL();
    }


    //需要将获取的数据通过postMessage发送出去,因为如皋当前是在iframe中运行的top document中是获取不到的
    if (videoURL) {
        if (typeof postUrlToTopDocument === "function") {
            postUrlToTopDocument("getMuiPlayerVideoURL",videoURL);
        }
    }


    console.log('使用MuiPlayer通用解析方法,解析到的Video URL:', videoURL);
    console.log("");
    return videoURL;
}




/**
 * 通用的使用了 BibiPlayer 播放器站点的视频地址解析
 * 注意:
 * 1. BibiPlayer播放器一般会被嵌入到iframe标签中;所以油猴脚本就需要能在iframe中运行即不能使用// @noframes
 * 2. 当然BibiPlayer也能在最顶层的document中;所有也需要再top document中运行
 * 3. 如果是在iframe中解析到的视频链接就需要通过window.top.postMessage将其发动top document中,因为如果网页中存在iframe就会造成跨域问题,并且iframe之间也有跨域问题。 
 * 
 * ⚠️注意⚠️: 解析到的数据不一定正确,可能是解析方法失效了,也可能是站点视频资源不能直接解析。比如:视频地址来自专门的解析接口。
 * ⚠️处理⚠️: 还没处理blob:https://ai.baipiaozhe.com/df样式的解析链接
 **/
function getBibiPlayerVideoURL()
{
    console.log(winPage(), "使用BibiPlayer通用解析方法,开始解释视频地址.........");
    let videoURL = "";

    /**
     * 方案一:直接从BibiPlayer播放器的config中的url获取;
     * 如果站点使用了iframe那么直接使用window.config.url,config.url就获取不到数据了;
     * 这时就需要使用油猴脚本的unsafeWindow(他能访问iframe中的数据)功能了,就可以直接使用unsafeWindow.config?.url获取。
     * 
     * ⚠️注意⚠️:该操作不一定能解析到数据
     **/
    //获取当前的window如果是在油猴脚本中则优先使用unsafeWindow
    const win = typeof unsafeWindow !== "undefined" ? unsafeWindow : window;
    //直接获取script中的config变量再取其中的url属性
    if (url = win.config?.url) {
        console.log(winPage(), "BibiPlayer 通过script中的config变量中的url属性获取到的视频地址:",url);

        videoURL = url;
    }
    


    if (!videoURL) {
        /**
         * 方案二:直接使用document.title,location.href的数据来获取视频地址
         * 说明:
         * 1. 因为BibiPlayer的title和href有固定的标识,通过这些就可以判断当前top document或iframe中是否有BibiPlayer播放器
         * 例如:
         * document.title:哔哩哔哩播放器
         * location.href:https://play.ttfile.top/player/?url=https://play.modujx11.com/20260628/KW50rZAY/index.m3u8
         * 实现:可以先匹配标题:哔哩哔哩播放器 是否相等,如果匹配成功就可以直接获取location.href中的url的值。
         * 
         * ⚠️注意⚠️:该操作不一定能解析到数据
         **/
        let iframeTitle = document.title;
        let iframeHref = location.href;

        console.log(winPage(), "document.title:",document.title);
        console.log(winPage(), "location.href:",location.href);

        //通过title可以匹配当前节点是否是BibiPlayer播放器
        if ( iframeTitle == "哔哩哔哩播放器" ) {
            //解析href中的url参数就是视频地址了
            const url = new URL(location.href).searchParams.get("url");
            if (url) {
                console.log(winPage(), "BibiPlayer 通过iframe location.href params url获取的视频地址:",url);
                videoURL = url;
            }
        }
    }



     /**
     * 当解析到的链接类型形如:blob:https://ai.baipiaozhe.com/df5e7ddd 的样式说明当前播放器使用了blob地址而不是真实播放地址 
     * 就可以现将videoURL置空,然后下一步尝试使用Performance Resource Timing获取视频地址。
     **/
    if ( videoURL && videoURL.startsWith("blob:") ) {
        console.log(winPage(), "当前站点使用了BibiPlayer播放器,解析到了blob地址,需要使用Performance Resource Timing解析:", videoURL);
        //解析到的blob地址就置空
        videoURL = "";
    }


    // 方案三:尝试获取Performance Resource Timing记录
    if (!videoURL) {
        videoURL = getPerformanceResourceTimingVideoURL();
    }


    //需要将获取的数据通过postMessage发送出去,因为如皋当前是在iframe中运行的top document中是获取不到的
    if (videoURL) {
        if (typeof postUrlToTopDocument === "function") {
            postUrlToTopDocument("getBibiPlayerVideoURL",videoURL);
        }
    }



    console.log(winPage(), '使用BibiPlayer通用解析方法,解析到的Video URL:', videoURL);
    console.log("");
    return videoURL;
}









/**
 * 功能:通过Performance Resource Timing记录的请求获取视频地址。 -  ⚠️⚠️:该操作在main document 和 iframe document中都能运行且资源是相互隔离的
 * ⚠️⚠️:该方法基本上能获取所有网页中的视频资源且不区分使用什么播放器 - 因为只要能被Performance Resource Timing记录的请求都能获取。 
 * 说明:
 * 1. 这种方式就是获取浏览器的所有Resource请求记录数据,并且只要当前document的生命周期没有结束浏览器就会一直记录再执行操作就能获取所有的记录。
 * 2. 想要获取视频资源就需要对Performance Resource得到的资源进行筛选,筛选.m3u8,.mp4,.flv等类型的资源链接;但是一般不匹配.ts因为它一般为视频片段没意义。
 * 3. 筛选匹配时一般不要使用includes,因为它容易造成无匹配,比如:url.includes(".mp4")匹配到test.mp4.backup , url.includes(".webm")匹配到manifest.webmanifest
 * 4. Performance 是记录当前document生命周期内所有的 Resource 请求。就算切换了新视频那么旧的视频资源请求任然在Resource中;如果页面刷新和跳转那么Resource就会是新的记录。
 * 5. 如果是在iframe中运行,那么每次获取到的Resource资源一般都会刷新;因为iframe切换一般会是一个新的,而在主页面中运行并切换视频(比如换集数),主document生命周期没结束。
 * 6. 如果遇到当前document生命周期没结束,但视频资源切换了也需要清除旧的Resource资源,可以监听location.href变换时,使用 performance.clearResourceTimings() 手动清除Resource
 * 7. Performance 获取的资源最好是获取最后一条记录,因为他一定是最新的。
 * 
 * 
 * ⚠️注意⚠️:
 * 1. ⚠️该操作可以用在main document 和 iframe document中;需要注意跨域资源访问限制问题,即只能获取各自操作域内的资源数据。
 * 2. 只能获取当前document中的资源,不能获取其它document中的资源。
 * 3. 同一个视频可能有很多资源链接,比如有的网站支持切换视频源,这样就会有多个视频资源了。这是就需要判断是去第一条还是最后一条数据;如果区第一条数据就需要在location.href切换时需要执行performance.clearResourceTimings()清理操作。
 * 4. 该操作也不能保证一定能够获取到视频资源。
 * 5. ⚠️先获取到的资源的质量一般要好于后面后取的。
 * 6. ⚠️如果在当前页面切换视频时需要performance.clearResourceTimings();执行清理操作,防止获取到旧数据(取获取到资源中的第一条资源时可能会出现)。
 * 7. ⚠️有些视频链接是加密的,即使获取到链接也无法单独拿出来使用。
 **/
function getPerformanceResourceTimingVideoURL()
{
    console.log(winPage(), "使用Performance Resource Timing通用解析方法,开始解释视频地址.........");
    let _urls_ = [];


    //获取并筛选匹配视频链接
    _urls_ = performance.getEntriesByType("resource")
    .map(e => e.name)
    .filter(url =>
        // url.includes(".m3u8") ||
        // url.includes(".mp4") ||
        // url.includes(".flv") ||
        // url.includes(".mov") ||
        // url.includes(".webm") ||
        // url.includes(".ts") ||
        // url.includes("playlist") ||
        // url.includes("segment") 


        // /\.(m3u8|mp4|flv|mov|webm)(\?|$)/i.test(url)
        // /\.(m3u8|mp4|flv|mov|webm)$/i.test(url) 
        // /\.(m3u8|mp4|flv|mov|webm)(\?|$)/i.test(url) && !/[?&]url=https?:\/\//i.test(url)
        // /\.(m3u8|mp4|flv|mov|webm)$/i.test( (new URL(url)).pathname ) 

        isVideoURL(url)
    );

    //将urls按顺序去重
    let urls = [...new Set(_urls_)];


    //获取第一条数据 - 资源一般最好。 如果切换集数需要先clearResourceTimings()清除一下,否者后面依然取的是上一级的数据。
    let firstUrl = urls.at(0);
    //获取最后一条数据 - 资源一般没有那么好。 这样即使切换了集数最后获取的数据也是最新的。
    let lastUrl =  urls.at(-1);
    console.log(winPage(), "使用Performance Resource Timing通用解析方法, 解析到的所有urls:",urls);
    console.log(winPage(), "使用Performance Resource Timing通用解析方法, 解析到的第一个url:",firstUrl);
    console.log(winPage(), "使用Performance Resource Timing通用解析方法, 解析到的最后一个url:",lastUrl);


    let videoURL = firstUrl;


    //需要将获取的数据通过postMessage发送出去,因为如皋当前是在iframe中运行的top document中是获取不到的
    if (videoURL) {
        if (typeof postUrlToTopDocument === "function") {
            postUrlToTopDocument("getPerformanceResourceTimingVideoURL",videoURL);
        }
    }

    console.log(winPage(), "使用Performance Resource Timing通用解析方法,解析到的视频资源数量:",urls.length,"- 提示:当前提取的第一个视频资源");
    console.log(winPage(), '使用Performance Resource Timing通用解析方法,解析到的Video URL:', videoURL);
    console.log("");


    return videoURL;
}

/**
 * ⚠️⚠️: 在当前document生命周期中清除Performance Resource Timing记录的请求数据
 **/
function clearPerformanceResourceTiming()
{
    console.log(winPage(),"清除当前document生命周期中Performance Resource Timing记录数据......");

    //清除Performance Resource Timing  - 需要注意时机
    performance.clearResourceTimings();
}







// -------------------------------通用视频地址解析-------------------------------









// -------------------------------数据发送-------------------------------

/**
 * 通过window.top.postMessage将数据发送到top document
 * name: 用于标识是谁发送的数据
 * url:  发送的视频地址
 **/
function postUrlToTopDocument(name,url)
{
    console.log(winPage(),"将数据发送到Top Document! name:",name," url:",url);

    //确保url数据存在
    if (url) {
        //需要将获取的数据通过postMessage发送出去,因为如皋当前是在iframe中运行的top document中是获取不到的
        window.top.postMessage({
            type:"VIDEO_URL",
            url:url
        }, "*");
    }else{
        console.log(winPage(),"将数据发送到Top Document! 状态:url的值不存在,取消发送!");
    }
    console.log("");
}

/**
 * addPerformanceResourceTimingEventListener监听事件专用
 **/
function postPerformanceObserverUrlToTopDocument(name,url)
{
    console.log(winPage(),"将数据发送到Top Document! name:",name," url:",url);

    //确保url数据存在
    if (url) {
        //需要将获取的数据通过postMessage发送出去,因为如皋当前是在iframe中运行的top document中是获取不到的
        window.top.postMessage({
            type:"Performance_Observer_VIDEO_URL",
            url:url
        }, "*");
    }else{
        console.log(winPage(),"将数据发送到Top Document! 状态:url的值不存在,取消发送!");
    }
    console.log("");
}


/**
 * 发送通知触发通用视频解析操作:postParseGeneralVideoURL()
 * 注意:该通知会触发所有添加了监听的document,即main和iframe document都会触发。
 **/ 
function postParseGeneralVideoURL()
{
    console.log(winPage(),"发送通知,触发执行parseGeneralVideoURL通用解析方法!");
    console.log("");

    //需要将获取的数据通过postMessage发送出去,因为如皋当前是在iframe中运行的top document中是获取不到的
    window.top.postMessage({
        type:"Parser_General_Video_URL",
        url:"Trigger Event"
    }, "*");
}

// -------------------------------数据发送-------------------------------






// -------------------------------数据监听-------------------------------

/**
 * 在main document中添加监听;用于接收从 main 和 iframe document中发送的数据。
 * 其中:
 * 1. 该函数在main document中添加一次即可。
 * 2. VIDEO_URL地址用变量videoURLFromPostMessage来接送
 * 
 **/
function addMainDocumentEventListener()
{
    console.log(winPage(), "AddMainDocumentEventListener.......");

    /**
     * 添加监听用于接收window.top.postMessage发送的数据
     **/
    window.addEventListener(
        "message",
        e=>{
            //消息类型,用于区分不同的消息
            let type = e.data.type;
            //消息的值,用于具体的数据
            let value = e.data.url;

            // 需要消息中的值
            if (value) {
                //用于接收一些通用解析方法解析到数据
                if(type==="VIDEO_URL"){
                    //赋值给最顶层top document中的变量
                    videoURLFromPostMessage =  value;

                    console.log(winPage(), "videoURLFromPostMessage - 收到视频地址:", value);
                }

                //接收Performance Resource Timing Observer 发送的数据
                if(type==="Performance_Observer_VIDEO_URL"){
                    //赋值给最顶层top document中的变量
                    videoURLFromPerformanceObserver =  value;

                    console.log(winPage(), "videoURLFromPerformanceObserver - 收到视频地址:", value);
                }
            }



            /**
             * 不需要消息中的参数值
             **/

            //触发通用视频解析方法
            if(type==="Parser_General_Video_URL"){
                //触发通用视频解析方法
                parseGeneralVideoURL();

                console.log(winPage(), "收到Parser_General_Video_URL通知 - 触发执行通用视频解析方法:parseGeneralVideoURL()..........");
                console.log("");
            }

        }
    );



    // Performance Resource Timing  Observer
    addPerformanceResourceTimingEventListener();


    //添加XMLHttpRequest监听 - 这个监听还有些问题
    //addXMLPlayerEventListener();
}



/**
 * 通过监听XMLHttpRequest的方式来解析视频地址
 * 注意:
 * 1. 该方法只能监听使用了XMLHttpRequest的播放器
 * 2. 该监听方式也可能会(没测试)有跨域问题,比如资源跨域,iframe跨域等;出现跨域问题就会有资源访问限制。
 **/
function addXMLPlayerEventListener()
{
    console.log(winPage(), '添加XMLHttpRequest 监听事件........');

    const originalOpen = XMLHttpRequest.prototype.open;

    XMLHttpRequest.prototype.open = function (method, url) {
        console.log(winPage(), "XMLHttpRequest Listener XHR:", url);

        // if (typeof url === "string" && isVideoURL(url) ) { //isVideoURL(url) -> url并不能保证能构建成URL对象
        if (typeof url === "string" && /\.(m3u8|mp4|flv|webm|wmv|mov|m4s|mpd)(\?|$)/i.test(url) ) {
            console.log(winPage(), "找到视频资源:", url);

            //需要将获取的数据通过postMessage发送出去,因为如皋当前是在iframe中运行的top document中是获取不到的
            if (typeof postPerformanceObserverUrlToTopDocument === "function") {
                postPerformanceObserverUrlToTopDocument("addXMLPlayerEventListener",url);
            }

            // 停止监听,恢复原始方法
            // XMLHttpRequest.prototype.open = originalOpen;
        }

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



/**
 * 功能: 通过监听的方式获取Performance Resource Timing中记录的最新数据
 * ⚠️⚠️:该方法基本上能获取所有网页中的视频资源且不区分使用什么播放器 - 因为只要能被Performance Resource Timing记录的请求都能获取。 
 * 注意:
 * 1. Performance Resource Timing每记录一次数据,就会触发一次监听操作。这就会导致该操作每次存储的是否最新的一条数据;因为之前存储的数据会被后监听到的数据覆盖。
 * 2. 该方法与getPerformanceResourceTimingVideoURL的区别是:
 *    - 当前document有都少请求被记录就会触发多少次监听事件。
 *    - getPerformanceResourceTimingVideoURL操会会获取当前document周期内所有记录的请求数据。
 * 3. ⚠️该操作可以用在main document 和 iframe document中;需要注意跨域资源访问限制问题,即只能获取各自操作域内的资源数据。
 * 4. ⚠️先获取到的资源的质量一般要好于后面后取的。
 **/
function addPerformanceResourceTimingEventListener()
{
    console.log(winPage(), '添加Performance Resource Timing 监听事件........');

    //添加Performance监听
    const observer = new PerformanceObserver(list => {
        for (const entry of list.getEntries()) {
            let url = entry.name;
            let u = new URL(url);

            //匹配视频资源链接
            if ( isVideoURL(url) ) {
                console.log(winPage(), 'Performance Resource Timing 监听到了数据:',url);

                //需要将获取的数据通过postMessage发送出去,因为如皋当前是在iframe中运行的top document中是获取不到的
                if (typeof postPerformanceObserverUrlToTopDocument === "function") {
                    postPerformanceObserverUrlToTopDocument("addPerformanceResourceTimingEventListener",url);
                }
            }

        }
    });

    observer.observe({
        entryTypes:["resource"]
    });
}



/**
 * 通过定时器监听 当前页面地址切换时候 
 * 注意:
 * 1. 不要在所有站点添加,按照需要添加
 * 2. 一般只有使用videoURLFromPostMessage的值来获取视频地址的才会添加该方法。
 **/
function addLocationHrefChangeEventListener()
{

    //通过监听location.href来检查是否切换了iframe
    let lastUrl = location.href;
    setInterval(() => {
        //location.href发生了改变,说明进行了单点页面切换
        if (location.href !== lastUrl) { 
            lastUrl = location.href;
            console.log(winPage(), "location.href 发生变化:", lastUrl);

            
            //就可以重置videoURLFromPostMessage的值了,防止再新页面中获取就的videoURLFromPostMessage数据
            videoURLFromPostMessage = "";
            console.log(winPage(), "LocationHrefChangeEventListener:重置videoURLFromPostMessage的值!");


            videoURLFromPerformanceObserver = "";
            console.log(winPage(), "LocationHrefChangeEventListener:重置videoURLFromPerformanceObserver的值!");


            //清除Performance Resource Timing记录数据  -  注意这个清除操作
            clearPerformanceResourceTiming();
            console.log(winPage(), "LocationHrefChangeEventListener:清除Performance Resource Timing记录数据!");
        }
    }, 200);

}


// -------------------------------数据监听-------------------------------












// -------------------------------hentai e-xhentai.org 站点资源解析-------------------------------
/**
 * hentai e-xhentai.org站点,获取当前图集图片数量
 **/
function hentai_getAlbumPicCount(){
    const cells = document.querySelectorAll('td.gdt2');
    for (let cell of cells) {
        const text = cell.textContent.trim();
        if (/pages|页/i.test(text)) {
          const match = text.match(/\d+/);
          if (match) return parseInt(match[0], 10);
        }
    }
    return null;
}


// -------------------------------hentai e-xhentai.org 站点资源解析-------------------------------






// -------------------------------解析页面中的磁力链接-------------------------------
/**
 * 获取页面中的所有magnet磁力链接
 **/ 
function getMagnetLinks(){
    // 从整个 HTML 文档中提取磁力链接
    const htmlContent = document.documentElement.innerHTML;
    const magnetRegex = /magnet:\?xt=urn:[a-zA-Z0-9:]+/g;
    const magnetLinks = Array.from(new Set(htmlContent.match(magnetRegex) || []));
    console.log(magnetLinks);
    return magnetLinks;
}
// -------------------------------解析页面中的磁力链接-------------------------------










// -------------------------------其它-------------------------------
//获取当前页面定义的标识
function winPage()
{
     const page = window.self === window.top ? "Top Document - " : "Iframe Document - ";
     return page;
}

/**
 * 功能:判断链接是否是视频 
 * 注意:不一定完全正确
 **/
function isVideoURL(url)
{
    // url.includes(".m3u8") ||
    // url.includes(".mp4") ||
    // url.includes(".flv") ||
    // url.includes(".mov") ||
    // url.includes(".webm") ||
    // url.includes(".ts") ||
    // url.includes("playlist") ||
    // url.includes("segment") 


    const u = new URL(url);

    // ① path 是否是视频
    // const isVideoPath = /\.(m3u8|mp4|flv|webm|wmv|mov|m4s|mpd)$/i.test(u.pathname);
    const isVideoPath = /\.(m3u8|mp4|flv|webm|wmv|mov|m4s|mpd)(\?|$)/i.test(u.pathname);

    // ② filename 是否是视频
    const disposition = decodeURIComponent(
        u.searchParams.get("response-content-disposition") || ""
    );

    const isVideoFilename = /\.(m3u8|mp4|flv|webm|wmv|mov|m4s|mpd)/i.test(disposition);

    // ③ 排除播放器 - https://play.ttfile.top/player/?url=https://play.modujx10.com/video/index.m3u8
    const isPlayer = u.searchParams.has("url");

    if ((isVideoPath || isVideoFilename) && !isPlayer) {
        // console.log("✅✅检测的链接是视频链接! url:", url);
        return true;
    }else{
        // console.log("😁😁检测的链接不是视频链接! url:", url);
        return false;
    }
}


// -------------------------------其它-------------------------------











// -------------------------------处理文件名合规性-------------------------------

/**
 * 功能:将字符串中不能作为文件名的字符替换成"_",并且如果存在英文的双引号"替换成英文的单引号'
 * replace(/"/g, '“');      //正则表达式
 * replaceAll('"', '“');    //直接替换函数
 **/
function sanitizeFileName(filename) {
    //使用正则表达式将"替换成'
    var name = filename;
    //去掉字符串两端的空格和不可见字符(例如空格、换行、制表符等)
    name = name.trim();

    name = name.replace(/"/g, "'");     //注意:这儿使用的正则表达式无法将"替换成',然后再调用这个函数的外部直接对字符串操作又可以,不知道是什么原因。所以直接使用replaceAll方法进行替换。
    name = name.replaceAll('"', "'");
    name = name.replaceAll(':', ":");
    name = name.replaceAll('”', "'");
    name = name.replaceAll('!', "!");

    // name = name.replaceAll('/', "|");
    name = name.replaceAll('/', "⁄");

    //将不能作为文件名的字符替换为:_
    name = name.replace(/[:]+/g, '_');  // -osx
    // name = name.replace(/[\/\\:*?"<>|]+/g, '_');  // win

    return name
}

/**
 *  功能:将字符串中不能作为路径的字符替换成”_“
 **/ 
function sanitizePath(path) {
  return path.replace(/[\/\\:*?"<>|]+/g, '_');  // 替换为单个下划线
}

// -------------------------------处理文件名合规性-------------------------------





// -------------------------------文字生成图片-------------------------------
/**
 * 功能:将文字生成PNG图片
 * text: 文字内容
 * width: 图片宽度
 * height: 图片高度
 * 使用示例:
 const img = document.createElement("img");
 img.src = textToImage("Copy yt-dlp-n",128,44);
 document.body.appendChild(img);
 **/ 
function textToImage(text, width, height) {
  const canvas = document.createElement("canvas");
  canvas.width = width;
  canvas.height = height;
  const ctx = canvas.getContext("2d");

  // 设置背景填充颜色,需要执行fillRect,fill等方法才会生效
  ctx.fillStyle = "#1ca7ee";


  // // 填充背景为矩形 
  // ctx.fillRect(0, 0, width, height);


  // // 填充背景为圆形
  // // 获取圆形半径(取宽高中的最小值的一半)
  // const radius = Math.min(width, height) / 2;
  // const centerX = width / 2;
  // const centerY = height / 2;
  // ctx.beginPath();
  // ctx.arc(centerX, centerY, radius, 0, Math.PI * 2);
  // ctx.fill();


  // 绘制文字
  ctx.fillStyle = "#fff";
  ctx.font = "700 14px Arial";
  ctx.textAlign = "center";
  ctx.textBaseline = "middle";
  // ✅文字绘制在画布中心
  ctx.fillText(text, canvas.width / 2, canvas.height / 2);

  return canvas.toDataURL("image/png");
}



/**
 * 功能:创建128x44的IMG对象
 * text:图片显示的内容
 **/
function createIMGElement(text) {
 let imgItem = document.createElement("img");
 imgItem.src = textToImage(text,128,44);
 return imgItem;
}
// -------------------------------文字生成图片-------------------------------