Custom aliyundrive

阿里云直链导出

目前為 2022-05-06 提交的版本,檢視 最新版本

  1. // ==UserScript==
  2. // @name Custom aliyundrive
  3. // @name:zh Custom aliyundrive
  4. // @namespace https://github.com/invobzvr
  5. // @version 1.12
  6. // @description 阿里云直链导出
  7. // @author invobzvr
  8. // @match *://www.aliyundrive.com/drive*
  9. // @match *://www.aliyundrive.com/s/*
  10. // @grant GM_addStyle
  11. // @grant GM_getValue
  12. // @grant GM_setValue
  13. // @grant GM_xmlhttpRequest
  14. // @connect 127.0.0.1
  15. // @connect localhost
  16. // @connect *
  17. // @require https://gf.qytechs.cn/scripts/443030-hook-js/code/Hookjs.js?version=1037826
  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. a2config: GM_getValue('a2config', {
  26. host: '127.0.0.1',
  27. port: 6800,
  28. dir: 'Download',
  29. }),
  30. xhr: function (details) {
  31. return new Promise((res, rej) => {
  32. GM_xmlhttpRequest(Object.assign(details, {
  33. onerror: rej,
  34. onload: res,
  35. }));
  36. });
  37. },
  38. wait: function (selectors, key) {
  39. return new Promise(res => {
  40. let el = document.querySelector(selectors),
  41. iid = setInterval(() => (el ? true : el = document.querySelector(selectors)) && (key ? el[key] : true) && (clearInterval(iid), res(el)), 100);
  42. });
  43. },
  44. install: async function () {
  45. GM_addStyle(`.that-backdrop {
  46. background: #0006;
  47. bottom: 0;
  48. display: grid;
  49. left: 0;
  50. overflow: auto;
  51. position: fixed;
  52. right: 0;
  53. top: 0;
  54. z-index: 200;
  55. }
  56. .that-modal {
  57. align-self: center;
  58. background: #fff;
  59. border-radius: 5px;
  60. justify-self: center;
  61. margin: 20px;
  62. padding: 0 30px;
  63. user-select: none;
  64. }
  65. .that-title {
  66. font-size: 30px;
  67. padding: 20px;
  68. text-align: center;
  69. }
  70. .that-input-group {
  71. display: table;
  72. margin-bottom: 10px;
  73. }
  74. .that-label {
  75. display: table-cell;
  76. font-size: 16px;
  77. padding: 0 10px;
  78. text-align: center;
  79. width: 100%;
  80. }
  81. .that-input {
  82. font-size: 20px;
  83. padding: 5px 9px;
  84. }
  85. .that-input[type=checkbox] {
  86. vertical-align: middle;
  87. }
  88. .that-options {
  89. margin: auto;
  90. width: 80%;
  91. }
  92. .that-option-item {
  93. margin-left: 12px;
  94. white-space: nowrap;
  95. }
  96. .that-options .that-label {
  97. font-size: 13px;
  98. padding: 0 3px 0 0;
  99. }
  100. .that-actions {
  101. margin: 20px;
  102. text-align: center;
  103. }
  104. .that-button {
  105. background: #09f;
  106. border-radius: 5px;
  107. border: none;
  108. color: #fff;
  109. padding: 7px 18px;
  110. }
  111. .that-toastbox {
  112. display: grid;
  113. min-width: 360px;
  114. padding: 10px;
  115. position: fixed;
  116. right: 0;
  117. top: 0;
  118. width: 30%;
  119. z-index: 201;
  120. }
  121. .that-toast-item {
  122. background: #09f;
  123. border-radius: 7px;
  124. box-shadow: 0 2px 10px #0005;
  125. color: #fff;
  126. margin-bottom: 10px;
  127. padding: 10px 10px 15px 10px;
  128. opacity: 0;
  129. transition: .2s;
  130. transform: scale(.8);
  131. }
  132. .that-toast-item.in {
  133. opacity: 1;
  134. transform: scale(1);
  135. }
  136. .that-toast-item.success {
  137. background: #00a65a;
  138. }
  139. .that-toast-item.info {
  140. background: #ffa150;
  141. }
  142. .that-toast-item.error {
  143. background: #dd4b39;
  144. }
  145. .that-toast-item .that-title,
  146. .that-toast-item .that-label {
  147. padding: 0 10px;
  148. text-align: left;
  149. word-break: break-word;
  150. }`);
  151. that.inithook();
  152. addEventListener('pushstate', that.onPushState);
  153. that.rk = `__reactFiber$${Object.keys(await that.wait('#root', '_reactRootContainer')).find(ii => ii.startsWith('__reactContainer$')).split('$')[1]}`;
  154. that.tbwmo = new MutationObserver(that.tbwmc);
  155. that.ddmmo = new MutationObserver(that.ddmmc);
  156. that.ddmmo.observe(document.body, { childList: true });
  157. that.mmmo = new MutationObserver(that.mmmc);
  158. that.init();
  159. },
  160. inithook: function () {
  161. History.prototype.pushState.hook({
  162. scope: History.prototype,
  163. before: function () {
  164. dispatchEvent(new CustomEvent('pushstate', { detail: arguments[2] }));
  165. },
  166. });
  167. },
  168. init: async function () {
  169. that.listModel = (await that.wait('[class*=node-list--]'))[that.rk].return.memoizedProps.listModel;
  170. that.tbwmo.observe(document.querySelector('[class*=page-content--]'), { childList: true });
  171. },
  172. tbwmc: function ([mr]) {
  173. if (mr.addedNodes.length && (that.tbwel = mr.addedNodes[0].querySelector('[class*=toolbar-wrapper]'))) {
  174. let btn = that.tbwel.firstChild;
  175. that.tbwel.insertAdjacentHTML('afterbegin', '<div style="background:#fff;height:30px;margin-left:8px;width:.1px"></div>');
  176. let dlBtn = that.tbwel.insertAdjacentElement('afterbegin', btn.cloneNode(true)),
  177. a2Btn = that.tbwel.insertAdjacentElement('afterbegin', btn.cloneNode(true));
  178. dlBtn.title = 'Download';
  179. dlBtn.addEventListener('click', () => that.download(that.listModel.selectedItems, that.normal));
  180. a2Btn.title = 'Aria2';
  181. a2Btn.addEventListener('click', () => that.download(that.listModel.selectedItems, that.aria2, () => [...that.listModel.selectedIds].forEach(that.listModel.removeSelect)));
  182. a2Btn.addEventListener('contextmenu', evt => (evt.preventDefault(), that.configa2(true)));
  183. }
  184. },
  185. ddmmc: function ([mr]) {
  186. let ddm = mr.addedNodes.length && mr.addedNodes[0].querySelector('[class*=dropdown-menu--]');
  187. ddm && that.listModel && that.mmmo.observe(ddm, { attributes: true, attributeFilter: ['class'] });
  188. },
  189. mmmc: function ([mr]) {
  190. let el = mr.target;
  191. if (el.className.includes('-prepare')) {
  192. let props = el[that.rk].child.memoizedProps,
  193. list = location.pathname.startsWith('/s/') ? [props.model] : props.fileModel ? [props.fileModel] : props.fileListModel ? props.fileListModel.selectedItems : null;
  194. if (!list) {
  195. return;
  196. }
  197. let dlBtn, a2Btn,
  198. ul = el.firstChild;
  199. if (!el.querySelector('[custom]')) {
  200. let btn = ul.firstChild;
  201. ul.insertAdjacentHTML('afterbegin', '<li class="ant-dropdown-menu-item-divider" custom></li>');
  202. dlBtn = ul.insertAdjacentElement('afterbegin', btn.cloneNode(true));
  203. dlBtn.setAttribute('custom', 'normal')
  204. dlBtn.querySelector('[class*=menu-name--]').innerText = 'Download';
  205. a2Btn = ul.insertAdjacentElement('afterbegin', btn.cloneNode(true));
  206. a2Btn.setAttribute('custom', 'aria2')
  207. a2Btn.querySelector('[class*=menu-name--]').innerText = 'Aria2';
  208. } else {
  209. dlBtn = ul.querySelector('[custom=normal]');
  210. a2Btn = ul.querySelector('[custom=aria2]');
  211. }
  212. dlBtn.onclick = () => (that.closeMenu(), that.download(list, that.normal));
  213. a2Btn.onclick = () => (that.closeMenu(), that.download(list, that.aria2));
  214. a2Btn.oncontextmenu = evt => (evt.preventDefault(), evt.stopPropagation(), that.closeMenu(), that.configa2(true));
  215. }
  216. },
  217. closeMenu: function () {
  218. setTimeout(() => document.body.dispatchEvent(new MouseEvent('mousedown', { bubbles: true })));
  219. },
  220. onPushState: function (evt) {
  221. if (evt.detail === '/drive/' || evt.detail.startsWith('/drive/folder')) {
  222. !that.listModel && that.init();
  223. } else {
  224. that.listModel = null;
  225. that.tbwmo.disconnect();
  226. }
  227. },
  228. download: async function (list, func, callback) {
  229. list.length !== (list = list.filter(ii => ii.type == 'file')).length && that.toast({
  230. type: 'info',
  231. title: 'Folders are skipped',
  232. timer: 1e3,
  233. });
  234. list.length && (await func(list), callback && callback());
  235. },
  236. normal: async function (list) {
  237. if (list.length === 1) {
  238. location.href = await that.urlOf(list[0]);
  239. } else {
  240. let ctnr = that.modal('that-backdrop', `<div class="that-modal" style="width:50%">
  241. <div class="that-title">Urls</div>
  242. <textarea style="height:${window.innerHeight * .5}px;width:100%;white-space:nowrap"></textarea>
  243. <div class="that-actions">
  244. <button class="that-button">OK</button>
  245. </div>
  246. </div>`);
  247. ctnr.querySelector('textarea').value = (await Promise.all(list.map(ii => that.urlOf(ii)))).join('\n');
  248. ctnr.onclick = evt => ['that-backdrop', 'that-button'].includes(evt.target.className) && ctnr.remove();
  249. }
  250. },
  251. aria2: async function (list) {
  252. let a2config;
  253. if (!that.a2config.remember) {
  254. let ret = await that.configa2();
  255. if (ret) {
  256. a2config = ret;
  257. } else {
  258. return that.toast({
  259. type: 'info',
  260. title: 'Canceled',
  261. });
  262. }
  263. }
  264. !a2config && (a2config = that.a2config);
  265. let data = {
  266. id: 'INVOTOYS',
  267. jsonrpc: '2.0',
  268. method: 'system.multicall',
  269. params: [await Promise.all(list.map(async ii => ({
  270. methodName: 'aria2.addUri',
  271. params: [[await that.urlOf(ii)], {
  272. dir: a2config.dir,
  273. out: ii.name,
  274. referer: 'https://www.aliyundrive.com/',
  275. 'user-agent': navigator.userAgent,
  276. }],
  277. })))],
  278. };
  279. if (a2config.token) {
  280. let token = `token:${a2config.token}`;
  281. data.params[0].forEach(ii => ii.params.unshift(token));
  282. }
  283. let res = await that.xhr({
  284. method: 'post',
  285. responseType: 'json',
  286. url: `http${a2config.https ? 's' : ''}://${a2config.host}:${a2config.port}/jsonrpc`,
  287. data: JSON.stringify(data),
  288. }).catch(err => err);
  289. that.toast(res.status == 200 ? {
  290. type: 'success',
  291. title: 'Sent successfully',
  292. } : {
  293. type: 'error',
  294. title: 'Failed to connect to Aria2',
  295. text: res.error,
  296. });
  297. },
  298. urlOf: async function (model) {
  299. return model.downloadUrl || model.url || await model.getDownloadUrl();
  300. },
  301. configa2: async function (save) {
  302. let ret = await new Promise(res => {
  303. let ctnr = that.modal('that-backdrop', `<div class="that-modal">
  304. <div class="that-title">Aria2 Config</div>
  305. <form>
  306. <div class="that-input-group"><span class="that-label">Host</span><input class="that-input" name="host" value="${that.a2config.host}"></div>
  307. <div class="that-input-group"><span class="that-label">Port</span><input class="that-input" name="port" value="${that.a2config.port}"></div>
  308. <div class="that-input-group"><span class="that-label">Dir</span><input class="that-input" name="dir" value="${that.a2config.dir}"></div>
  309. <div class="that-input-group"><span class="that-label">Token</span><input class="that-input" name="token" value="${that.a2config.token || ''}"></div>
  310. <div class="that-options">
  311. <label class="that-option-item"><span class="that-label">Https</span><input class="that-input" name="https" type="checkbox"${that.a2config.https ? ' checked' : ''}></label>
  312. <label class="that-option-item"><span class="that-label">Remember</span><input class="that-input" name="remember" type="checkbox"${that.a2config.remember ? ' checked' : ''}></label>
  313. </div>
  314. </form>
  315. <div class="that-actions">
  316. <button class="that-button">OK</button>
  317. </div>
  318. </div>`);
  319. ctnr.onclick = evt => {
  320. switch (evt.target.className) {
  321. case 'that-backdrop':
  322. ctnr.remove();
  323. res();
  324. break;
  325. case 'that-button':
  326. ctnr.remove();
  327. res(Object.fromEntries(new FormData(ctnr.querySelector('form'))));
  328. break;
  329. }
  330. }
  331. });
  332. ret && (save || ret.remember) && GM_setValue('a2config', that.a2config = ret);
  333. return ret;
  334. },
  335. modal: function (ctnrName, innerHTML) {
  336. let ctnr = document.querySelector(`.${ctnrName}`);
  337. if (!ctnr) {
  338. ctnr = document.body.appendChild(document.createElement('div'));
  339. ctnr.className = ctnrName;
  340. }
  341. ctnr[innerHTML instanceof Element ? 'insertAdjacentElement' : 'insertAdjacentHTML']('beforeend', innerHTML);
  342. return ctnr;
  343. },
  344. toast: function (options) {
  345. let tst = document.createElement('div'),
  346. ctnr = that.modal('that-toastbox', tst);
  347. tst.className = `that-toast-item ${options.type || ''}`;
  348. tst.innerHTML = `<div class="that-title">${options.title || ''}</div><div class="that-label">${options.text || ''}</div>`;
  349. setTimeout(() => tst.classList.add('in'), 10);
  350. options.time !== null && setTimeout(() => {
  351. tst.classList.remove('in');
  352. tst.addEventListener('transitionend', () => (tst.remove(), !ctnr.childElementCount && ctnr.remove()));
  353. }, options.time || 3e3);
  354. },
  355. };
  356.  
  357. that.install();
  358. })();

QingJ © 2025

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