Greasy Fork

Greasy Fork is available in English.

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

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

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

// ==UserScript==
// @name         阿里云盘标清替换成最高画质
// @namespace    http://tampermonkey.net/
// @version      1.1.1
// @description  通过阿里云盘开放平台将原标清替换成最高画质(支持分享链接),打开视频自动播放,视频前进后退5秒,更多倍速播放以及记忆倍速,视频下载,播放器工具栏置底并设置透明度,播放历史记录,字幕颜色设置,上传本地字幕(vtt,srt,ass),快捷键: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==

var openapiclient_id = GM_getValue('openapiclient_id') || '55091393987b4cc090b090ee17e85e0a'
var newurl, sdlurl, isfullscreen, time, sinfo, openapicode_verifier, observer, storedBlob, savedevice_id
var vtthearder = 'WEBVTT\n0:00:00.000 --> 0:00:01.500\n<b>脚本制作人:bygavin(星峰)</b>'
var oldxhr = unsafeWindow.XMLHttpRequest
var oldfetch = unsafeWindow.fetch
function newobj() { }
if (openapiclient_id) {
        (function (send) {
        unsafeWindow.XMLHttpRequest.prototype.send = function (sendParams) {
            const sendurl = this.__recordInfo__.url
            if (sendurl.indexOf("/file/list") > 0 || sendurl.indexOf("/file/search") > 0 || sendurl.indexOf("/file/list_by_share") > 0) {
                const oldargument = JSON.parse(sendParams)
                oldargument.limit = 200
                if (sendurl.indexOf("/file/list_by_share") <= 0) {
                    savedevice_id = oldargument?.drive_id
                }
                arguments[0] = JSON.stringify(oldargument);
            }
            send.apply(this, arguments);
        };
    })(unsafeWindow.XMLHttpRequest.prototype.send);
            unsafeWindow.XMLHttpRequest = function () {
        let tagetobk = new newobj();
        tagetobk.oldxhr = new oldxhr();
        let handle = {
            get: function (target, prop) {
                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
                const lpfn = localStorage.getItem('last_play_file_name')?.split('>').pop()
                if (responseURL.indexOf("/file/get_video_preview_play_info") > 0 && prop.indexOf('response') !== -1) {
                    const isshare = responseURL.indexOf("/file/get_video_preview_play_info_by_share") > 0
                    const response = target.oldxhr?.response || target.oldxhr?.responseText
                    var res = JSON.parse(response);
                    if (!lpfn) {
                        const share_token = target.oldxhr.__reqCtx__.headers["x-share-token"]
                        sinfo = { share_token: share_token, file_id: res.file_id }
                        sdlurl = ''
                        newurl = getsetnewurl(res.file_id)
                        if (newurl) {
                            newurl.length > 1 && (sdlurl = newurl[1])
                            newurl = newurl[0]
                        }
                        else {
                            if (isshare) {
                                const saveinfo = savefile(res.file_id, share_token).responses[0].body
                                newurl = getVideoPreviewPlayInfo(saveinfo.drive_id, saveinfo.file_id)
                                deletefile(saveinfo.file_id)
                            }
                            else {
                                newurl = getVideoPreviewPlayInfo(res.drive_id, res.file_id)
                            }
                            newurl && getsetnewurl(res.file_id, [newurl])
                        }
                        newurl && (res.video_preview_play_info.live_transcoding_task_list[isshare ? 1 : 0].url = newurl)
                        createVttBlob(res)
                        res = JSON.stringify(res);
                        if (newurl)
                            return res;
                    }
                    else {
                        sdlurl = ''
                        newurl = res.video_preview_play_info.live_transcoding_task_list[isshare ? 1 : 0].url
                        return response;
                    }
                }
                else if (responseURL.indexOf("/file/get_video_preview_play_info") > 0 && prop === "statusText") {
                    morerate();
                }
                else if ((responseURL.indexOf("/file/list") > 0 || responseURL.indexOf("/file/list_by_share") > 0) && prop === "statusText" && lpfn && observer === undefined) {
                    observer = new MutationObserver(function () {
                        const target = document.querySelector('.text-primary--JzAb9')
                        if (target) {
                            observer.disconnect();
                            const lastvideo = document.querySelector('.text-primary--JzAb9[title="' + lpfn + '"]');
                            if (lastvideo) {
                                lastvideo.click()
                                localStorage.removeItem('last_play_file_name')
                            }
                            else {
                                const anyvideo = document.querySelector('img[alt="video"]')
                                if (anyvideo)
                                    anyvideo.click()
                                else
                                    localStorage.removeItem('last_play_file_name')
                            }
                        }
                    });
                    let config = { childList: true, subtree: true };
                    observer.observe(document, config);
                }
                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;
    }
            unsafeWindow.fetch = function (...bianliang) {
        return new Promise(function (resolve) {
            oldfetch(...bianliang).then(function (response) {
                let handler = {
                    get: function (target, prop) {
                        if (typeof Reflect.get(target, prop) === 'function') {
                            if (Reflect.get(target, prop + 'proxy') === undefined) {
                                target[prop + 'proxy'] = (...funcargs) => {
                                    let result = target[prop].call(target, ...funcargs)
                                    if (bianliang.length > 0 && prop === 'blob' && (bianliang[0].indexOf('/subtitle/subtitle') > 0 || bianliang[0].endsWith('#bygavin'))) {
                                        return new Promise(function (resolve) {
                                            result.then(
                                                function (data) {
                                                    if (bianliang[0].endsWith('#bygavin')) {
                                                        if (!storedBlob) {
                                                            resolve(data)
                                                        }
                                                        else {
                                                            const blob = new Blob([storedBlob.replace('WEBVTT', vtthearder)], { type: 'application/octet-stream;charset=utf-8;' });
                                                            resolve(blob);
                                                        }
                                                    }
                                                    else if (bianliang[0].indexOf('/subtitle/subtitle') > 0) {
                                                        let reader = new FileReader();
                                                        reader.onload = function (e) {
                                                            const vtt = e.target.result.replace('WEBVTT', vtthearder)
                                                            const blob = new Blob([vtt], { type: 'application/octet-stream;charset=utf-8;' });
                                                            resolve(blob);
                                                        };
                                                        reader.readAsText(data);
                                                    }
                                                }
                                            )
                                        });
                                    }
                                    return result
                                }
                            }
                            return Reflect.get(target, prop + 'proxy')
                        }
                        return Reflect.get(target, prop);
                    },
                    set(target, prop, value) {
                        return Reflect.set(target, prop, value);
                    },
                };
                let proxy = new Proxy(response, handler)
                resolve(proxy)
            })
        });
    }
    }


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 getsaveinfo = GM_getValue('saveinfo');
    let saveinfo = getsaveinfo || {}
    if (getsaveinfo && typeof getsaveinfo === "string") {
        saveinfo = JSON.parse(getsaveinfo)
        GM_setValue('saveinfo', 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 = 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);
    }
}
function getopenapicode() {
    openapicode_verifier = generateUUID(12, '-')
    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);
    }
}
function getopenapitoken() {
    const nowtime = new Date().getTime();
    const openapitoken = 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', tokejson)
        return tokejson.access_token;
    } else {
        return (xhr.statusText);
    }
}
function createVttBlob(json) {
    const blob = new Blob([vtthearder + '\n'], { type: 'application/octet-stream;charset=utf-8;c=bygavin' });
    const url = URL.createObjectURL(blob);
    let sublist = json.video_preview_play_info.live_transcoding_subtitle_task_list || []
    if (sublist.length > 0) {
        const newsublist = []
        var xhr = new oldxhr();
        sublist.forEach(function (item) {
            xhr.open('GET', item.url, false);
            xhr.send();
            if (xhr.status === 200) {
                if (xhr.responseText.length > 1000) {
                    newsublist.push(item)
                }
            }
        })
        sublist = newsublist
    }
    sublist.push({ "status": "finished", url: url + "#bygavin" })
    json.video_preview_play_info.live_transcoding_subtitle_task_list = sublist
}
function addkeyboardevent(e) {
    const videoElement = document.querySelector('video')
    var isvideo = true
    const volumeflag = 0.05
    let volume = videoElement.volume
    if (e && e.key === 'ArrowLeft') {
        videoElement.currentTime -= 5;
    }
    else if (e && e.key === 'ArrowRight') {
        videoElement.currentTime += 5;
    }
    else if (e && e.key === 'ArrowUp') {
        volume += volumeflag;
    }
    else if (e && e.key === 'ArrowDown') {
        volume -= volumeflag;
    }
    else {
        isvideo = false
    }
    videoElement.volume = volume > 1 ? 1 : volume < 0 ? 0 : volume
    return isvideo
}
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()
        }
        addkeyboardevent(e)
    }
    if (e.altKey && e.code == 'KeyS') {
        if (savedevice_id) {
            const savefile_id = this.location.pathname.split('/')[4] || 'root'
            if (confirm("确定设置此目录为临时转存目录"))
                GM_setValue("saveinfo", { 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', {})
        }
    }
});
const colorsgv = '#637dff'
const settingsvg = '<svg fill="' + colorsgv + '" style="margin-right: 15px; stroke-width="0" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024" class="hope-icon hope-c-XNyZK hope-c-PJLV hope-c-PJLV-ifkxHPo-css" tips="local_settings" height="1.5em" width="2em" style="overflow: visible;"><path d="M924.8 625.7l-65.5-56c3.1-19 4.7-38.4 4.7-57.8s-1.6-38.8-4.7-57.8l65.5-56a32.03 32.03 0 009.3-35.2l-.9-2.6a443.74 443.74 0 00-79.7-137.9l-1.8-2.1a32.12 32.12 0 00-35.1-9.5l-81.3 28.9c-30-24.6-63.5-44-99.7-57.6l-15.7-85a32.05 32.05 0 00-25.8-25.7l-2.7-.5c-52.1-9.4-106.9-9.4-159 0l-2.7.5a32.05 32.05 0 00-25.8 25.7l-15.8 85.4a351.86 351.86 0 00-99 57.4l-81.9-29.1a32 32 0 00-35.1 9.5l-1.8 2.1a446.02 446.02 0 00-79.7 137.9l-.9 2.6c-4.5 12.5-.8 26.5 9.3 35.2l66.3 56.6c-3.1 18.8-4.6 38-4.6 57.1 0 19.2 1.5 38.4 4.6 57.1L99 625.5a32.03 32.03 0 00-9.3 35.2l.9 2.6c18.1 50.4 44.9 96.9 79.7 137.9l1.8 2.1a32.12 32.12 0 0035.1 9.5l81.9-29.1c29.8 24.5 63.1 43.9 99 57.4l15.8 85.4a32.05 32.05 0 0025.8 25.7l2.7.5a449.4 449.4 0 00159 0l2.7-.5a32.05 32.05 0 0025.8-25.7l15.7-85a350 350 0 0099.7-57.6l81.3 28.9a32 32 0 0035.1-9.5l1.8-2.1c34.8-41.1 61.6-87.5 79.7-137.9l.9-2.6c4.5-12.3.8-26.3-9.3-35zM788.3 465.9c2.5 15.1 3.8 30.6 3.8 46.1s-1.3 31-3.8 46.1l-6.6 40.1 74.7 63.9a370.03 370.03 0 01-42.6 73.6L721 702.8l-31.4 25.8c-23.9 19.6-50.5 35-79.3 45.8l-38.1 14.3-17.9 97a377.5 377.5 0 01-85 0l-17.9-97.2-37.8-14.5c-28.5-10.8-55-26.2-78.7-45.7l-31.4-25.9-93.4 33.2c-17-22.9-31.2-47.6-42.6-73.6l75.5-64.5-6.5-40c-2.4-14.9-3.7-30.3-3.7-45.5 0-15.3 1.2-30.6 3.7-45.5l6.5-40-75.5-64.5c11.3-26.1 25.6-50.7 42.6-73.6l93.4 33.2 31.4-25.9c23.7-19.5 50.2-34.9 78.7-45.7l37.9-14.3 17.9-97.2c28.1-3.2 56.8-3.2 85 0l17.9 97 38.1 14.3c28.7 10.8 55.4 26.2 79.3 45.8l31.4 25.8 92.8-32.9c17 22.9 31.2 47.6 42.6 73.6L781.8 426l6.5 39.9zM512 326c-97.2 0-176 78.8-176 176s78.8 176 176 176 176-78.8 176-176-78.8-176-176-176zm79.2 255.2A111.6 111.6 0 01512 614c-29.9 0-58-11.7-79.2-32.8A111.6 111.6 0 01400 502c0-29.9 11.7-58 32.8-79.2C454 401.6 482.1 390 512 390c29.9 0 58 11.6 79.2 32.8A111.6 111.6 0 01624 502c0 29.9-11.7 58-32.8 79.2z"></path></svg>'
const potplayerdiv = '<svg style="margin-left: 10px;margin-right: 10px;cursor: pointer;" fill="' + colorsgv + '" stroke-width="0" class="hope-icon hope-c-XNyZK hope-c-PJLV hope-c-PJLV-ifkxHPo-css history" tips="history" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2770" width="2.5em" height="2.5em"><path d="M981.184 160.096c-143.616-20.64-302.336-32.096-469.184-32.096s-325.568 11.456-469.184 32.096c-27.52 107.712-42.816 226.752-42.816 351.904s15.264 244.16 42.816 351.904c143.648 20.64 302.336 32.096 469.184 32.096s325.568-11.456 469.184-32.096c27.52-107.712 42.816-226.752 42.816-351.904s-15.264-244.16-42.816-351.904zM384 704l0-384 320 192-320 192z" p-id="2771"></path></svg>'
function setStyle() {
    const fontcss = GM_getValue('fontColor') || ''
    const newsetstyle = document.querySelector('#newsetstyle')
    newsetstyle && newsetstyle.remove();
    var style = document.createElement('style');
    const butnum = 3
    const rightpx = 41 + butnum * 62
    style.id = 'newsetstyle'
    style.innerHTML = `
    .btnsetting{position: absolute;right: 10px;}
    .btnsetting:hover>.mymemu:not(:empty) {display: flex;}
    .mymemu {position: relative;display: none;right: -${rightpx}px;top: -20px;background-color: white;border-radius:10px}
    .mymemu button{ padding: 5px 10px; border: none; color: #fff; border-radius: 4px; cursor: pointer;margin-top: 15px;margin-bottom: 15px;margin-right: 10px;}
    .list--5o17x,.scroll-container--MsQem{scrollbar-width: none; -ms-overflow-style: none; }
    #playhistory-panel .breadcrumb-item-link--9zcQY{color: var(--theme_hover);}
    :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}
    `;
    fontcss && (style.innerHTML += '.cue--rlq6T.cue-medium--be9UK,#colorPreview{color:' + fontcss + '}')
    document.head.appendChild(style);
}
setStyle();
function getsetnewurl(file_id, setnewurl) {
    const today = new Date().toLocaleDateString()
    const newurls = GM_getValue('newurls') || {}
    if (!newurls.date)
        newurls.date = today
    if (setnewurl) {
        newurls[file_id] = setnewurl;
        GM_setValue('newurls', newurls)
    }
    else {
        if (today === newurls.date) {
            let geturl = newurls[file_id]
            if (geturl) {
                if (typeof geturl === "string")
                    geturl = [geturl]
                const newsearch = new URLSearchParams(geturl[0])
                if (parseInt(newsearch.get('x-oss-expires') + '000') < new Date().getTime()) {
                    geturl = undefined
                }
            }
            return geturl
        }
        else {
            GM_setValue('newurls', {})
            return undefined;
        }
    }
}
function setfullscreen() {
    function videoEndHandler() {
        changevideo(1)
    }
    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
        });
    });
    const video = document.querySelector("video");
    video.removeEventListener('ended', videoEndHandler, false);
    video.addEventListener('ended', videoEndHandler, false);
}
function morerate() {
    storedBlob = null
    let playbackRate = GM_getValue("playbackRate") || 1
    let video = document.querySelector("video");
    if (video) {
        video.onplay = function () {
            video.playbackRate = playbackRate
            video.volume = GM_getValue('volume') || 1
            if (video.volume < 1) {
                video.volume += .01
                video.volume -= .01
            }
            setTimeout(() => {
                const allsubel = document.querySelectorAll('.meta--iPZhB')
                const subInput = allsubel[allsubel.length - 1]
                subInput.querySelector('.text--G8ymN').textContent = '本地外挂字幕'
                subInput.querySelector('.text--G8ymN').title = '本地外挂字幕'
            }, 0)
            playing();
        };
    }
    const ul = document.querySelector('div[class^="drawer-list-grid"]');
    if (!ul || !video) {
        return;
    }
    else if (ul.childNodes.length > 7) {
        return
    }
    addsubcolor();
    adddownloadbut();
    setfullscreen();
    video.addEventListener('dblclick', function () {
        document.querySelector('.action--HeWYA:not([data-active])').click()
    });
    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();
    });
}
function addsubcolor() {
    const fontcss = GM_getValue('fontColor') || ''
    let colorvalue = ''
    if (fontcss)
        colorvalue = 'value="' + fontcss + '"'
    const subhearder = document.querySelectorAll('.scroll-wrapper--aByOe .drawer-label--FWKBs')[1]
    subhearder.insertAdjacentHTML('beforeend', '<input ' + colorvalue + ' style="margin-right: 130px;top:-50px;position:relative;" type="color" id="colorInput"><span id="colorPreview">字幕颜色</span>')
    var colorInput = document.querySelector('#colorInput');
    var colorPreview = document.querySelector('#colorPreview');
    colorPreview.addEventListener('click', function () {
        colorInput.click();
    });
    colorInput.addEventListener('change', function () {
        colorPreview.style.color = colorInput.value;
        GM_setValue('fontColor', colorInput.value)
        setStyle();
    });
    const loadsubbtn = document.querySelector('.meta--iPZhB').parentElement.parentElement
    loadsubbtn.insertAdjacentHTML('beforeend', loadsubbtn.innerHTML.replace('手动添加外挂字幕', '添加本地外挂字幕').replace('data-is-current', 'style="margin-left: auto;" data-is-current'))
    loadsubbtn.style.display = 'flex'
    loadsubbtn.querySelectorAll('li .meta--iPZhB')[1].parentElement.addEventListener('click', function () {
        var input = document.createElement('input');
        input.type = 'file';
        input.style.display = 'none';
        document.body.appendChild(input);
        input.addEventListener('change', function (e) {
            var file = e.target.files[0];
            var reader = new FileReader();
            reader.onload = function (event) {
                storedBlob = event.target.result;
                if (file.name.toLowerCase().endsWith('.ass')) {
                    storedBlob = convertAssToVtt(storedBlob)
                }
                else if (file.name.toLowerCase().endsWith('.srt')) {
                    storedBlob = convertSrtToVtt(storedBlob)
                }
                document.body.removeChild(input);
                document.querySelector('.action--H2mi0[data-active="true"]').click()
                const allsubel = document.querySelectorAll('.meta--iPZhB')
                const subInput = allsubel[allsubel.length - 1]
                subInput.querySelector('.text--G8ymN').textContent = file.name
                subInput.querySelector('.text--G8ymN').title = file.name
                if (subInput.parentElement.getAttribute('data-is-current') === 'true')
                    subInput.click()
                subInput.click()
            };
            reader.readAsText(file);
        });
        input.click()
    })
}
function convertAssToVtt(assSubtitles) {
    const assLines = assSubtitles.split('\n');
    let vttOutput = `WEBVTT\n`;
    assLines.forEach((line) => {
        if (!line.trim() || line.trim().startsWith(';') || !line.trim().startsWith('Dialogue:')) return;
        const lines = line.split(',');
        const startTime = lines[1];
        const endTime = lines[2];
        const text = lines.slice(9).join(',');
        const startTimeVtt = convertAssTimeToVttTime(startTime);
        const endTimeVtt = convertAssTimeToVttTime(endTime);
        vttOutput += `\n${startTimeVtt} --> ${endTimeVtt}\n${text}\n`
    });
    return vttOutput;
}
function convertAssTimeToVttTime(assTime) {
    const parts = assTime.split('.').map(part => part.padStart(2, '0'));
    const hours = parts[0].split(':')[0];
    const minutes = parts[0].split(':')[1];
    const seconds = parts[0].split(':')[2];
    const milliseconds = '000' + parts[1];
    return `${hours}:${minutes}:${seconds}.${milliseconds.substring(milliseconds.length - 3)}`;
}
function convertSrtToVtt(srtSubtitles) {
    const assLines = srtSubtitles.split('\n');
    let vttOutput = `WEBVTT\n`;
    assLines.forEach((line) => {
        if (isNumeric(line.trim())) return;
        if (line.indexOf('-->') !== -1) {
            line = line.replaceAll(',', '.')
        }
        vttOutput += line + '\n';
    });
    return vttOutput;
}
function isNumeric(str) {
    const regex = /^\d+$/;
    return regex.test(str);
}
function adddownloadbut() {
    const selffile = location.pathname.startsWith('/drive/file')
    const div = document.querySelector('.actions--YfXrK')
    div.insertAdjacentHTML('beforeend', '<div class="action--H2mi0 btndownload" data-active="false" data-disabled="false">下载</div>');
    document.querySelector('.btndownload').addEventListener("click", () => {
        const dl = document.createElement('a');
        if (!sdlurl) {
            const sss = new URLSearchParams(newurl)
            const sfile_id = sss.get('f')
            const newurls = GM_getValue('newurls') || {}
            if (selffile) {
                sdlurl = getDownloadUrl(sss.get('dr'), sfile_id)
                newurls[sfile_id] = [newurl, sdlurl]
            }
            else {
                const saveinfo = savefile(sinfo.file_id, sinfo.share_token).responses[0].body
                sdlurl = getDownloadUrl(saveinfo.drive_id, saveinfo.file_id)
                deletefile(saveinfo.file_id)
                newurls[sinfo.file_id] = [newurl, sdlurl]
            }
            GM_setValue('newurls', newurls)
        }
        dl.href = sdlurl
        dl.download = ''
        dl.click();
    })
}
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 playing() {
    const lpfn = localStorage.getItem('last_play_file_name')?.split('>').pop()
    if (lpfn) {
        document.querySelector('.title--RYadk[title="' + lpfn + '"]')?.click()
        localStorage.removeItem('last_play_file_name')
    }
    else {
        const video = document.querySelector('video')
        const sss = new URLSearchParams(newurl)
        const file_id = sss.get('f')
        const playhistory = GM_getValue('playhistory') || []
        const file_path = location.pathname
        const nameel = document.querySelectorAll('.breadcrumb--gnRPG[data-calc="true"] .breadcrumb-item--j8J5H')
        let file_name = ''
        for (let i = 1; i < nameel.length; i++) {
            file_name += nameel[i].outerHTML
        }
        var listItems = Array.from(document.querySelectorAll('.list--5o17x li'));
        var currentIndex = listItems.findIndex(li => li.getAttribute('data-is-current') === 'true');
        file_name += listItems[currentIndex].querySelector('.title--RYadk').textContent
        const playinfo = { file_id: file_id, file_name: file_name, file_path: file_path }
        const index = playhistory.findIndex(item => item.file_path === file_path)
        const currentTime = JSON.parse(localStorage.getItem('currentTime') || '{}')
        if (currentTime?.currentTime) {
            const currentTimeindex = playhistory.findIndex(item => item.file_path === currentTime.file_path)
            if (currentTimeindex !== -1) {
                playhistory[currentTimeindex].currentTime = currentTime.currentTime
            }
        }
        if (index === -1) {
            playhistory.unshift(playinfo)
        } else {
            if (playhistory[index].currentTime && playhistory[index].file_id === file_id)
                video.currentTime = playhistory[index].currentTime
            if (playhistory[index].anotherName)
                playinfo.anotherName = playhistory[index].anotherName
            playhistory[index] = playinfo
        }
        GM_setValue('playhistory', playhistory.slice(0, 10))
        video.addEventListener('timeupdate', function () {
            if (video.currentTime) {
                localStorage.setItem('currentTime', JSON.stringify({ file_path: file_path, currentTime: video.currentTime }))
            }
        })
        video.addEventListener('volumechange', function () {
            GM_setValue('volume', video.volume)
            const volume = video.volume * 100
            let volumelevel = parseInt((volume - 2) / 33) + 1
            volumelevel = volume == 0 ? 0 : volumelevel
            document.querySelectorAll('.current--Dbz2w.current--0tS5B')[1].style.width = volume + '%'
            document.querySelectorAll('.indicator--oPSic.indicator--qlLq-')[1].style = `left: ${volume}%; transform: translate(-${volume}%, -50%);`
            document.querySelectorAll('.icon--EkKaB.icon--D3kMk use')[1].setAttribute('xlink:href', '#PDSvolume' + volumelevel)
        })

        video.parentElement.parentElement.removeEventListener('keydown', null);
        video.parentElement.parentElement.addEventListener('keydown', function (e) {
            if (addkeyboardevent(e))
                e.stopPropagation()
        })
    }
}
function abnormalplay() {
    const errobserver = new MutationObserver(function () {
        const target = document.querySelector('.error--3ZTlN')
        if (target) {
            document.querySelector('.video-player--k1J-M .btn--UrTVT').click()
        }
    });
    let config = { childList: true, subtree: true };
    errobserver.observe(document.body, config);
}
unsafeWindow.onload = function () {
    abnormalplay();
    addplayhistory()
};
function addplayhistory() {
    var settingsButton = document.createElement('div');
    settingsButton.innerHTML = `<div style="position: fixed; right: 40px; bottom:115px; z-index:112;border:none;width:auto;" class='membership-wrapper--6egJF'>${potplayerdiv}</div>`;
    document.body.appendChild(settingsButton);
    document.querySelector('.history').addEventListener('click', showManagementPanel);
}
function showManagementPanel() {
    // 移除之前的管理页面
    var previousPanel = document.getElementById('playhistory-panel');
    if (previousPanel) {
        previousPanel.parentNode.removeChild(previousPanel);
    }
    var playlist = GM_getValue('playhistory') || [];
    // 创建弹出元素
    var panel = document.createElement('div');
    panel.id = 'playhistory-panel';
    panel.style.position = 'fixed';
    panel.style.top = '0';
    panel.style.left = '0';
    panel.style.width = '100%';
    panel.style.height = '100%';
    panel.style.backgroundColor = 'rgba(0, 0, 0, 0.5)';
    panel.style.zIndex = '9999';
    panel.innerHTML =
        `<div style="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); background-color: #fff; padding: 20px; border-radius: 10px; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2); width: 800px;">
        <div style="display: flex; margin-bottom: 10px;"><h2 style="margin-top: 0; margin-bottom: 20px; font-size: 24px;">历史播放记录</h2>        
        <div style="display: flex; justify-content: flex-end;top:30px" class="btnsetting">
                <div class="mymemu" style="right:0px">
                <button class="reset-button" playinfo-index="-1" style="background-color: #ffa31a;">重置所有设置</button>
                <button class="clear-button" playinfo-index="-1" style="background-color: #ff4d4f;">清空播放记录</button>
                </div>
                ${settingsvg}
                </div></div>
        <hr style="margin-top: 20px; margin-bottom: 20px;">
        <div style="overflow-y: auto; max-height: 600px;">
        ${playlist.map(function (playinfo, index) {
            let anotherNamehtml = `<div class="breadcrumb-item--j8J5H" data-label="${playinfo.anotherName}" data-hide="false" data-more="false"><div class="breadcrumb-item-link--9zcQY">${playinfo.anotherName}</div><div class="breadcrumb-item-separator--MnbFV">›</div></div>`
            anotherNamehtml += playinfo.file_name.split('>').pop();
            return `<div class="history-item" style="display: flex; margin-bottom: 10px;">
                <div style="display: ruby;margin-top: auto;margin-bottom: auto;" class="breadcrumb--gnRPG" data-calc="true">${playinfo.anotherName ? anotherNamehtml : playinfo.file_name}</div>
                <div style="display: flex; justify-content: flex-end;" class="btnsetting">
                <div class="mymemu" style="right:0px">
                <button class="rename-button" playinfo-index="${index}" style="background-color: #ffa31a;">别名</button>
                <button class="delete-button" playinfo-index="${index}" style="background-color: #ff4d4f;">删除</button>
                <button class="play-button" playinfo-index="${index}" style="background-color: #007fff;">播放</button>
                </div>
                ${settingsvg}
                </div>
                </div>
            `;
        }).join('')}
        </div>
        </div>
    `;
    document.body.appendChild(panel);

    // 绑定事件
    panel.addEventListener('click', function (e) {
        var playhistory = GM_getValue('playhistory') || [];
        const index = e.target.getAttribute('playinfo-index')
        if (e.target.classList.contains('play-button')) {
            localStorage.setItem('last_play_file_name', playhistory[index].file_name)
            location.href = playhistory[index].file_path
        }
        else if (e.target.classList.contains('delete-button')) {
            playhistory.splice(index, 1)
            GM_setValue('playhistory', playhistory)
            showManagementPanel()
        }
        else if (e.target.classList.contains('rename-button')) {
            var userInput = prompt("设置别名为:");
            if (userInput) {
                playhistory[index].anotherName = userInput
                GM_setValue('playhistory', playhistory)
                showManagementPanel()
            }
        }
        else if (e.target.classList.contains('reset-button')) {
            if (confirm("确定要重置所有设置吗?")) {
                clearsetValue(['openapiclient_id', 'openapitoken', 'saveinfo', 'playbackRate', 'volume', 'fontColor', 'newurls', 'playhistory']);
            }
        }
        else if (e.target.classList.contains('clear-button')) {
            if (confirm("确定要清空播放记录吗?")) {
                clearsetValue(['newurls', 'playhistory']);
            }
        }
        else if (e.target == panel) {
            panel.parentNode.removeChild(panel);
        }
        function clearsetValue(keys) {
            keys.map(key => GM_setValue(key, null))
            showManagementPanel()
        }
    });
}