// ==UserScript== // @name Custom aliyundrive // @name:zh Custom aliyundrive // @namespace https://github.com/invobzvr // @version 1.3 // @description 阿里云直链导出 // @author invobzvr // @match *://www.aliyundrive.com/drive/* // @grant GM_getValue // @grant GM_setValue // @grant GM_xmlhttpRequest // @connect 127.0.0.1 // @connect localhost // @connect * // @require https://cdn.jsdelivr.net/npm/sweetalert2@11 // @homepageURL https://github.com/invobzvr/invotoys.js/tree/main/aliyundrive // @supportURL https://github.com/invobzvr/invotoys.js/issues // @license GPL-3.0 // @downloadURL none // ==/UserScript== (function () { const Toast = Swal.mixin({ position: 'top-end', showConfirmButton: false, timer: 3e3, timerProgressBar: true, toast: true, didOpen: tst => { tst.addEventListener('mouseenter', Swal.stopTimer); tst.addEventListener('mouseleave', Swal.resumeTimer); } }); const that = { a2config: GM_getValue('a2config', { host: '127.0.0.1', port: 6800, dir: 'Download', }), xhr: function (details) { return new Promise((res, rej) => { GM_xmlhttpRequest(Object.assign(details, { onerror: rej, onload: res, })); }); }, wait: function (selectors, key) { return new Promise(res => { let el = document.querySelector(selectors), iid = setInterval(() => (el ? true : el = document.querySelector(selectors)) && (key ? el[key] : true) && (clearInterval(iid), res(el)), 100); }); }, install: async function () { history.pushState = that.HOOK.H_PUSHSTATE; addEventListener('PUSHSTATE', that.onPushState); that.rk = `__reactFiber$${Object.keys(await that.wait('#root', '_reactRootContainer')).find(ii => ii.startsWith('__reactContainer$')).split('$')[1]}`; that.tbwmo = new MutationObserver(that.tbwmc); that.ddmmo = new MutationObserver(that.ddmmc); that.ddmmo.observe(document.body, { childList: true }); that.mmmo = new MutationObserver(that.mmmc); that.init(); }, init: async function () { that.listModel = (await that.wait('[class*=node-list--]'))[that.rk].return.memoizedProps.listModel; that.tbwmo.observe(document.querySelector('[class*=page-content--]'), { childList: true }); }, tbwmc: function ([mr]) { if (mr.addedNodes.length && (that.tbwel = mr.addedNodes[0].querySelector('[class*=toolbar-wrapper]'))) { let btn = that.tbwel.firstChild; that.tbwel.insertAdjacentHTML('afterbegin', '
'); let dlBtn = that.tbwel.insertAdjacentElement('afterbegin', btn.cloneNode(true)), a2Btn = that.tbwel.insertAdjacentElement('afterbegin', btn.cloneNode(true)); dlBtn.title = 'Download'; dlBtn.addEventListener('click', () => that.download(that.listModel.selectedItems, that.normal)); a2Btn.title = 'Aria2'; a2Btn.addEventListener('click', () => that.download(that.listModel.selectedItems, that.aria2, () => [...that.listModel.selectedIds].forEach(that.listModel.removeSelect))); a2Btn.addEventListener('contextmenu', evt => (evt.preventDefault(), that.configa2(true))); } }, ddmmc: function ([mr]) { let ddm = mr.addedNodes.length && mr.addedNodes[0].querySelector('[class*=dropdown-menu--]'); ddm && that.listModel && that.mmmo.observe(ddm, { attributes: true, attributeFilter: ['class'] }); }, mmmc: function ([mr]) { let el = mr.target; if (el.className.includes('-prepare')) { let props = el[that.rk].child.memoizedProps, list = props.fileModel ? [props.fileModel] : props.fileListModel ? props.fileListModel.selectedItems : null; if (!list) { return; } let dlBtn, a2Btn, ul = el.firstChild; if (!el.querySelector('[custom]')) { let btn = ul.firstChild; ul.insertAdjacentHTML('afterbegin', '
  • '); dlBtn = ul.insertAdjacentElement('afterbegin', btn.cloneNode(true)); dlBtn.setAttribute('custom', 'normal') dlBtn.querySelector('[class*=menu-name--]').innerText = 'Download'; a2Btn = ul.insertAdjacentElement('afterbegin', btn.cloneNode(true)); a2Btn.setAttribute('custom', 'aria2') a2Btn.querySelector('[class*=menu-name--]').innerText = 'Aria2'; } else { dlBtn = ul.querySelector('[custom=normal]'); a2Btn = ul.querySelector('[custom=aria2]'); } dlBtn.onclick = () => (document.body.dispatchEvent(new MouseEvent('mousedown', { bubbles: true })), that.download(list, that.normal)); a2Btn.onclick = () => (document.body.dispatchEvent(new MouseEvent('mousedown', { bubbles: true })), that.download(list, that.aria2)); } }, onPushState: function (evt) { if (evt.detail === '/drive/' || evt.detail.startsWith('/drive/folder')) { !that.listModel && that.init(); } else { that.listModel = null; that.tbwmo.disconnect(); } }, download: async function (list, func, callback) { list.length !== (list = list.filter(ii => ii.type == 'file')).length && await Toast.fire({ icon: 'warning', title: 'Folders are skipped', timer: 1e3, }); list.length && (await func(list), callback && callback()); }, normal: function (list) { if (list.length === 1) { location.href = list[0].downloadUrl; } else { Swal.fire({ title: 'Urls', input: 'textarea', inputValue: list.map(ii => ii.downloadUrl).join('\n'), inputAttributes: { style: `height:${window.innerHeight * .5}px;white-space:nowrap`, }, width: '60%', }); } }, aria2: async function (list) { if (!that.a2config.remember) { if (!await that.configa2()) { return Toast.fire({ icon: 'info', title: 'Canceled', }); } } let res = await that.xhr({ method: 'post', responseType: 'json', url: `http://${that.a2config.host}:${that.a2config.port}/jsonrpc`, data: JSON.stringify({ id: 'INVOTOYS', jsonrpc: '2.0', method: 'system.multicall', params: [list.map(ii => ({ methodName: 'aria2.addUri', params: [[ii.downloadUrl], { dir: that.a2config.dir, referer: 'https://www.aliyundrive.com/', 'user-agent': navigator.userAgent, }], }))], }), }).catch(err => err); Toast.fire(res.status == 200 ? { icon: 'success', title: 'Sended successfully', } : { icon: 'error', title: 'Failed to connect to Aria2', text: res.error || '', }); }, configa2: async function (save) { let ret = await Swal.fire({ title: 'Aria2 Config', html: `
    Host
    Port
    Dir
    `, preConfirm: () => Object.fromEntries(new FormData(Swal.getHtmlContainer().firstChild).entries()), }); ret.isConfirmed && (save || ret.value.remember) && GM_setValue('a2config', that.a2config = ret.value); return ret.isConfirmed; }, ORI: { H_PUSHSTATE: History.prototype.pushState, }, HOOK: { H_PUSHSTATE: function () { dispatchEvent(new CustomEvent('PUSHSTATE', { detail: arguments[2] })); return that.ORI.H_PUSHSTATE.apply(this, arguments); }, }, }; that.install(); })();