Greasy Fork is available in English.

阿里云盘一键秒传到115云盘播放

先通过alist将阿里云盘资源秒传到115网盘,然后再通过cd2播放,alist转存有缓存时间,cd2是实时,所以用它来播放,需要alist添加阿里云盘和115的存储,挂载路径为阿里云盘和115且需要设置缓存时间设置为1分钟,不然转存后可能要等很久才能够秒传,cd2需添加115网盘,名称为115,阿里云盘需设置默认转存目录进入目标目录后按ALT+S保存,设置的临时目录需和alist挂载设备一致,不可一个在资源库,一个在备份盘

// ==UserScript==
// @name         阿里云盘一键秒传到115云盘播放
// @namespace    http://tampermonkey.net/
// @version      1.0.2
// @description  先通过alist将阿里云盘资源秒传到115网盘,然后再通过cd2播放,alist转存有缓存时间,cd2是实时,所以用它来播放,需要alist添加阿里云盘和115的存储,挂载路径为阿里云盘和115且需要设置缓存时间设置为1分钟,不然转存后可能要等很久才能够秒传,cd2需添加115网盘,名称为115,阿里云盘需设置默认转存目录进入目标目录后按ALT+S保存,设置的临时目录需和alist挂载设备一致,不可一个在资源库,一个在备份盘
// @author       bygavin
// @match        https://www.aliyundrive.com/drive*
// @match        https://www.alipan.com/drive*
// @match        https://www.aliyundrive.com/s/*
// @match        https://www.alipan.com/s/*
// @icon         https://img.alicdn.com/imgextra/i1/O1CN01JDQCi21Dc8EfbRwvF_!!6000000000236-73-tps-64-64.ico
// @license      MIT
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        unsafeWindow
// @grant        GM_xmlhttpRequest
// ==/UserScript==

//#region 变量定义
var savedevice_id, alistaliyun, getundone, getalist, oneclicksave, startbatch, morethen5G, videonamelist
var obj = {}, pathinfo = {}
const alist115yun = "/115/阿里云转存"
var cd2url = GM_getValue("cd2url") || ''
var alisturl = GM_getValue("alisturl") || ''
var alisttoken = GM_getValue("alisttoken") || ''
var oldxhr = unsafeWindow.XMLHttpRequest
var savemode = GM_getValue("savemode") || 'save115'
const saveinfo = GM_getValue('saveinfo') || {}
const default_drive_id = JSON.parse(localStorage.getItem('token'))?.default_drive_id
function newobj() { }
//#endregion

//#region 劫持send
(function (send) {
    unsafeWindow.XMLHttpRequest.prototype.send = function (sendParams) {
        const sendurl = new URL(this.__recordInfo__.url).pathname
        if (sendurl.endsWith("/file/list") || sendurl.endsWith("/file/list_by_share")) {//修改获取列表数量为200
            const oldargument = JSON.parse(sendParams)
            oldargument.limit && (oldargument.limit = 200)
            arguments[0] = JSON.stringify(oldargument);
        }
        else if (sendurl.endsWith('/batch')) {//强制修改转存路径
            const oldargument = JSON.parse(sendParams)
            oldargument.requests.map(item => {
                if (item.body.to_parent_file_id) {
                    item.body.to_parent_file_id = saveinfo.savefile_id
                    item.body.to_drive_id = saveinfo.savedevice_id
                }
            })
            arguments[0] = JSON.stringify(oldargument);
        }
        send.apply(this, arguments);
    };
})(unsafeWindow.XMLHttpRequest.prototype.send);
//#endregion

//#region 劫持xhr
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 = new URL(target.oldxhr.responseURL).pathname
            if (responseURL.endsWith('/batch') && prop.indexOf('response') !== -1 && saveinfo.savedevice_id) {
                const res = JSON.parse(target.oldxhr?.response || target.oldxhr?.responseText);
                const resstatus = res.responses.pop()?.status
                if ((resstatus === 200 || resstatus === 201)) {
                    alito115play(saveinfo.alistaliyun, true)
                }
            }
            else if (responseURL.endsWith('file/get_path') && prop.indexOf('response') !== -1) {
                const res = JSON.parse(target.oldxhr?.response || target.oldxhr?.responseText);
                savedevice_id = res.items[0].drive_id
                let dirlist = res.items.map(item => item.name).reverse()
                if (savedevice_id === default_drive_id)
                    dirlist = ["备份文件", ...dirlist]
                alistaliyun = ["阿里云盘", ...dirlist].join('/')
                pathinfo.alistaliyun = alistaliyun
                pathinfo.pathname = location.pathname
            }
            else if ((responseURL.endsWith("file/list") || responseURL.endsWith("file/list_by_share")) && prop.indexOf('response') !== -1) {
                const res = JSON.parse(target.oldxhr?.response || target.oldxhr?.responseText);
                if (res.items?.length) {
                    morethen5G = res.items.filter((item) => { return item.size > 5368709120 }).map(item => item.name).join(',')
                    videonamelist = res.items.map(item => item.name)
                }
            }
            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);
        }
    }
    return new Proxy(tagetobk, handle);
}

//#region 功能主体
function alito115play(videopath, isshare) {
    startbatch && clearTimeout(startbatch)
    startbatch = setTimeout(() => {
        if (videopath)
            obj.showNotify("开始转存:" + videopath);
        else {
            obj.showNotify("无效的资源路径", "fail");
            return
        }
        const resfun = function (response) {
            if (response.status === 200) {
                const alistlistinfo = JSON.parse(response.responseText).data.content
                if (!alistlistinfo) {
                    getalist && clearTimeout(getalist)
                    getalist = setTimeout(() => {
                        obj.showNotify("缓存未刷新,请等待");
                        alistfun("api/fs/list", JSON.stringify({ "path": videopath, "password": "", "page": 1, "per_page": 0, "refresh": false }), resfun)
                    }, 1000)
                }
                else {
                    const savedir = alistlistinfo.length == 1 && alistlistinfo[0].is_dir ? ('/' + alistlist[0]) : ''
                    const pandir = isshare ? '' : ('/' + videopath.split('/').pop())
                    const alistlist = alistlistinfo.map(item => item.name);
                    const potplayerrun = function () {
                        const cd2 = new URL(cd2url)
                        const potplayerurl = `potplayer://${cd2url}static/${cd2.protocol.slice(0, -1) + '/' + cd2.host}/True/${encodeURIComponent(alist115yun + savedir + pandir)}.clfsplaylist.m3u`
                        const dl = document.createElement('a');
                        dl.href = potplayerurl
                        dl.click();
                    }
                    if (!isshare) {
                        if (new Set([...videonamelist, ...alistlist]).size === alistlist.length) {
                            obj.showNotify("资源已存在115网盘中,无需转存,启动potplayer播放115网盘资源");
                            potplayerrun()
                            return
                        }
                    }
                    const copyjson = { "src_dir": videopath, "dst_dir": alist115yun + pandir, "names": alistlist }
                    const removejson = { "dir": videopath, "names": alistlist }
                    obj.showNotify("开始秒传到115");
                    alistfun("api/fs/copy", JSON.stringify(copyjson), function (res1) {
                        if (res1.status === 200) {
                            const removealiyun = function () {
                                alistfun("api/admin/task/copy/undone", null, function (res2) {
                                    if (res1.status === 200) {
                                        const undonelist = JSON.parse(res2.responseText).data.length
                                        if (undonelist) {
                                            obj.showNotify("秒传进行中,请等待");
                                            getundone && clearTimeout(getundone)
                                            getundone = setTimeout(removealiyun, 1000)
                                        }
                                        else {
                                            const play115 = function () {
                                                obj.showNotify("启动potplayer播放115网盘资源");
                                                potplayerrun()
                                            }
                                            if (isshare) {
                                                obj.showNotify("开始删除阿里云转存");
                                                alistfun("api/fs/remove", JSON.stringify(removejson), play115)
                                            }
                                            else {
                                                setTimeout(play115, 0)
                                            }
                                        }
                                    }
                                }, 'GET')
                            }
                            getundone && clearTimeout(getundone)
                            getundone = setTimeout(removealiyun, 1000)
                        }
                    })
                }
            }
        }
        if (savemode === 'save115')
            alistfun("api/fs/list", JSON.stringify({ "path": videopath, "password": "", "page": 1, "per_page": 0, "refresh": false }), resfun)
        else {
            const alist115yuns = alist115yun.split('/')
            const dirname = alist115yuns.pop()
            const removejson = { "dir": alist115yuns.join('/'), "names": [dirname] }
            alistfun("api/fs/remove", JSON.stringify(removejson), function () {
                alistfun("api/fs/list", JSON.stringify({ "path": videopath, "password": "", "page": 1, "per_page": 0, "refresh": false }), resfun)
            })
        }
    }, 333)
}
//#endregion

//#region alist请求
function alistfun(url, data, fun, method) {
    GM_xmlhttpRequest({
        method: (method || 'POST'), url: alisturl + url,
        data: data,
        headers: {
            "authorization": alisttoken,
            "content-type": "application/json;charset=UTF-8",
        },
        onload: fun
    });
}
//#endregion
//#endregion

//#region 键盘快捷键
document.addEventListener('keydown', function (e) {
    if (e.altKey && e.code == 'KeyS') {
        if (savedevice_id && alistaliyun) {
            saveinfo.savedevice_id = savedevice_id
            saveinfo.savefile_id = (this.location.pathname.split('/')[4] || 'root')
            saveinfo.alistaliyun = alistaliyun
            obj.confirm('确定设置此目录为临时转存目录?', () => GM_setValue("saveinfo", saveinfo))
        }
    }
});
//#endregion

//#region  svg图标
const aliyunto115 = '<svg class="aliyunto115" fill="#637dff" width="3.7em" height="3.7em"><use xlink:href="#PDSsetting"></use></svg>'
//#endregion

//#region 自定义样式
function setStyle() {
    document.querySelector('#newsetstyle1')?.remove();
    var style = document.createElement('style');
    style.id = 'newsetstyle1'
    style.innerHTML = `
    #setting-panel .breadcrumb-item-link--9zcQY,#confirm-panel .breadcrumb-item-link--9zcQY{color:var(--theme_hover)}
    .history-item{display:flex;margin-bottom:10px}.history-item .breadcrumb-item--j8J5H{max-width:50%}.history-item .breadcrumb-item-link--9zcQY{overflow: hidden;text-overflow: ellipsis;}.history-item input::placeholder{color: #3b8f4c;}
    .right-bottom-container--6JaaW{right:-47px !important;transition:right 0.3s;}.right-bottom-container--6JaaW:hover{right:0px !important;}
    .aliyunto115{position:relative;right:-47px;transition:right 0.3s}.aliyunto115:hover{fill:var(--theme_hover);right:0px;}
    #setting-panel,#confirm-panel{position:fixed;top:0px;left:0px;width:100%;height:100%;background-color:rgba(0,0,0,0.5);z-index:99}
    #setting-panel>div,#confirm-panel>div{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}
    #confirm-panel .ant-input{box-shadow:0 0 0 1px var(--theme_primary)}
    .btnsetting{position:absolute;right:10px}
    .btnsetting:hover>.mymemu:not(:empty){display:flex}
    .mymemu{position:relative;display:none;top:-20px}
    .mymemu button{padding:5px 10px;border:none;color:#fff;border-radius:4px;cursor:pointer;margin-top:15px;margin-right:10px}
    .breadcrumb--gnRPG.play-button:hover{color:rebeccapurple}
    .oneclicksave{margin-left:16px;background-color: #00c270 !important;}
    `;
    const Notifycss = [
        ".notify{display:none;position:absolute;top:0;left:25%;width:50%;text-align:center;overflow:hidden;z-index:1010}",
        ".alert-success,.alert-loading{background:#36be63 !important;}",
        ".alert-fail{background:#ff794a !important;}",
        ".alert.fade{opacity:0;-webkit-transition:opacity .15s linear;-o-transition:opacity .15s linear;transition:opacity .15s linear}",
        ".alert.fade.in{opacity:1}"
    ]
    style.innerHTML += Notifycss.join(' ')
    document.head.appendChild(style);
}
setStyle();
//#endregion

//#region 显示管理面板
function showManagementPanel() {
    document.querySelector('#confirm-panel,#setting-panel')?.remove()
    var panelsetting = document.createElement('div');
    panelsetting.id = 'setting-panel';
    panelsetting.style = `z-index: 100;`
    panelsetting.innerHTML =
        `<div><div style="display: flex; "><h2 style="margin-top: 0; margin-bottom: 10px; font-size: 24px;">设置</h2><h2 style="margin:0 auto 10px auto; font-size: 24px;">${!saveinfo.alistaliyun ? '请到目标文件夹按Alt+S设置转存目录' : ('当前转存目录:' + saveinfo.alistaliyun.replace('阿里云盘/', ''))}</h2><div style="display: flex; justify-content: flex-end;top:30px" class="btnsetting"><div class="mymemu" style="right:10px;display:block"><button class="save-button" playinfo-index="-1" style="background-color: #00c270;">保存</button></div></div></div><hr style="margin-bottom: 20px;"><div class="history-item"><div data-more="false" data-hide="false" class="breadcrumb-item--j8J5H" style="flex: 1;"><div class="breadcrumb-item-link--9zcQY">115盘存储模式</div></div><ul class="tabs--dur-d tabs--SWY-k" style="flex: 4;margin-left: 10px;"><li class="save115 tab--j-QyM active--SEscZ"><span class="title--la5nd" title="不会进行清理,如115盘容量不够时会失败">保留</span></li><li class="clear115 tab--j-QyM"><span class="title--la5nd" title="转存到115盘之前会删除该文件夹再重新创建">清空</span></li></ul></div><div class="history-item"><div data-more="false" data-hide="false" class="breadcrumb-item--j8J5H" style="flex: 1;"><div class="breadcrumb-item-link--9zcQY">CD2网址</div></div><input placeholder="例如:http://192.168.99.211:19798/ 最后需要有/" value='${cd2url}' style="margin-left: 13px;height: 100%;flex: 4;" class="ant-input ant-input-borderless input--TWZaN" type="text"></div><div class="history-item"><div data-more="false" data-hide="false" class="breadcrumb-item--j8J5H" style="flex: 1;"><div class="breadcrumb-item-link--9zcQY">Alist网址</div></div><input placeholder="例如:http://192.168.99.211:5244/ 最后需要有/" value='${alisturl}' style="margin-left: 13px;height: 100%;flex: 4;" class="ant-input ant-input-borderless input--TWZaN" type="text"></div><div class="history-item"><div data-more="false" data-hide="false" class="breadcrumb-item--j8J5H" style="flex: 1;"><div class="breadcrumb-item-link--9zcQY">Alist Token</div></div><input placeholder="登录alist后,按F12,获取 应用程序>>本地存储>>token 的值" value='${alisttoken}' style="margin-left: 13px;height: 100%;flex: 4;" class="ant-input ant-input-borderless input--TWZaN" type="text"></div>
    </div>`
    document.body.appendChild(panelsetting);
    panelsetting.querySelectorAll('.tab--j-QyM').forEach(item => item.classList.toggle('active--SEscZ', false))
    panelsetting.querySelector('.tab--j-QyM.' + savemode).classList.toggle('active--SEscZ', true)
    panelsetting.addEventListener('click', function (event) {
        let el = event.target;
        if (el == panelsetting)
            panelsetting.parentNode.removeChild(panelsetting);
        else if (el.classList.contains('save-button')) {
            const inputs = panelsetting.querySelectorAll('input')
            GM_setValue('cd2url', inputs[0].value)
            GM_setValue('alisturl', inputs[1].value)
            GM_setValue('alisttoken', inputs[2].value)
            location.reload()
        }
        else if (el.classList.contains('title--la5nd') || el.classList.contains('tab--j-QyM')) {
            el = el.classList.contains('tab--j-QyM') ? el : el.parentElement
            if (!el.classList.contains('active--SEscZ')) {
                panelsetting.querySelectorAll('.tab--j-QyM').forEach(item => item.classList.toggle('active--SEscZ'))
                savemode = el.classList.contains('save115') ? 'save115' : 'clear115'
                GM_setValue('savemode', savemode)
            }
        }
    })
}
//#endregion

//#region 显示提示信息

obj.showNotify = function (message, ...args) {
    if (unsafeWindow.application) {
        unsafeWindow.application.showNotify(message, ...args);
    }
    else {
        document.body.insertAdjacentHTML('beforeend', '<div id="J_Notify" class="notify" style="margin: 10px auto; display: none;"></div>')
        unsafeWindow.application = {
            notifySets: {
                type_class_obj: { success: "alert-success", fail: "alert-fail", loading: "alert-loading" },
                count: 0,
                delay: 3e3
            },
            showNotify: function (message, ...args) {
                const opts = { message: message }
                args.forEach(arg => {
                    if (typeof arg === 'number')
                        opts.time = arg
                    else if (typeof arg === 'string')
                        opts.type = ["success", "fail", "loading"].includes(arg) ? arg : "success"
                });
                var that = this, class_obj = that.notifySets.type_class_obj, count = that.notifySets.count;
                opts.type == "loading" && (delay *= 5);
                var JNotify = document.getElementById('J_Notify');
                if (!document.querySelector(".alert")) {
                    if (JNotify) {
                        JNotify.innerHTML = '<div class="alert in fade button--WC7or primary--NVxfK medium--Pt0UL"></div>';
                        JNotify.style.display = 'block';
                    }
                }
                else {
                    Object.keys(class_obj).forEach(function (key) {
                        JNotify.classList.toggle(class_obj[key], false);
                    });
                }
                var alert = document.querySelector('.alert');
                alert.textContent = opts.message;
                alert.classList.add(class_obj[opts.type]);
                that.notifySets.count += 1;
                var delay = opts.time || that.notifySets.delay;
                setTimeout(function () {
                    if (++count == that.notifySets.count) {
                        that.hideNotify();
                    }
                }, delay);
            },
            hideNotify: function () {
                document.getElementById('J_Notify').innerHTML = '';
            }
        };
        obj.showNotify(message, ...args);
    }
};

obj.hideNotify = function () {
    if (unsafeWindow.application) {
        unsafeWindow.application.hideNotify();
    }
};
//#endregion

//#region 页面完全加载完成执行
new MutationObserver(function () {
    const savebtn = document.querySelector('.right-wrapper--cxNFP,.header--wVY7B')
    if (savebtn && alisturl && alisttoken && !savebtn.querySelector('.oneclicksave')) {
        savebtn.insertAdjacentHTML('beforeend', '<button class="oneclicksave button--WC7or primary--NVxfK medium--Pt0UL btn-save--SqM8z">一键转存播放</button>')
        savebtn.querySelector('.oneclicksave').addEventListener('click', () => {
            const thisrun = function () {
                if (savebtn.classList[0] === 'header--wVY7B') {
                    alistaliyun = pathinfo?.alistaliyun == alistaliyun && pathinfo?.pathname == location.pathname ? alistaliyun : ''
                    alito115play(alistaliyun, false)
                }
                else {
                    oneclicksave = true
                    document.querySelector('.button--WC7or.primary--NVxfK.medium--Pt0UL.btn-save--SqM8z').click()
                }
            }
            if (morethen5G) {
                obj.confirm(morethen5G + "\n超过了5G不支持秒传,是否继续?", () => thisrun())
            } else {
                thisrun()
            }
        })
    }
    const savenow = document.querySelector('.button--WC7or.primary--NVxfK.small--e7LRt.button--a4hgk')
    if (savenow && oneclicksave) {
        oneclicksave = false
        savenow.click()
    }
    const setbutton = document.getElementById('setting-button')
    if (!setbutton) {
        var settingsButton = document.createElement('div');
        settingsButton.innerHTML = `<div id="setting-button" style="cursor:pointer;position: fixed; right: 0px; bottom:92px; z-index:112;border:none;width:auto;">${aliyunto115}</div>`;
        document.body.appendChild(settingsButton);
        settingsButton.addEventListener('click', showManagementPanel);
    }
}).observe(document.body, { childList: true, subtree: true });
//#endregion

//#region 确认框
obj.confirm = function (message, fun) {
    document.querySelector('#confirm-panel,#setting-panel')?.remove()
    var panelconfirm = document.createElement('div');
    panelconfirm.id = 'confirm-panel';
    panelconfirm.style = `z-index: 100;`
    panelconfirm.innerHTML =
        `<div style="background-color: rgb(225, 230, 215);"><div style="display: flex; "><h2 style="margin:0 auto 10px auto; font-size: 24px;">${message}</h2></div><div class="history-item"><div class="mymemu" style="display:block;margin: auto;top: 0px;"><button class="cancel-button" style="background-color: #E6A23C;">取消</button><button class="save-button" style="background-color: #1681FF;">确定</button></div></div>
    </div>`
    document.body.appendChild(panelconfirm);
    panelconfirm.addEventListener('click', function (event) {
        event.target.classList.contains('save-button') && fun && fun();
        (event.target == panelconfirm || event.target.classList.contains('save-button') || event.target.classList.contains('cancel-button')) && panelconfirm.parentNode.removeChild(panelconfirm);
    })

}
//#endregion