Greasy Fork is available in English.

PotPlayer云盘-专供版

此脚本为《PotPlayer播放云盘视频》姊妹篇,需配合MediaPlayParse - PanVideo.as脚本使用。在potplayer中选择画质、字幕,迅雷云盘增加原画,阿里云盘增加时长。

// ==UserScript==
// @name         PotPlayer云盘-专供版
// @namespace    https://github.com/Bleu404/PotplayerPanVideoSV
// @version      1.1.0
// @description  此脚本为《PotPlayer播放云盘视频》姊妹篇,需配合MediaPlayParse - PanVideo.as脚本使用。在potplayer中选择画质、字幕,迅雷云盘增加原画,阿里云盘增加时长。
// @author       bleu
// @compatible   edge Tampermonkey
// @compatible   chrome Tampermonkey
// @compatible   firefox Tampermonkey
// @license      MIT
// @match        https://pan.xunlei.com/*
// @match        https://www.aliyundrive.com/*
// @icon         https://fastly.jsdelivr.net/gh/Bleu404/PRPO@latest/png/ppvsv.png
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_xmlhttpRequest
// @grant        GM_registerMenuCommand
// @grant        unsafeWindow
// @connect      *
// @connect      xunlei.com
// @connect      aliyundrive.com
// @connect      jianguoyun.com
// @connect      teracloud.jp
// @require      https://fastly.jsdelivr.net/npm/sweetalert2@11.1.0/dist/sweetalert2.all.min.js
// @require      https://fastly.jsdelivr.net/npm/bleutools@1.0.2/bleutools.min.js
// ==/UserScript==

(function () {
    'use strict';
    const ORGXHRSRH = XMLHttpRequest.prototype.setRequestHeader;
    let bleuc,contextMenu, itemsInfo, arryIndex, Option, observer,cloud;
    const flieTypeStr = ".wmv,.rmvb,.avi,.mp4,.mkv,.flv,.swf.mpeg4,.mpeg2,.3gp,.mpga,.qt,.rm,.wmz,.wmd,.wvx,.wmx,.wm,.mpg,.mpeg,mov,.asf,.m4v,";
    const tools = {
        getCloudName() {
            switch (document.domain) {
                case 'xunlei.com':
                    cloud = xunlei;
                    break;
                case 'pan.xunlei.com':
                    cloud = xunlei;
                    break;
                case 'www.aliyundrive.com':
                    cloud = aliyun;
                    this.hookXHRHeader();
                    break;
            }
        },
        checkFileType(name) {
            let type = name.toLowerCase().substring(name.lastIndexOf('.')) || "bleu"
            return flieTypeStr.indexOf(`${type},`) >= 0 ? true : false
        },
        async putFileInWebdav(name, info) {
            let header = {
                "authorization": `Basic ${btoa(`${bleuc.cun}:${bleuc.cpw}`)}`
            }
            let url = `https://${bleuc.cip}/PanPlaylist`;
            let method = bleuc.cip.indexOf('teracloud')>0?'GET':'PROPFIND';
            await bleu.XHR(method, url, undefined, header, undefined).then( () => {}
            ,async()=>{await bleu.XHR('MKCOL', url, undefined, header, undefined)})
            url = `https://${bleuc.cip}/PanPlaylist/${name}`;
            await bleu.XHR('PUT',url , info, header, 'xml').then(() => {
                bleu.swalInfo(`✅${name}`, 3000, 'center');
            }, () => bleu.swalInfo(`❌${name}`, 3000, 'center'))
        },
        checkConfig() {
            bleuc = JSON.parse(GM_getValue('bleuc') || null) || {cip: '',cun: '',cpw: ''};
            if (!(bleuc.cip != '' && bleuc.cun != '' && bleuc.cpw != '')) {
                bleu.swalInfo(`❗请先设置WEBDAV`, '', 'center')
                return false
            }
            return true
        },
        saveConfig() {
            let temp = document.querySelector('#cip').value.trim()
            temp = temp.charAt(temp.length - 1) === '/' ? temp.substring(0, temp.length - 1) : temp
            temp = temp.indexOf('https://') < 0 ? temp : temp.replace('https://', '')
            GM_setValue("bleuc", JSON.stringify({
                'cip': temp,
                'cun': document.querySelector('#cun').value.trim(),
                'cpw': document.querySelector('#cpw').value.trim(),
            }));
        },
        configHtml() {
            bleuc = JSON.parse(GM_getValue('bleuc') || null) || {cip: '',cun: '',cpw: ''};
            let html = `
                <div class="bleuc_config_item"><p>
                <div><label>主机:</label><input type="text" class="bleuc_inp" id="cip" value="${bleuc.cip}"/></div>
                <div><label>用户:</label><input type="text" class="bleuc_inp" id="cun" value="${bleuc.cun}"/></div>
                <div><label>密码:</label><input type="text" class="bleuc_inp" id="cpw" value="${bleuc.cpw}"/></div></p></div>
                `;
            return html;
        },
        hookXHRHeader() {
            XMLHttpRequest.prototype.setRequestHeader = function(header, value) {
                if(header == "x-signature"){
                    aliyun._signature=value;
                }
                return ORGXHRSRH.apply(this, arguments);
            }
        },
        cssStyle: `
            .bleuc_config_item{border-radius: 10px;font-size: 20px;margin: 12px 50px;color: #fff;background: linear-gradient(45deg,#12c2e9, #c471ed, #f64f59);box-shadow: 2px 2px 2px 1px rgba(0, 0, 0, 0.2);}
            .bleuc_config_item label{font-size: 15px}
            .bleuc_config_item input.bleuc_inp{margin: 0px 10px;font-size: 15px;background: linear-gradient(45deg,#12c2e9, #c471ed, #f64f59);border-style:none;color:black;width:200px}
            .bleuc_config_item p{text-align: left;margin: 0px 20px;}`,

    }
    const xunlei = {
        addTag() {
            if (contextMenu.innerText.match(/PotPlayer/)) return
            let ul = document.createElement('ul');
            ul.innerHTML = `<a id="bleuReSave" class="pan-dropdown-menu-item">PotPlayer打开</a>`;
            contextMenu.firstChild.prepend(ul.firstChild);
            main.addClickEvent();
        },
        getselectFilesInfo() {
            let temp = document.querySelectorAll('li.SourceListItem__item--XxpOC.SourceListItem__active--4U0f4');
            temp.forEach((item) => {
                this._pushItem(item.__vue__.info);
            })
        },
        async updateFile(item) {
            Option["list"].push({
                "title": item.name,
                "url": `https://api-pan.xunlei.com/drive/v1/files/${item.id}`
            });
        },
        async openNextDir(item) {
            let url = `https://api-pan.xunlei.com/drive/v1/files?limit=100&parent_id=${item.id}&filters={"phase":{"eq":"PHASE_TYPE_COMPLETE"},"trashed":{"eq":false}}&with_audit=true`;
            await bleu.XHR('GET', url, undefined, Option.header).then((res) => {
                arryIndex++;
                res.files.forEach((item) => {
                    xunlei._pushItem(item);
                })
            },()=>{
                bleu.swalInfo("❗进出目录之后重新转存", '', 'center');
                throw "PPVSV:迅雷-超时,重新转存";
            })
        },
        findContext(node) {
            if (node.className === 'pan-content') {
                node = node.querySelector('div.pan-dropdown-menu.context-menu');
                if (!node) return;
                contextMenu = node;
                xunlei.addTag();
            }
        },
        closeMenu() {},
        _pushItem(temp) {
            if (!itemsInfo[arryIndex]) itemsInfo[arryIndex] = [];
            if (temp.kind === 'drive#file' && !tools.checkFileType(temp.name)) return
            let itemInfo = {
                'id': temp.id,
                'isdir': temp.kind === 'drive#file' ? false : true,
                'name': temp.name,
            };
            itemsInfo[arryIndex].push(itemInfo);
        },
        getHeaderInfo() {
            Option.header = {};
            Option.header.withCredentials = false;
            Option.header['content-type'] = 'application/json';
            for (let key in localStorage) {
                let temp = localStorage.getItem(key)
                if (key.indexOf('credentials') === 0) {
                    Option.header["Authorization"] = JSON.parse(temp).token_type + ' ' + JSON.parse(temp).access_token;
                    Option.header["token_type"] = JSON.parse(temp).token_type;
                    Option.header["access_token"] = JSON.parse(temp).access_token;
                    Option.header["refresh_token"] = JSON.parse(temp).refresh_token;
                    Option["clientid"] = key.substring(key.indexOf('_') + 1);
                }
                if (key.indexOf('captcha') === 0)
                    Option.header['x-captcha-token'] = JSON.parse(temp).token
                if (key === 'deviceid')
                    Option.header['x-device-id'] = temp.substring(temp.indexOf('.') + 1, 32 + temp.indexOf('.') + 1)
            }
        },
        async finallyFunc() {
            Option.header["clientid"] = Option["clientid"];
            await tools.putFileInWebdav('panvideo.txt', JSON.stringify(Option));
            unsafeWindow.location.href = `potplayer://panvideo##xunlei##https://${bleuc.cip}/PanPlaylist/panvideo.txt##${bleuc.cun}##${bleuc.cpw}`;
        }
    }
    const aliyun = {
        addTag() {
            if (contextMenu.innerText.match(/PotPlayer|新建/)) return
            let ul = document.createElement('ul');
            ul.innerHTML = `<li id="bleuReSave" class="ant-dropdown-menu-item ant-dropdown-menu-item-only-child" role="menuitem"><div class="outer-menu--ihDUR"><div data-confirm="false" class="menu-wrapper--1ZYh_" data-spm-anchor-id="aliyundrive.drive.0.i11.40516c75ahPUGN"><div class="menu-name--1F5vk" data-spm-anchor-id="aliyundrive.drive.0.i12.40516c75ahPUGN">PotPlayer打开</div></div></div></li>`;
            contextMenu.prepend(ul.firstChild);
            main.addClickEvent();
        },
        getselectFilesInfo() {
            let temp = document.querySelectorAll('div[data-index]')
            let attrName;
            for(let attr in temp[0]){
                if(attr.indexOf('__reactFiber')==0){
                    attrName = attr;
                    break;
                }
            }
            temp.forEach((item)=>{
                if(item.querySelector('input')&&item.querySelector('input').checked){
                    let value = item[attrName].return.pendingProps;
                    aliyun._pushItem(value.data[value.index]||value.data[value.rowIndex][value.columnIndex]);
                }
            })
            
        },
        async updateFile(item) {
            Option["list"].push({
                "title": item.name,
                "url": "https://"+item.id
            });
        },
        async openNextDir(item) {
            let url = `https://api.aliyundrive.com/adrive/v3/file/list?jsonmask=next_marker%2Citems(name%2Cfile_id%2Cdrive_id%2Ctype%2Csize%2Ccreated_at%2Cupdated_at%2Ccategory%2Cfile_extension%2Cparent_file_id%2Cmime_type%2Cstarred%2Cthumbnail%2Curl%2Cstreams_info%2Ccontent_hash%2Cuser_tags%2Ctrashed%2Cvideo_media_metadata%2Cvideo_preview_metadata)`,
                token = JSON.parse(localStorage.getItem('token')),
                data = {
                    'drive_id': token.default_drive_id,
                    'parent_file_id': item.id,
                    'limit': 100,
                },
                header = {
                    'x-canary': 'client=web,app=adrive,version=v2.4.0',
                    'x-device-id': document.cookie.match(/cna=([^;]*)/)[1],
                    authorization: `${token.token_type} ${token.access_token}`,
                    'x-signature':this._signature
                };
            await bleu.XHR('POST', url, JSON.stringify(data),header).then((res) => {
                arryIndex++;
                res.items.forEach((item)=>{
                    aliyun._pushItem(item);
                },()=>{
                    bleu.swalInfo("🔴💬刷新页面,重新获取", '', 'center');
                    throw "PPVSV:阿里-超时,重新转存";
                })
            })
        },
        _signature:'',
        findContext(node) {
            node = document.querySelector('ul.ant-dropdown-menu');
            if (!node) return;
            //observer.disconnect();
            contextMenu = node;
            aliyun.addTag();
        },
        closeMenu(){
            contextMenu.parentNode.className='ant-dropdown dropdown-menu--1KRbu ant-dropdown-placement-bottomLeft  ant-dropdown-hidden';
            contextMenu.parentNode.style.left='-578px'; 
            contextMenu.parentNode.style.top='-646px';
        },
        _pushItem(temp) {
            if(!itemsInfo[arryIndex]) itemsInfo[arryIndex]= [];
            if (temp.type==='file'&&temp.category!="video") return
            let itemInfo = {
                'id': temp.fileId||temp.file_id,
                'isdir': temp.type === 'file' ? false : true,
                'name': temp.name,
            };
            itemsInfo[arryIndex].push(itemInfo);
        },
        getHeaderInfo() {
            Option.header ={};
            let token = JSON.parse(localStorage.getItem('token'));
            Option.header["authorization"] =`${token.token_type} ${token.access_token}`;
            Option.header["drive_id"] =token.default_drive_id;
            Option.header["x-signature"]=this._signature;
            Option.header["x-device-id"]=document.cookie.match(/cna=([^;]*)/)[1];//decodeURIComponent(localStorage.getItem('APLUS_CNA').substring(9))
        },
        async finallyFunc(){
            await tools.putFileInWebdav('panvideo.txt', JSON.stringify(Option));
            unsafeWindow.location.href = `potplayer://panvideo##aliyun##https://${bleuc.cip}/PanPlaylist/panvideo.txt##${bleuc.cun}##${bleuc.cpw}`;
        }
    }
    const main = {
        init() {
            observer = new MutationObserver(function (mutations) {
                for (let mutation of mutations) {
                    if (mutation.type === 'childList') {
                        cloud.findContext(mutation.target);
                    }
                }
            });
            observer.observe(document, {
                'childList': true,
                'subtree': true
            });
        },
        addClickEvent() {
            let bleuButton = document.getElementById('bleuReSave');
            bleuButton.addEventListener('click', async function () {
                itemsInfo = [];
                arryIndex = 0;
                Option = {}, Option["list"] = [];
                if(!tools.checkConfig())return;
                cloud.closeMenu();
                cloud.getselectFilesInfo();
                cloud.getHeaderInfo();
                if (!itemsInfo[arryIndex]||itemsInfo[arryIndex].length === 0) {
                    bleu.swalInfo(`❌未选择文件转存!`, 3000, 'center')
                    return;
                }
                try {
                    await main.updateAllFiles(itemsInfo[arryIndex]);
                } catch (e) {
                    console.log(e);
                    return;
                }
                Option["list"].length!=0&&cloud.finallyFunc();
            })
        },
        async updateAllFiles(loopArry) {
            for (let index = 0; index < loopArry.length; index++) {
                if (!loopArry[index].isdir) {
                    await cloud.updateFile(loopArry[index]);
                } else {
                    await cloud.openNextDir(loopArry[index]);
                    await main.updateAllFiles(itemsInfo[arryIndex]);
                }
                bleu.sleep(800);
            }
        },
    };
    tools.getCloudName();
    //tools.checkConfig();
    bleu.addCssStyle(tools.cssStyle);
    GM_registerMenuCommand('配置WEBDAV', () => {
        bleu.swalUI('WEBDAV', tools.configHtml(), '400px').then(tools.saveConfig)
    }, 'w')
    main.init();
})();