Greasy Fork

Greasy Fork is available in English.

阿里云盘标清替换成最高画质

通过阿里云盘开放平台将原标清替换成最高画质(支持分享链接),打开视频自动播放,视频前进后退5秒,更多倍速播放以及记忆倍速,播放器工具栏置底并设置透明度,快捷键:home上一集,end下一集,enter全屏

目前为 2024-05-15 提交的版本,查看 最新版本

// ==UserScript==
// @name         阿里云盘标清替换成最高画质
// @namespace    http://tampermonkey.net/
// @version      1.0.4
// @description  通过阿里云盘开放平台将原标清替换成最高画质(支持分享链接),打开视频自动播放,视频前进后退5秒,更多倍速播放以及记忆倍速,播放器工具栏置底并设置透明度,快捷键:home上一集,end下一集,enter全屏
// @author       bygavin
// @match        https://www.aliyundrive.com/*
// @match        https://www.alipan.com/*
// @icon         https://img.alicdn.com/imgextra/i1/O1CN01JDQCi21Dc8EfbRwvF_!!6000000000236-73-tps-64-64.ico
// @license      MIT
// @grant        unsafeWindow
// @grant        GM_setValue
// @grant        GM_getValue
// ==/UserScript==

// 将localStorage内容转移到油猴脚本,以便两个域名共用,下个版本移除该段代码
['openapitoken', 'playbackRate', 'newurls', 'saveinfo', 'openapiclient_id'].forEach((el) => {
    const key = el.toString()
    const elvalue = localStorage.getItem(key)
    localStorage.removeItem(key)
    if (elvalue) {
        GM_setValue(key, elvalue)
    }
})

let openapiclient_id = GM_getValue('openapiclient_id') || '55091393987b4cc090b090ee17e85e0a'//该client_id是官方文档出现的,测试可用,也可自行申请替换
const openapicode_verifier = generateUUID(12, '-')
var newurl
var isfullscreen = false;
var device_list = {};
var time
var oldxhr = unsafeWindow.XMLHttpRequest
function newobj() { }
if (openapiclient_id) {
    //劫持xhr
    unsafeWindow.XMLHttpRequest = function () {
        let tagetobk = new newobj();
        tagetobk.oldxhr = new oldxhr();
        let handle = {
            get: function (target, prop, receiver) {
                if (prop === 'oldxhr') {
                    return Reflect.get(target, prop);
                }
                if (typeof Reflect.get(target.oldxhr, prop) === 'function') {
                    if (Reflect.get(target.oldxhr, prop + 'proxy') === undefined) {
                        target.oldxhr[prop + 'proxy'] = new Proxy(Reflect.get(target.oldxhr, prop), {
                            apply: function (target, thisArg, argumentsList) {
                                return Reflect.apply(target, thisArg.oldxhr, argumentsList);
                            }
                        });
                    }
                    return Reflect.get(target.oldxhr, prop + 'proxy')
                }
                const responseURL = target.oldxhr.responseURL
                if (responseURL.indexOf("/file/get_video_preview_play_info_by_share") > 0 && prop.indexOf('response') !== -1) {//播放分享链接
                    const response = target.oldxhr?.response || target.oldxhr?.responseText
                    var shareinfo = JSON.parse(response);
                    newurl = getsetnewurl(shareinfo.file_id)
                    if (!newurl) {
                        const share_token = target.oldxhr.__reqCtx__.headers["x-share-token"]
                        const saveinfo = savefile(shareinfo.file_id, share_token).responses[0].body
                        newurl = getVideoPreviewPlayInfo(saveinfo.drive_id, saveinfo.file_id)
                        deletefile(saveinfo.file_id)
                        newurl && getsetnewurl(shareinfo.file_id, newurl)
                    }
                    newurl && (shareinfo.video_preview_play_info.live_transcoding_task_list[1].url = newurl)
                    shareinfo = JSON.stringify(shareinfo);
                    if (newurl)
                        return shareinfo;
                }
                else if (responseURL.indexOf("/file/get_video_preview_play_info") > 0 && prop.indexOf('response') !== -1) {//播放自己云盘内容
                    const response = target.oldxhr?.response || target.oldxhr?.responseText
                    var res = JSON.parse(response);
                    newurl = getsetnewurl(res.file_id)
                    if (!newurl) {
                        newurl = getVideoPreviewPlayInfo(res.drive_id, res.file_id)
                        newurl && getsetnewurl(res.file_id, newurl)
                    }
                    newurl && (res.video_preview_play_info.live_transcoding_task_list[0].url = newurl)
                    res = JSON.stringify(res);
                    if (newurl)
                        return res;
                }
                else if (responseURL.indexOf("get_video_preview_play_info") > 0 && prop === "statusText") {//预览链接加载完成
                    morerate();
                }
                else if (responseURL.endsWith("/user/get") && prop.indexOf('response') !== -1) {//获取自己云盘的设备id
                    const response = target.oldxhr?.response || target.oldxhr?.responseText
                    const res = JSON.parse(response)
                    device_list.resource_drive_id = res.resource_drive_id
                    device_list.backup_drive_id = res.backup_drive_id
                }
                return Reflect.get(target.oldxhr, prop);
            },
            set(target, prop, value) {
                return Reflect.set(target.oldxhr, prop, value);
            },
            has(target, key) {
                return Reflect.has(target.oldxhr, key);
            }
        }
        let ret = new Proxy(tagetobk, handle);
        return ret;
    }
}

//通过阿里云盘开放平台获取视频信息
function getVideoPreviewPlayInfo(drive_id, file_id) {
    const access_token = getopenapitoken();
    var xhr = new oldxhr();
    xhr.open("POST", "https://openapi.aliyundrive.com/adrive/v1.0/openFile/getVideoPreviewPlayInfo", false);
    xhr.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
    xhr.setRequestHeader("Authorization", access_token);
    xhr.send(JSON.stringify({ "drive_id": drive_id, "file_id": file_id, "category": "live_transcoding", "url_expire_sec": 14400 }));
    if (xhr.status === 200) {
        return (JSON.parse(xhr.responseText)).video_preview_play_info?.live_transcoding_task_list.pop().url;
    } else {
        return (xhr.statusText);
    }
}

//通过阿里云盘开放平台获取视频下载链接
function getDownloadUrl(drive_id, file_id) {
    const access_token = getopenapitoken();
    var xhr = new oldxhr();
    xhr.open("POST", "https://openapi.aliyundrive.com/adrive/v1.0/openFile/getDownloadUrl", false);
    xhr.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
    xhr.setRequestHeader("Authorization", access_token);
    xhr.send(JSON.stringify({ "drive_id": drive_id, "file_id": file_id, "expire_sec": 14400 }));
    if (xhr.status === 200) {
        return (JSON.parse(xhr.responseText)).url;
    } else {
        return (xhr.statusText);
    }
}

//转存文件到备份盘的根目录
function savefile(sharefileid, share_token) {
    const token = JSON.parse(localStorage.getItem('token'))
    const saveinfo = JSON.parse(GM_getValue('saveinfo') || "{}")
    const shareid = location.pathname.split('/')[2]
    const savedata = {
        "requests": [
            {
                "body": {
                    "file_id": sharefileid,
                    "share_id": shareid,
                    "auto_rename": true,
                    "to_parent_file_id": saveinfo?.savefile_id || 'root',
                    "to_drive_id": saveinfo?.savedevice_id || token.default_drive_id
                },
                "headers": {
                    "Content-Type": "application/json"
                },
                "id": "0",
                "method": "POST",
                "url": "/file/copy"
            }
        ],
        "resource": "file"
    }
    var xhr = new oldxhr();
    xhr.open("POST", "https://api.aliyundrive.com/adrive/v3/batch", false);
    xhr.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
    xhr.setRequestHeader("Authorization", token.access_token);
    xhr.setRequestHeader("X-Share-Token", share_token);
    xhr.send(JSON.stringify(savedata));
    if (xhr.status === 200) {
        return (JSON.parse(xhr.responseText));
    } else {
        return (xhr.statusText);
    }
}

//删除刚刚转存的文件
function deletefile(file_id) {
    const token = JSON.parse(localStorage.getItem('token'))
    const saveinfo = JSON.parse(GM_getValue('saveinfo') || "{}")
    const deleteinfo = {
        "requests": [
            {
                "body": {
                    "drive_id": saveinfo?.savedevice_id || token.default_drive_id,
                    "file_id": file_id
                },
                "headers": {
                    "Content-Type": "application/json"
                },
                "id": file_id,
                "method": "POST",
                "url": "/file/delete"
            }
        ],
        "resource": "file"
    }
    var xhr = new oldxhr();
    xhr.open("POST", "https://api.aliyundrive.com/adrive/v3/batch", false);
    xhr.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
    xhr.setRequestHeader("Authorization", token.access_token);
    xhr.send(JSON.stringify(deleteinfo));
    if (xhr.status === 200) {
        return (JSON.parse(xhr.responseText));
    } else {
        return (xhr.statusText);
    }
}

//获取阿里云盘开放平台code
function getopenapicode() {
    const accesstk = JSON.parse(localStorage.getItem('token')).access_token
    var xhr = new oldxhr();
    var url = 'https://open.aliyundrive.com/oauth/users/authorize';
    var data = {
        scope: 'user:base,file:all:read,file:all:write',
        authorize: 1,
        drives: ['backup', 'resource']
    };
    var params = '?client_id=' + openapiclient_id + '&redirect_uri=oob&scope=user:base,file:all:read,file:all:write&code_challenge=' + openapicode_verifier + '&code_challenge_method=plain';
    xhr.open('POST', url + params, false);
    xhr.setRequestHeader('Content-Type', 'application/json');
    xhr.setRequestHeader('authorization', 'Bearer ' + accesstk);
    xhr.send(JSON.stringify(data));
    if (xhr.status === 200) {
        const codeurl = JSON.parse(xhr.responseText).redirectUri
        return codeurl.split('=')[1]
    } else {
        return (xhr.statusText);
    }
}

//获取阿里云盘开放平台token
function getopenapitoken() {
    const nowtime = new Date().getTime();
    const openapitoken = JSON.parse(GM_getValue('openapitoken') || '{}')
    if (openapitoken.access_token) {
        if (openapitoken.createdate + openapitoken.expires_in > nowtime) {
            return openapitoken.access_token
        }
    }
    const openapicode = getopenapicode()
    var xhr = new oldxhr();
    var url = 'https://openapi.aliyundrive.com/oauth/access_token';
    var data = {
        client_id: openapiclient_id,
        grant_type: 'authorization_code',
        code: openapicode,
        code_verifier: openapicode_verifier
    };
    xhr.open('POST', url, false);
    xhr.setRequestHeader('Content-Type', 'application/json');
    xhr.send(JSON.stringify(data));
    if (xhr.status === 200) {
        const tokejson = JSON.parse(xhr.responseText)
        tokejson.createdate = nowtime
        GM_setValue('openapitoken', JSON.stringify(tokejson))
        return tokejson.access_token;
    } else {
        return (xhr.statusText);
    }
}

//单双击功能
function clickanddbclick() {
    const video = document.querySelector("video");
    let timer = 0;
    let delay = 200;
    let prevent = false;

    video.addEventListener('click', function (event) {
        timer = setTimeout(function () {
            if (!prevent) {
                document.querySelector('.video-player--k1J-M .btn--UrTVT').click()
            }
            prevent = false;
        }, delay);
    });

    video.addEventListener('dblclick', function (event) {
        clearTimeout(timer);
        prevent = true;
        document.querySelector('.action--HeWYA:not([data-active])').click()
    });
}

//全屏以及自动播放
function setfullscreen() {
    if (isfullscreen && document.fullscreenElement === null) {
        document.querySelector('.action--HeWYA:not([data-active])').click()
    }
    document.querySelector('.video-player--k1J-M .btn--UrTVT').click()
    var elements = document.querySelectorAll('.list--5o17x li, .next--k9RTS');
    elements.forEach(function (element) {
        element.addEventListener('click', function () {
            isfullscreen = document.fullscreenElement !== null
        });
    });
    function videoEndHandler() {
        changevideo(1)
    }
    const video = document.querySelector("video");
    video.removeEventListener('ended', videoEndHandler, false);
    video.addEventListener('ended', videoEndHandler, false);

}

//更多播放倍率
function morerate() {
    let playbackRate = GM_getValue("playbackRate") || 1;
    let video = document.querySelector("video");
    if (video) {
        video.onplay = function () {
            video.playbackRate = playbackRate;
        };
    }
    const ul = document.querySelector('div[class^="drawer-list-grid"]');
    if (!ul || !video) {
        return;
    }
    else if (ul.childNodes.length > 7) {//确保只执行一次
        return
    }
    setfullscreen();
    clickanddbclick();
    const close = ul.parentElement.parentElement.previousElementSibling.children.item(1);
    let firstChild = [...ul.children].find(
        (el) => el.firstChild.textContent === "1.5 倍"
    );
    let secendChild = [...ul.children].find(
        (el) => el.firstChild.textContent === "0.75 倍"
    );
    const originChild = firstChild;
    const rates = ["12", "10", "8", "6", "5", "4", "3", "2.5", "2", "0.5", "0.25", "0.1"];
    rates.forEach((rate) => {
        if (parseInt(rate) < 2) {
            const cloneNode = secendChild.cloneNode(true);
            cloneNode.firstChild.innerHTML = `${rate} 倍`;
            ul.insertBefore(cloneNode, secendChild);
            secendChild = cloneNode;
        }
        else {
            const cloneNode = firstChild.cloneNode(true);
            cloneNode.firstChild.innerHTML = `${rate} 倍`;
            ul.insertBefore(cloneNode, firstChild);
            firstChild = cloneNode;
        }
    });
    ul.insertBefore(originChild, firstChild);
    const backRateNodes = [...ul.children];
    const changeSelectColor = (select) => {
        setTimeout(() => {
            backRateNodes.forEach((item) => {
                item.dataset.isCurrent = "false";
            });
            if (!select.dataset.isCurrent) {
                select.parentElement.dataset.isCurrent = "true";
            } else {
                select.dataset.isCurrent = "true";
            }
        });
    };
    const currentTarget = [...ul.children].find(
        (el) => el.firstChild.textContent === `${playbackRate} 倍`
    );
    changeSelectColor(currentTarget);
    const tagNodes = [...ul.children];
    for (let i = 0; i < 3; i++) {
        const node = tagNodes[tagNodes.length - 1 - i];
        if (node && node.parentNode) {
            node.parentNode.removeChild(node);
        }
    }
    ul.addEventListener("click", (e) => {
        const target = e.target;
        playbackRate = target.textContent.replace(" 倍", "");
        video.playbackRate = playbackRate;
        GM_setValue("playbackRate", playbackRate);
        changeSelectColor(target);
        close.click();
    });
    isChanged = true;
}

//获取或设置最高画质m3u8链接
function getsetnewurl(file_id, setnewurl) {
    const today = new Date().toLocaleDateString()
    const newurls = JSON.parse(GM_getValue('newurls') || '{}')
    if (!newurls.date)
        newurls.date = today
    if (setnewurl) {
        newurls[file_id] = setnewurl;
        GM_setValue('newurls', JSON.stringify(newurls))
    }
    else {
        if (today === newurls.date) {
            let geturl = newurls[file_id]
            const newsearch = new URLSearchParams(geturl)
            if (parseInt(newsearch.get('x-oss-expires') + '000') < new Date().getTime()) {
                geturl = undefined
            }
            return geturl
        }
        else {//隔天清空缓存
            GM_setValue('newurls', '{}')
            return undefined;
        }
    }
}

//生成guid
function generateUUID(length, radix) {
    var d = new Date().getTime();
    var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
        var r = (d + Math.random() * 16) % 16 | 0;
        d = Math.floor(d / 16);
        return (c == 'x' ? r : (r & 0x3 | 0x8)).toString(16);
    });
    length && (uuid = uuid.substring(0, length));
    radix && (uuid = uuid.replaceAll(radix, ''));
    return uuid
}

//切换上下集体
function changevideo(direction) {
    time && clearTimeout(time)
    time = setTimeout(() => {
        var listItems = Array.from(document.querySelectorAll('.list--5o17x li'));
        var currentIndex = listItems.findIndex(li => li.getAttribute('data-is-current') === 'true');
        if ((direction === -1 && currentIndex > 0) || (direction === 1 && currentIndex < listItems.length - 1)) {
            isfullscreen = document.fullscreenElement !== null
            listItems[currentIndex + direction]?.click();
        }
    }, 111)
}

//处理播放异常
function abnormalplay() {
    // 创建一个观察器实例并传入回调函数
    const observer = new MutationObserver(function (mutationsList, observer) {
        for (let mutation of mutationsList) {
            if (mutation.type === 'childList') {
                const isPlayError = containsErrorMessage(mutation.target, '播放异常,请稍后再试');
                if (isPlayError) {
                    document.querySelector('.video-player--k1J-M .btn--UrTVT')?.click()
                }
            }
        }
    });
    // 选择需要观察变动的节点
    const targetNode = document.body;
    // 配置观察选项
    const config = { childList: true, subtree: true };
    // 传入目标节点和观察选项
    observer.observe(targetNode, config);

    function containsErrorMessage(node, text) {
        if (node.nodeType === Node.TEXT_NODE) {
            // 如果是文本节点,检查文本内容是否包含指定文本
            if (node.textContent.includes(text)) {
                return true;
            }
        } else if (node.childNodes && node.childNodes.length > 0) {
            // 如果是元素节点,并且有子节点,递归遍历子节点
            for (let i = 0; i < node.childNodes.length; i++) {
                if (containsErrorMessage(node.childNodes[i], text)) {
                    return true;
                }
            }
        }
        return false;
    }
}

//自定义样式
(function () {
    var style = document.createElement('style');
    style.innerHTML = `
    :fullscreen .video-player--k1J-M {bottom: 0px;opacity:0!important;}
    .video-player--k1J-M {bottom: -80px;opacity:0.8!important;}
    .text-primary--3DHOJ{overflow:visible;font-weight:bold}
    .loader--3P7-4,.loader--zXBWG{opacity:0.8!important}
    .outer-wrapper--3ViSy{opacity:0!important}
    .outer-wrapper--3ViSy:hover,.video-player--k1J-M:hover{opacity:0.8!important}
    .button--1pH7M,.container--CIvrv{display:none}
    .ended-container--Tz5lR,.content-wrapper--A93tB,.feature-blocker--vh7jp,.sign-bar--1XrSl,.ai-summary-btn--fQnJ{display:none!important}
    `;
    document.head.appendChild(style);
})()

//键盘快捷键
document.addEventListener('keydown', function (e) {
    const videoElement = document.querySelector('video')
    if (videoElement) {
        if (e.key === 'Home' || e.key === 'End') {
            var direction = event.key === 'Home' ? -1 : 1;
            changevideo(direction)
        }
        else if (e.key === 'Enter') {
            document.querySelector('.action--HeWYA:not([data-active])').click()
        }
        else if (e && e.keyCode === 37) {
            videoElement.currentTime -= 5;
        }
        else if (e && e.keyCode === 39) {
            videoElement.currentTime += 5;
        }
        return false
    }
    if (e.altKey && e.code == 'KeyS') {
        let savedevice_id = ''
        if (location.pathname.startsWith('/drive/file/resource')) {
            savedevice_id = device_list.resource_drive_id
        }
        else if (this.location.pathname.startsWith('/drive/file/backup')) {
            savedevice_id = device_list.backup_drive_id
        }
        if (savedevice_id) {
            const savefile_id = this.location.pathname.split('/')[4] || 'root'
            if (confirm("确定设置此目录为临时转存目录"))
                GM_setValue("saveinfo", JSON.stringify({ savedevice_id: savedevice_id, savefile_id: savefile_id }))
        }
    }
    else if (e.altKey && e.code == 'KeyD') {
        var userInput = prompt("输入阿里云盘开放平台的client_id");
        if (userInput !== null) {
            GM_setValue('openapiclient_id', userInput)
            openapiclient_id = userInput;
            GM_setValue('openapitoken', '{}')
        }
    }
});