Custom aliyundrive

阿里云直链导出

目前為 2022-07-18 提交的版本,檢視 最新版本

  1. // ==UserScript==
  2. // @name Custom aliyundrive
  3. // @name:zh Custom aliyundrive
  4. // @namespace https://github.com/invobzvr
  5. // @version 1.17
  6. // @description 阿里云直链导出
  7. // @author invobzvr
  8. // @match *://www.aliyundrive.com/drive*
  9. // @match *://www.aliyundrive.com/s/*
  10. // @grant GM_getValue
  11. // @grant GM_setValue
  12. // @grant GM_xmlhttpRequest
  13. // @connect 127.0.0.1
  14. // @connect localhost
  15. // @connect *
  16. // @require https://gf.qytechs.cn/scripts/443030-hook-js/code/Hookjs.js?version=1037826
  17. // @require https://gf.qytechs.cn/scripts/447483-box-js/code/Boxjs.js?version=1067657
  18. // @homepageURL https://github.com/invobzvr/invotoys.js/tree/main/aliyundrive
  19. // @supportURL https://github.com/invobzvr/invotoys.js/issues
  20. // @license GPL-3.0
  21. // ==/UserScript==
  22.  
  23. (function () {
  24. const that = {
  25. Toast: Box.mixin({ toast: true, time: 3e3 }),
  26. a2config: GM_getValue('a2config', {
  27. host: '127.0.0.1',
  28. port: 6800,
  29. dir: 'Download',
  30. }),
  31. xhr: function (details) {
  32. return new Promise((res, rej) => {
  33. GM_xmlhttpRequest(Object.assign(details, {
  34. onerror: rej,
  35. onload: res,
  36. }));
  37. });
  38. },
  39. wait: function (selectors, key) {
  40. return new Promise(res => {
  41. let el = document.querySelector(selectors),
  42. iid = setInterval(() => (el ? true : el = document.querySelector(selectors)) && (key ? el[key] : true) && (clearInterval(iid), res(el)), 100);
  43. });
  44. },
  45. install: async function () {
  46. that.inithook();
  47. addEventListener('pushstate', that.onPushState);
  48. that.rk = `__reactFiber$${Object.keys(await that.wait('#root', '_reactRootContainer')).find(ii => ii.startsWith('__reactContainer$')).split('$')[1]}`;
  49. that.tbwmo = new MutationObserver(that.tbwmc);
  50. that.ddmmo = new MutationObserver(that.ddmmc);
  51. that.ddmmo.observe(document.body, { childList: true });
  52. that.mmmo = new MutationObserver(that.mmmc);
  53. that.init();
  54. },
  55. inithook: function () {
  56. History.prototype.pushState.hook({
  57. scope: History.prototype,
  58. before: function () {
  59. dispatchEvent(new CustomEvent('pushstate', { detail: arguments[2] }));
  60. },
  61. });
  62. },
  63. init: async function () {
  64. that.listModel = (await that.wait('[class*=node-list--]'))[that.rk].return.memoizedProps.listModel;
  65. that.tbwmo.observe(document.querySelector('[class*=page-content--]'), { childList: true });
  66. },
  67. tbwmc: function ([mr]) {
  68. if (mr.addedNodes.length && (that.tbwel = mr.addedNodes[0].querySelector('[class*=toolbar-wrapper]'))) {
  69. let btn = that.tbwel.firstChild;
  70. that.tbwel.insertAdjacentHTML('afterbegin', '<div style="background:#fff;height:30px;margin-left:8px;width:.1px"></div>');
  71. let dlBtn = that.tbwel.insertAdjacentElement('afterbegin', btn.cloneNode(true)),
  72. a2Btn = that.tbwel.insertAdjacentElement('afterbegin', btn.cloneNode(true));
  73. dlBtn.title = 'Download';
  74. dlBtn.addEventListener('click', () => that.download(that.listModel.selectedItems, that.normal));
  75. a2Btn.title = 'Aria2';
  76. a2Btn.addEventListener('click', () => that.download(that.listModel.selectedItems, that.aria2, () => [...that.listModel.selectedIds].forEach(that.listModel.removeSelect)));
  77. a2Btn.addEventListener('contextmenu', evt => (evt.preventDefault(), that.configa2(true)));
  78. }
  79. },
  80. ddmmc: function ([mr]) {
  81. let ddm = mr.addedNodes.length && mr.addedNodes[0].querySelector('[class*=dropdown-menu--]');
  82. ddm && that.listModel && that.mmmo.observe(ddm, { attributes: true, attributeFilter: ['class'] });
  83. },
  84. mmmc: function ([mr]) {
  85. let el = mr.target;
  86. if (el.className.includes('-prepare')) {
  87. let props = el[that.rk].child.memoizedProps,
  88. list = location.pathname.startsWith('/s/') ? [props.model] : props.fileModel ? [props.fileModel] : props.fileListModel ? props.fileListModel.selectedItems : null;
  89. if (!list) {
  90. return;
  91. }
  92. let dlBtn, a2Btn,
  93. ul = el.firstChild;
  94. if (!el.querySelector('[custom]')) {
  95. let btn = ul.firstChild;
  96. ul.insertAdjacentHTML('afterbegin', '<li class="ant-dropdown-menu-item-divider" custom></li>');
  97. dlBtn = ul.insertAdjacentElement('afterbegin', btn.cloneNode(true));
  98. dlBtn.setAttribute('custom', 'normal')
  99. dlBtn.querySelector('[class*=menu-name--]').innerText = 'Download';
  100. a2Btn = ul.insertAdjacentElement('afterbegin', btn.cloneNode(true));
  101. a2Btn.setAttribute('custom', 'aria2')
  102. a2Btn.querySelector('[class*=menu-name--]').innerText = 'Aria2';
  103. } else {
  104. dlBtn = ul.querySelector('[custom=normal]');
  105. a2Btn = ul.querySelector('[custom=aria2]');
  106. }
  107. dlBtn.onclick = () => (that.closeMenu(), that.download(list, that.normal));
  108. a2Btn.onclick = () => (that.closeMenu(), that.download(list, that.aria2));
  109. a2Btn.oncontextmenu = evt => (evt.preventDefault(), evt.stopPropagation(), that.closeMenu(), that.configa2(true));
  110. }
  111. },
  112. closeMenu: function () {
  113. setTimeout(() => document.body.dispatchEvent(new MouseEvent('mousedown', { bubbles: true })));
  114. },
  115. onPushState: function (evt) {
  116. if (evt.detail === '/drive/' || evt.detail.startsWith('/drive/folder')) {
  117. !that.listModel && that.init();
  118. } else {
  119. that.listModel = null;
  120. that.tbwmo.disconnect();
  121. }
  122. },
  123. download: async function (list, func, callback) {
  124. list.length !== (list = list.filter(ii => ii.type == 'file')).length && new that.Toast({
  125. type: 'warning',
  126. title: 'Folders are skipped',
  127. });
  128. list.length && await func(list) && callback && callback();
  129. },
  130. normal: async function (list) {
  131. if (list.length === 1) {
  132. location.href = await that.urlOf(list[0]);
  133. } else {
  134. new Box({
  135. title: 'Urls',
  136. html: `<textarea style="height:${window.innerHeight * .5}px;width:100%;white-space:nowrap"></textarea>`,
  137. didBuild: async modal => {
  138. modal.style.width = '50%';
  139. modal.querySelector('textarea').value = (await Promise.all(list.map(ii => that.urlOf(ii)))).join('\n');
  140. },
  141. });
  142. }
  143. return true;
  144. },
  145. aria2: async function (list) {
  146. let a2config;
  147. if (!that.a2config.remember) {
  148. let ret = await that.configa2(false, modal => {
  149. let names = [...document.querySelectorAll('#root [class*=breadcrumb-item--]')];
  150. names = names.slice(1, names.length / 2).map(ii => ii.dataset.label);
  151. modal.querySelector('[name=dir]').insertAdjacentHTML('afterend', `<details>
  152. <summary><label class="box-option-item"><input class="box-input" name="wds" type="checkbox" onchange="this.closest('details').open = this.checked"><span class="box-label">with directory structure</span></label></summary>
  153. <input class="box-input" name="struct" value="${names.join('/')}">
  154. </details>`);
  155. });
  156. if (ret) {
  157. a2config = ret;
  158. } else {
  159. new that.Toast({
  160. type: 'warning',
  161. title: 'Cancelled',
  162. });
  163. return false;
  164. }
  165. }
  166. !a2config && (a2config = that.a2config);
  167. let dir = a2config.dir;
  168. a2config.wds && (dir = `${dir}/${a2config.struct}`);
  169. let data = {
  170. id: 'INVOTOYS',
  171. jsonrpc: '2.0',
  172. method: 'system.multicall',
  173. params: [await Promise.all(list.map(async ii => ({
  174. methodName: 'aria2.addUri',
  175. params: [[await that.urlOf(ii)], {
  176. dir: dir,
  177. out: ii.name,
  178. referer: 'https://www.aliyundrive.com/',
  179. 'user-agent': navigator.userAgent,
  180. }],
  181. })))],
  182. };
  183. if (a2config.token) {
  184. let token = `token:${a2config.token}`;
  185. data.params[0].forEach(ii => ii.params.unshift(token));
  186. }
  187. let res = await that.xhr({
  188. method: 'post',
  189. responseType: 'json',
  190. url: `http${a2config.https ? 's' : ''}://${a2config.host}:${a2config.port}/jsonrpc`,
  191. data: JSON.stringify(data),
  192. }).catch(err => err);
  193. new that.Toast(res.status == 200 ? {
  194. type: 'success',
  195. title: 'Sent to Aria2 successfully',
  196. } : {
  197. type: 'error',
  198. title: 'Failed to connect to Aria2',
  199. text: res.error,
  200. });
  201. return true;
  202. },
  203. urlOf: async function (model) {
  204. return model.downloadUrl || model.url || await model.getDownloadUrl();
  205. },
  206. configa2: async function (save, didBuild) {
  207. let ret = await new Box({
  208. title: 'Aria2 Config',
  209. html: `<form>
  210. <div class="box-input-group"><span class="box-label">Host</span><input class="box-input" name="host" value="${that.a2config.host}"></div>
  211. <div class="box-input-group"><span class="box-label">Port</span><input class="box-input" name="port" value="${that.a2config.port}"></div>
  212. <div class="box-input-group"><span class="box-label">Dir</span><input class="box-input" name="dir" value="${that.a2config.dir}"></div>
  213. <div class="box-input-group"><span class="box-label">Token</span><input class="box-input" name="token" value="${that.a2config.token || ''}"></div>
  214. <div class="box-options">
  215. <label class="box-option-item"><input class="box-input" name="https" type="checkbox"><span class="box-label"${that.a2config.https ? ' checked' : ''}>Https</span></label>
  216. <label class="box-option-item"><input class="box-input" name="remember" type="checkbox"><span class="box-label"${that.a2config.remember ? ' checked' : ''}>Remember</span></label>
  217. </div>
  218. </form>`,
  219. actions: {
  220. OK: modal => Object.fromEntries(new FormData(modal.querySelector('form'))),
  221. },
  222. didBuild: didBuild,
  223. });
  224. ret && (save || ret.remember) && GM_setValue('a2config', that.a2config = ret);
  225. return ret;
  226. },
  227. };
  228.  
  229. that.install();
  230. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址