LinkSwift

《也许同类型中最好用?》系列 - 一个基于 JavaScript 的网盘文件下载地址获取工具,基于【网盘直链下载助手】修改 | 支持 百度网盘/阿里云盘/中国移动云盘/天翼云盘/迅雷云盘/夸克网盘/UC网盘/123云盘 八大网盘 | 开源・自用・去广 | 改界面・添功能・修Bug | 既超越原版,亦是同类中最好用版本!👋

  1. // ==UserScript==
  2. // @name LinkSwift
  3. // @namespace github.com/hmjz100
  4. // @version 1.1.0.1
  5. // @author Hmjz100、油小猴
  6. // @icon data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMjggMTI4Ij48cGF0aCBkPSJNMTAzLjYgMTA3LjRjMy41LTIuMiA4LjktNi4xIDEzLjgtMTIuNXM3LjMtMTIuNSA4LjUtMTYuNWMuNS0xLjcgMi4yLTcuNSAyLjItMTQuNyAwLTEwLjEtMy4zLTI1LjEtMTUuNC0zNi44LTE0LjUtMTQtMzIuMS0xNC4zLTM1LjctMTQuMy04IDAtMTUuNyAxLjktMjIuNiA1LjJDNDQgMjMgMzUuNyAzMS40IDMwLjggNDEuN2MtMS4zIDIuOC00IDQuNy03LjEgNS00IC4zLTcuNSA0LjQtOC45IDkuNi0uNSAxLjktMS42IDMuNS0zLjEgNC43QzQuNCA2Ni44IDAgNzUuNyAwIDg1YzAgNi44IDIuMyAxMy4xIDYuMSAxOC4yIDUuNSA3LjQgMTQuMiAxMi4yIDI0IDEyLjJoNDcuMWM0LjQgMCAxMS0uNSAxOC4zLTMuNSAzLjItMS40IDUuOS0zIDguMS00LjV6IiBmaWxsPSIjQTA5OUYwIi8+PHBhdGggZD0iTTExOS44IDY0LjNjLjEtMTcuMS0xMC40LTI4LTEyLjUtMzAuMUM5NSAyMi4xIDc5LjkgMjEuOCA3Ni45IDIxLjhjLTE3LjYgMC0zMy4zIDEwLjUtMzkuOSAyNi43LS42IDEuMy0xLjggMi4zLTMuNCAyLjNoLS40Yy01LjggMC0xMC42IDQuOC0xMC42IDEwLjd2LjVjMCAxLjQtLjggMi42LTEuOSAzLjNDMTMuNCA2OSA4LjggNzYuOCA4LjggODVjMCAxMi4yIDkuOSAyMi4zIDIyLjIgMjIuM2g0NS4yYzMuNi0uMSAxNy42LS45IDI5LjYtMTIgMi45LTIuOCAxMy45LTEzLjcgMTQtMzF6IiBmaWxsPSIjNTc0QUI4Ii8+PHBhdGggZD0iTTExMC44IDU3LjRsLjIgMy4zYzAgMS4zLTEuMSAyLjQtMi4zIDIuNC0xLjMgMC0yLjMtMS4xLTIuMy0yLjRsLS4xLTIuOHYtLjNjMC0xLjIuOS0yLjIgMi4xLTIuM2guM2MuNyAwIDEuMy4zIDEuNy43LS4yLjEuMy41LjQgMS40em0tMy4zLTEwLjNjMCAxLjItMSAyLjMtMi4yIDIuM2gtLjFjLS44IDAtMS42LS41LTItMS4yLTQuNi04LjMtMTMuMy0xMy41LTIyLjgtMTMuNS0xLjIgMC0yLjMtMS0yLjMtMi4ydi0uMWMwLTEuMiAxLTIuMyAyLjItMi4zaC4xYTMwLjM3IDMwLjM3IDAgMCAxIDE1LjggNC40YzQuNiAyLjggOC40IDYuOCAxMS4xIDExLjUuMS4zLjIuNy4yIDEuMXpNODguMyA3My44TDczLjUgOTMuMmMtMS41IDEuOS0zLjUgMy4xLTUuNyAzLjVoLS4yYy0uNC4xLS44LjEtMS4yLjEtLjYgMC0xLjEtLjEtMS42LS4yLTIuMi0uNC00LjItMS43LTUuNi0zLjVMNDQuMyA3My45Yy0yLTIuNi0yLjUtNS40LTEuNC03LjcuMS0uMS4xLS4yLjItLjIgMS4yLTIgMy41LTMuMiA2LjQtMy4yaDYuNnYtNS43YzAtNi44IDQuNy0xMiAxMC45LTEyIDQuOCAwIDguNSAyLjYgMTAuMyA3LjIuNSAxLjMtLjIgMi43LTEuNSAzLjJzLTIuOC0uMS0zLjMtMS40Yy0xLjEtMi43LTIuOS00LTUuNS00LTMuNSAwLTYgMy02IDd2OC4xYzAgLjUtLjIgMS0uNiAxLjQtLjYuNy0xLjcgMS4xLTIuNiAxLjFoLTguNGMtMS4zIDAtMiAuNC0yLjEuNy0uMi40IDAgMS4zLjkgMi40TDYzLjEgOTBjLjkgMS4yIDIuMSAxLjggMy4zIDEuOHMyLjMtLjYgMy4xLTEuN2wxNC44LTE5LjNjLjktMS4xIDEuMS0yIC45LTIuNC0uMi0uMy0uOS0uNy0yLjEtLjdoLTcuNmMtLjkgMC0xLjctLjUtMi4xLTEuMi0uMy0uNC0uNC0uOC0uNC0xLjMgMC0xLjQgMS4xLTIuNSAyLjUtMi41aDcuNmMzLjEgMCA1LjUgMS4zIDYuNiAzLjVsLjMuN2MuNyAyLjEuMSA0LjYtMS43IDYuOXoiIGZpbGw9IiNmZmYiLz48L3N2Zz4=
  7. // @description 《也许同类型中最好用?》系列 - 一个基于 JavaScript 的网盘文件下载地址获取工具,基于【网盘直链下载助手】修改 | 支持 百度网盘/阿里云盘/中国移动云盘/天翼云盘/迅雷云盘/夸克网盘/UC网盘/123云盘 八大网盘 | 开源・自用・去广 | 改界面・添功能・修Bug | 既超越原版,亦是同类中最好用版本!👋
  8. // @description:zh-TW 《也許同類型中最好用?》系列 - 一個基於 JavaScript 的網盤檔案下載地址獲取工具,基於【網盤直鏈下載助手】改編 | 支援 百度網盤/阿里雲盤/中國移動雲盤/天翼雲盤/迅雷雲盤/夸克網盤/UC網盤/123雲盤 八大平台 | 開源・自用・除廣 | 改介面・擴功能・修Bug | 既超越原版,亦是同類中最好用版本!👋
  9. // @description:zh-HK 《也許同類型中最好用?》系列 - 一個基於 JavaScript 的網盤檔案下載地址獲取工具,基於【網盤直鏈下載助手】改編 | 支援 百度網盤/阿里雲盤/中國移動雲盤/天翼雲盤/迅雷雲盤/夸克網盤/UC網盤/123雲盤 八大平台 | 開源・自用・除廣 | 改介面・擴功能・修Bug | 既超越原版,亦是同類中最好用版本!👋
  10. // @license AGPL-3.0-or-later
  11. // @homepage https://github.com/hmjz100/LinkSwift/
  12. // @support https://github.com/hmjz100/LinkSwift/issues
  13. // @supportURL https://github.com/hmjz100/LinkSwift/issues
  14. // @require https://unpkg.com/jquery@3.6.0/dist/jquery.min.js
  15. // @require https://unpkg.com/sweetalert2@11.4.8/dist/sweetalert2.min.js
  16. // @resource SwalLigt https://unpkg.com/sweetalert2@11.4.8/dist/sweetalert2.min.css
  17. // @resource SwalDark https://unpkg.com/@sweetalert2/theme-dark@5.0.26/dark.min.css
  18. // @require https://unpkg.com/js-md5@0.7.3/build/md5.min.js
  19. // @run-at document-start
  20. // @match *://pan.baidu.com/disk/home*
  21. // @match *://yun.baidu.com/disk/home*
  22. // @match *://pan.baidu.com/disk/timeline*
  23. // @match *://yun.baidu.com/disk/timeline*
  24. // @match *://pan.baidu.com/disk/main*
  25. // @match *://yun.baidu.com/disk/main*
  26. // @match *://pan.baidu.com/youth/pan/main*
  27. // @match *://yun.baidu.com/youth/pan/main*
  28. // @match *://pan.baidu.com/disk/base*
  29. // @match *://yun.baidu.com/disk/base*
  30. // @match *://pan.baidu.com/disk/timeline*
  31. // @match *://yun.baidu.com/disk/timeline*
  32. // @match *://pan.baidu.com/pfile/*
  33. // @match *://yun.baidu.com/pfile/*
  34. // @match *://pan.baidu.com/s/*
  35. // @match *://pan.baidu.com/aipan/*
  36. // @match *://yun.baidu.com/s/*
  37. // @match *://yun.baidu.com/aipan/*
  38. // @match *://pan.baidu.com/share/*
  39. // @match *://yun.baidu.com/share/*
  40. // @match *://pan.baidu.com/embed/*
  41. // @match *://yun.baidu.com/embed/*
  42. // @match *://openapi.baidu.com/*
  43. // @match *://www.aliyundrive.com/s/*
  44. // @match *://www.aliyundrive.com/drive*
  45. // @match *://www.alipan.com/s/*
  46. // @match *://www.alipan.com/drive*
  47. // @match *://cloud.189.cn/web/*
  48. // @match *://pan.xunlei.com/*
  49. // @match *://pan.quark.cn/*
  50. // @match *://drive.uc.cn/*
  51. // @match *://yun.139.com/*
  52. // @match *://caiyun.139.com/*
  53. // @match *://*.123pan.com/*
  54. // @match *://*.123pan.cn/*
  55. // @match *://*.123684.com/*
  56. // @match *://*.123865.com/*
  57. // @match *://*.123952.com/*
  58. // @match *://*.123912.com/*
  59. // @match *://*.youxiaohou.com/*
  60. // @connect baidu.com
  61. // @connect baidupcs.com
  62. // @connect aliyundrive.com
  63. // @connect aliyundrive.net
  64. // @connect alipan.com
  65. // @connect alicloudccp.com
  66. // @connect aliyundrive.cloud
  67. // @connect 189.cn
  68. // @connect xunlei.com
  69. // @connect quark.cn
  70. // @connect uc.cn
  71. // @connect 123pan.com
  72. // @connect 123pan.cn
  73. // @connect 123684.com
  74. // @connect 123865.com
  75. // @connect 123952.com
  76. // @connect 123912.com
  77. // @connect cjjd19.com
  78. // @connect localhost
  79. // @connect *
  80. // @grant unsafeWindow
  81. // @grant window.close
  82. // @grant GM_xmlhttpRequest
  83. // @grant GM.xmlhttpRequest
  84. // @grant GM_setClipboard
  85. // @grant GM_setValue
  86. // @grant GM_getValue
  87. // @grant GM_deleteValue
  88. // @grant GM_openInTab
  89. // @grant GM_info
  90. // @grant GM_registerMenuCommand
  91. // @grant GM_cookie
  92. // @grant GM_getResourceText
  93. // @compatible Chrome
  94. // @compatible Edge
  95. // @compatible Firefox
  96. // @compatible Safari
  97. // @compatible Opera
  98. // ==/UserScript==
  99. /**
  100. * @name LinkSwift
  101. * @template (改)网盘直链下载助手
  102. * @author 油小猴
  103. * @author hmjz100
  104. * @namespace github.com/hmjz100
  105. * @description 一个基于 JavaScript 盘的文件下载地址获取工具
  106. * 支持 百度网盘/阿里云盘/中国移动云盘/天翼云盘/迅雷云盘/夸克网盘/UC网盘/123云盘 八大网盘
  107. * @version 1.1.0.1
  108. * @license AGPL-3.0-or-later
  109. * @see {@link https://github.com/hmjz100/LinkSwift/ Github 仓库}
  110. */
  111. (function linkSwift() {
  112. // 严格模式,确保代码安全执行
  113. 'use strict';
  114.  
  115. // unsafeWindow 检测
  116. if (typeof unsafeWindow === 'undefined') {
  117. window.unsafeWindow = window;
  118. }
  119.  
  120. /*
  121. 防止代码因其他原因被执行多次
  122. 代码出自 “Via 轻插件”,作者谷花泰
  123. */
  124. const key = encodeURIComponent('LinkSwift:主代码');
  125. if (window[key]) return;
  126. window[key] = true;
  127.  
  128. /*
  129. 网盘直链下载助手
  130. 代码改自 “网盘直链下载助手”,作者油小猴
  131. 有增添新代码
  132. */
  133. /* 全局参数 */
  134. let mount = idontknow("LinkSwift")
  135. let page = '', selectList = [], shareParams = {}, mode = '', color = '',
  136. doc = $(document), progress = {}, request = {}, ins = {}, idm = {}, colored = false, replacedElements = new Set(),
  137. scriptInfo = GM_info.script,
  138. sauthor = scriptInfo.author,
  139. sname = scriptInfo.name,
  140. sversion = (scriptInfo?.version?.toString() || "1.1.0.1"),
  141. sicon = (scriptInfo?.icon || "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMjggMTI4Ij48cGF0aCBkPSJNMTAzLjYgMTA3LjRjMy41LTIuMiA4LjktNi4xIDEzLjgtMTIuNXM3LjMtMTIuNSA4LjUtMTYuNWMuNS0xLjcgMi4yLTcuNSAyLjItMTQuNyAwLTEwLjEtMy4zLTI1LjEtMTUuNC0zNi44LTE0LjUtMTQtMzIuMS0xNC4zLTM1LjctMTQuMy04IDAtMTUuNyAxLjktMjIuNiA1LjJDNDQgMjMgMzUuNyAzMS40IDMwLjggNDEuN2MtMS4zIDIuOC00IDQuNy03LjEgNS00IC4zLTcuNSA0LjQtOC45IDkuNi0uNSAxLjktMS42IDMuNS0zLjEgNC43QzQuNCA2Ni44IDAgNzUuNyAwIDg1YzAgNi44IDIuMyAxMy4xIDYuMSAxOC4yIDUuNSA3LjQgMTQuMiAxMi4yIDI0IDEyLjJoNDcuMWM0LjQgMCAxMS0uNSAxOC4zLTMuNSAzLjItMS40IDUuOS0zIDguMS00LjV6IiBmaWxsPSIjQTA5OUYwIi8+PHBhdGggZD0iTTExOS44IDY0LjNjLjEtMTcuMS0xMC40LTI4LTEyLjUtMzAuMUM5NSAyMi4xIDc5LjkgMjEuOCA3Ni45IDIxLjhjLTE3LjYgMC0zMy4zIDEwLjUtMzkuOSAyNi43LS42IDEuMy0xLjggMi4zLTMuNCAyLjNoLS40Yy01LjggMC0xMC42IDQuOC0xMC42IDEwLjd2LjVjMCAxLjQtLjggMi42LTEuOSAzLjNDMTMuNCA2OSA4LjggNzYuOCA4LjggODVjMCAxMi4yIDkuOSAyMi4zIDIyLjIgMjIuM2g0NS4yYzMuNi0uMSAxNy42LS45IDI5LjYtMTIgMi45LTIuOCAxMy45LTEzLjcgMTQtMzF6IiBmaWxsPSIjNTc0QUI4Ii8+PHBhdGggZD0iTTExMC44IDU3LjRsLjIgMy4zYzAgMS4zLTEuMSAyLjQtMi4zIDIuNC0xLjMgMC0yLjMtMS4xLTIuMy0yLjRsLS4xLTIuOHYtLjNjMC0xLjIuOS0yLjIgMi4xLTIuM2guM2MuNyAwIDEuMy4zIDEuNy43LS4yLjEuMy41LjQgMS40em0tMy4zLTEwLjNjMCAxLjItMSAyLjMtMi4yIDIuM2gtLjFjLS44IDAtMS42LS41LTItMS4yLTQuNi04LjMtMTMuMy0xMy41LTIyLjgtMTMuNS0xLjIgMC0yLjMtMS0yLjMtMi4ydi0uMWMwLTEuMiAxLTIuMyAyLjItMi4zaC4xYTMwLjM3IDMwLjM3IDAgMCAxIDE1LjggNC40YzQuNiAyLjggOC40IDYuOCAxMS4xIDExLjUuMS4zLjIuNy4yIDEuMXpNODguMyA3My44TDczLjUgOTMuMmMtMS41IDEuOS0zLjUgMy4xLTUuNyAzLjVoLS4yYy0uNC4xLS44LjEtMS4yLjEtLjYgMC0xLjEtLjEtMS42LS4yLTIuMi0uNC00LjItMS43LTUuNi0zLjVMNDQuMyA3My45Yy0yLTIuNi0yLjUtNS40LTEuNC03LjcuMS0uMS4xLS4yLjItLjIgMS4yLTIgMy41LTMuMiA2LjQtMy4yaDYuNnYtNS43YzAtNi44IDQuNy0xMiAxMC45LTEyIDQuOCAwIDguNSAyLjYgMTAuMyA3LjIuNSAxLjMtLjIgMi43LTEuNSAzLjJzLTIuOC0uMS0zLjMtMS40Yy0xLjEtMi43LTIuOS00LTUuNS00LTMuNSAwLTYgMy02IDd2OC4xYzAgLjUtLjIgMS0uNiAxLjQtLjYuNy0xLjcgMS4xLTIuNiAxLjFoLTguNGMtMS4zIDAtMiAuNC0yLjEuNy0uMi40IDAgMS4zLjkgMi40TDYzLjEgOTBjLjkgMS4yIDIuMSAxLjggMy4zIDEuOHMyLjMtLjYgMy4xLTEuN2wxNC44LTE5LjNjLjktMS4xIDEuMS0yIC45LTIuNC0uMi0uMy0uOS0uNy0yLjEtLjdoLTcuNmMtLjkgMC0xLjctLjUtMi4xLTEuMi0uMy0uNC0uNC0uOC0uNC0xLjMgMC0xLjQgMS4xLTIuNSAyLjUtMi41aDcuNmMzLjEgMCA1LjUgMS4zIDYuNiAzLjVsLjMuN2MuNyAyLjEuMSA0LjYtMS43IDYuOXoiIGZpbGw9IiNmZmYiLz48L3N2Zz4="),
  142. mhandler = GM_info.scriptHandler,
  143. mversion = GM_info.version;
  144.  
  145. /* 设置选项 */
  146. // Shell类型(用于curl下载)
  147. let terminalType = {
  148. wc: "Microsoft Windows 命令提示符",
  149. wp: "Microsoft Windows PowerShell",
  150. lt: "Linux 终端",
  151. ls: "Linux Shell",
  152. mt: "Apple MacOS 终端",
  153. };
  154.  
  155. // 更换 百度网盘新界面/阿里云盘/迅雷云盘/移动云盘 主题颜色
  156. let assistantTheme = {
  157. yes: "更换主题颜色",
  158. no: "不更换主题颜色"
  159. };
  160.  
  161. /* Sweet Alert 2 */
  162. // 自定义元素 Class 名(于 showMainDialog() 中)
  163. let customClass = {
  164. popup: 'pl-popup',
  165. header: 'pl-header',
  166. title: 'pl-title',
  167. closeButton: 'pl-close',
  168. content: 'pl-content',
  169. input: 'pl-input',
  170. footer: 'pl-footer'
  171. };
  172.  
  173. // 弹窗默认设置
  174. let swalDefault = {
  175. heightAuto: false,
  176. scrollbarPadding: false,
  177. }
  178.  
  179. /**
  180. * SweetAlert2 的 Toast 提示框基础配置
  181. * @author 油小猴
  182. * @author hmjz100
  183. * @description 创建一个全局通用的 Toast 提示框实例,支持自动关闭、鼠标悬停暂停、右下角弹出等特性。
  184. *
  185. * @type {Sweetalert2.Toast}
  186. */
  187. let toast = Swal.mixin({
  188. toast: true,
  189. position: 'bottom-end',
  190. showConfirmButton: false,
  191. timer: 3500,
  192. timerProgressBar: true,
  193. showCloseButton: true,
  194. didOpen: function (toast) {
  195. toast.addEventListener('mouseenter', Swal.stopTimer);
  196. toast.addEventListener('mouseleave', Swal.resumeTimer);
  197. }
  198. });
  199.  
  200. /**
  201. * 消息提示工具类
  202. * @author 油小猴
  203. * @description 提供统一的提示信息展示方法,基于 SweetAlert2 的 Toast 实现;
  204. * 包含 success / error / warning / info / question 等类型。
  205. */
  206. let message = {
  207. success: function (text) {
  208. toast.fire({ title: text, icon: 'success' });
  209. },
  210. error: function (text) {
  211. toast.fire({ title: text, icon: 'error' });
  212. },
  213. warning: function (text) {
  214. toast.fire({ title: text, icon: 'warning' });
  215. },
  216. info: function (text) {
  217. toast.fire({ title: text, icon: 'info' });
  218. },
  219. question: function (text) {
  220. toast.fire({ title: text, icon: 'question' });
  221. }
  222. };
  223.  
  224. /**
  225. * 基础配置集合
  226. * @author 油小猴
  227. * @author hmjz100
  228. */
  229. const config = {
  230. base: {
  231. num: "865746",
  232. license: "AGPL3",
  233. service: {
  234. account: "https://pic.rmb.bdstatic.com/bjh/8b9e14345b3cdf96aedac2f3971adcb02681.png",
  235. rpc: "http://d.youxiaohou.com"
  236. },
  237. dom: {
  238. footer: "o(≧▽≦)o 十分感谢您的支持!来给此项目一个 <a href=\"https://github.com/hmjz100/LinkSwift\" target=\"_blank\" class=\"pl-a\" data-no-instant=\"1\">Star</a> 吧~",
  239. button: {
  240. api: {
  241. title: "API 下载",
  242. footer: "适用于 <a href=\"https://www.youxiaohou.com/zh-cn/idm.html\" target=\"_blank\" class=\"pl-a\" data-no-instant=\"1\">IDM</a>,<a href=\"https://www.youxiaohou.com/zh-cn/ndm.html\" target=\"_blank\" class=\"pl-a\" data-no-instant=\"1\">NDM</a> 以及浏览器自带下载<br/>o(≧▽≦)o 十分感谢您的支持!来给此项目一个 <a href=\"https://github.com/hmjz100/LinkSwift\" target=\"_blank\" class=\"pl-a\" data-no-instant=\"1\">Star</a> 吧~"
  243. },
  244. aria: {
  245. title: "Aria 下载",
  246. footer: "适用于 <a href=\"https://www.youxiaohou.com/zh-cn/xdown.html\" target=\"_blank\" class=\"pl-a\" data-no-instant=\"1\">XDown</a> 及 <a href=\"https://www.youxiaohou.com/zh-cn/linux.html#linux-shell\" target=\"_blank\" class=\"pl-a\" data-no-instant=\"1\">Linux Shell命令行</a><br/>o(≧▽≦)o 十分感谢您的支持!来给此项目一个 <a href=\"https://github.com/hmjz100/LinkSwift\" target=\"_blank\" class=\"pl-a\" data-no-instant=\"1\">Star</a> 吧~"
  247. },
  248. rpc: {
  249. title: "RPC 下载",
  250. footer: "适用于 <a href=\"https://www.youxiaohou.com/zh-cn/motrix.html\" target=\"_blank\" class=\"pl-a\" data-no-instant=\"1\">Motrix</a>,<a href=\"https://www.youxiaohou.com/download.html\" target=\"_blank\" class=\"pl-a\" data-no-instant=\"1\">Aria2 Tools</a>,<a href=\"https://www.youxiaohou.com/download.html\" target=\"_blank\" class=\"pl-a\" data-no-instant=\"1\">AriaNgGUI</a><br/>o(≧▽≦)o 十分感谢您的支持!来给此项目一个 <a href=\"https://github.com/hmjz100/LinkSwift\" target=\"_blank\" class=\"pl-a\" data-no-instant=\"1\">Star</a> 吧~"
  251. },
  252. curl: {
  253. title: "cURL 下载",
  254. footer: "适用于 <a href=\"https://www.youxiaohou.com/zh-cn/curl.html\" target=\"_blank\" class=\"pl-a\" data-no-instant=\"1\">Windows,Linux,MacOS 终端</a><br/>o(≧▽≦)o 十分感谢您的支持!来给此项目一个 <a href=\"https://github.com/hmjz100/LinkSwift\" target=\"_blank\" class=\"pl-a\" data-no-instant=\"1\">Star</a> 吧~"
  255. },
  256. bc: {
  257. title: "BC 下载",
  258. footer: "适用于 <a href=\"https://www.youxiaohou.com/zh-cn/bitcomet.html\" target=\"_blank\" class=\"pl-a\" data-no-instant=\"1\">比特彗星</a><br/>o(≧▽≦)o 十分感谢您的支持!来给此项目一个 <a href=\"https://github.com/hmjz100/LinkSwift\" target=\"_blank\" class=\"pl-a\" data-no-instant=\"1\">Star</a> 吧~"
  259. }
  260. }
  261. },
  262. assistant: {
  263. message: "请先安装网盘万能助手哦,安装后再刷新本页就好啦",
  264. link: "https://www.crxsoso.com/addon/detail/mphijdmblaalbakceeadippfkbgfgaaa"
  265. }
  266. },
  267. $baidu: {
  268. api: {
  269. ua: {
  270. downloadLink: "pan.baidu.com"
  271. },
  272. getAccessToken: "https://openapi.baidu.com/oauth/2.0/authorize?response_type=token&scope=basic,netdisk&client_id=IlLqBbU3GjQ0t46TRwFateTprHWl39zF&redirect_uri=oob&confirm_login=0",
  273. getLink: "https://pan.baidu.com/rest/2.0/xpan/multimedia?method=filemetas&dlink=1",
  274. getFiles: "https://pan.baidu.com/rest/2.0/xpan/file?method=list&showempty=1",
  275. getShareLink: "https://pan.baidu.com/api/sharedownload?channel=chunlei&clienttype=0&web=1&app_id=250528",
  276. getShareInfo: "https://pan.baidu.com/share/tplconfig?fields=sign,timestamp&channel=chunlei&web=1&app_id=250528&clienttype=0",
  277. getShareFiles: "https://pan.baidu.com/rest/2.0/xpan/share?method=list&showempty=1"
  278. },
  279. mount: {
  280. home: ".frame-main>div>div>div>div:has(.g-dropdown-button.g-new-create)",
  281. main: ".wp-s-agile-tool-bar__header",
  282. share: ".module-share-top-bar .x-button-box .g-dropdown-button.tools-more"
  283. }
  284. },
  285. $aliyun: {
  286. api: {
  287. getLink: "https://api.aliyundrive.com/v2/file/get_download_url",
  288. getShareLink: "https://api.aliyundrive.com/v2/file/get_share_link_download_url"
  289. },
  290. mount: {
  291. home: "[class^=\"header--\"]>[class^=\"actions--\"]",
  292. share: "[class^=\"banner--\"]>[class^=\"right--\"]",
  293. list: "[class^=\"node-list-table-view--\"]",
  294. grid: "[class^=\"node-list-grid-view--\"]",
  295. switch: "[class^=\"switch-wrapper--\"]"
  296. }
  297. },
  298. $mcloud: {
  299. api: {
  300. getLink: "https://personal-kd-njs.yun.139.com/hcy/file/getDownloadUrl"
  301. },
  302. mount: {
  303. home: ".top_button",
  304. share: ".top-btns"
  305. }
  306. },
  307. $tcloud: {
  308. api: {
  309. getAccessToken: "https://api.cloud.189.cn/open/oauth2/ssoH5.action",
  310. getLink: "https://api.cloud.189.cn/open/file/getFileDownloadUrl.action"
  311. },
  312. mount: {
  313. home: "[class*=\"FileHead_file-head-left\"]",
  314. share: ".nav-opea"
  315. }
  316. },
  317. $xunlei: {
  318. api: {
  319. mirror: [
  320. "vod0007-h05-vip-lixian.xunlei.com", "vod0008-h05-vip-lixian.xunlei.com", "vod0009-h05-vip-lixian.xunlei.com", "vod0010-h05-vip-lixian.xunlei.com", "vod0011-h05-vip-lixian.xunlei.com", "vod0012-h05-vip-lixian.xunlei.com", "vod0013-h05-vip-lixian.xunlei.com", "vod0014-h05-vip-lixian.xunlei.com", "vod0067-aliyun08-vip-lixian.xunlei.com", "vod0254-aliyun08-vip-lixian.xunlei.com", "vod0255-aliyun08-vip-lixian.xunlei.com", "vod0256-aliyun08-vip-lixian.xunlei.com", "vod0257-aliyun08-vip-lixian.xunlei.com", "vod0258-aliyun08-vip-lixian.xunlei.com", "vod0259-aliyun08-vip-lixian.xunlei.com", "vod0260-aliyun08-vip-lixian.xunlei.com", "vod0261-aliyun08-vip-lixian.xunlei.com", "vod0262-aliyun08-vip-lixian.xunlei.com", "vod0263-aliyun08-vip-lixian.xunlei.com", "vod0264-aliyun08-vip-lixian.xunlei.com", "vod0265-aliyun08-vip-lixian.xunlei.com", "vod0266-aliyun08-vip-lixian.xunlei.com", "vod0267-aliyun08-vip-lixian.xunlei.com", "vod0554-aliyun06-vip-lixian.xunlei.com", "vod0555-aliyun06-vip-lixian.xunlei.com", "vod0556-aliyun06-vip-lixian.xunlei.com", "vod0680-aliyun08-vip-lixian.xunlei.com", "vod0681-aliyun08-vip-lixian.xunlei.com", "vod0682-aliyun08-vip-lixian.xunlei.com", "vod0683-aliyun08-vip-lixian.xunlei.com", "vod0684-aliyun08-vip-lixian.xunlei.com", "vod0685-aliyun08-vip-lixian.xunlei.com", "vod0686-aliyun08-vip-lixian.xunlei.com", "vod0687-aliyun08-vip-lixian.xunlei.com", "vod0688-aliyun08-vip-lixian.xunlei.com", "vod0689-aliyun08-vip-lixian.xunlei.com", "vod0690-aliyun08-vip-lixian.xunlei.com", "vod0724-aliyun08-vip-lixian.xunlei.com", "vod0725-aliyun08-vip-lixian.xunlei.com", "vod0726-aliyun08-vip-lixian.xunlei.com", "vod0727-aliyun08-vip-lixian.xunlei.com", "vod0728-aliyun08-vip-lixian.xunlei.com", "vod0075.aliyun06.vip.lixian.xunlei.com", "vod0076.aliyun06.vip.lixian.xunlei.com", "vod0077.aliyun06.vip.lixian.xunlei.com", "vod0779-aliyun04-vip-lixian.xunlei.com", "vod0078.aliyun06.vip.lixian.xunlei.com", "vod0780-aliyun04-vip-lixian.xunlei.com", "vod0781-aliyun04-vip-lixian.xunlei.com", "vod0079.aliyun06.vip.lixian.xunlei.com", "vod0080.aliyun06.vip.lixian.xunlei.com", "vod0117.aliyun04.vip.lixian.xunlei.com", "vod0118.aliyun04.vip.lixian.xunlei.com", "vod0119.aliyun04.vip.lixian.xunlei.com", "vod1284-aliyun06-vip-lixian.xunlei.com", "vod1285-aliyun06-vip-lixian.xunlei.com", "vod1363-aliyun06-vip-lixian.xunlei.com", "vod1371-aliyun06-vip-lixian.xunlei.com", "vod1372-aliyun06-vip-lixian.xunlei.com", "vod1426-aliyun06-vip-lixian.xunlei.com", "vod1427-aliyun06-vip-lixian.xunlei.com", "vod1428-aliyun06-vip-lixian.xunlei.com", "vod1429-aliyun06-vip-lixian.xunlei.com", "vod1442-aliyun06-vip-lixian.xunlei.com", "vod1443-aliyun06-vip-lixian.xunlei.com", "vod1444-aliyun06-vip-lixian.xunlei.com", "vod1445-aliyun06-vip-lixian.xunlei.com", "vod1446-aliyun06-vip-lixian.xunlei.com", "vod1447-aliyun06-vip-lixian.xunlei.com", "vod1469-aliyun06-vip-lixian.xunlei.com", "vod1470-aliyun06-vip-lixian.xunlei.com", "vod1471-aliyun06-vip-lixian.xunlei.com", "vod1489-aliyun06-vip-lixian.xunlei.com", "vod1490-aliyun06-vip-lixian.xunlei.com", "vod1491-aliyun06-vip-lixian.xunlei.com", "vod1492-aliyun06-vip-lixian.xunlei.com", "vod1493-aliyun06-vip-lixian.xunlei.com", "vod0215.aliyun06.vip.lixian.xunlei.com", "vod0216.aliyun06.vip.lixian.xunlei.com", "vod0217.aliyun06.vip.lixian.xunlei.com", "vod0218.aliyun06.vip.lixian.xunlei.com", "vod0219.aliyun06.vip.lixian.xunlei.com", "vod0220.aliyun06.vip.lixian.xunlei.com", "vod0241.aliyun08.vip.lixian.xunlei.com", "vod0244.aliyun08.vip.lixian.xunlei.com", "vod0251.aliyun08.vip.lixian.xunlei.com", "vod0252.aliyun08.vip.lixian.xunlei.com", "vod0253.aliyun08.vip.lixian.xunlei.com", "vod0254.aliyun08.vip.lixian.xunlei.com", "vod0255.aliyun08.vip.lixian.xunlei.com", "vod0256.aliyun08.vip.lixian.xunlei.com", "vod0257.aliyun08.vip.lixian.xunlei.com", "vod0260.aliyun08.vip.lixian.xunlei.com", "vod0261.aliyun08.vip.lixian.xunlei.com", "vod0262.aliyun08.vip.lixian.xunlei.com", "vod0263.aliyun08.vip.lixian.xunlei.com", "vod0264.aliyun08.vip.lixian.xunlei.com", "vod0265.aliyun08.vip.lixian.xunlei.com", "vod0266.aliyun08.vip.lixian.xunlei.com", "vod0267.aliyun08.vip.lixian.xunlei.com", "vod3379-aliyun04-vip-lixian.xunlei.com", "vod3380-aliyun04-vip-lixian.xunlei.com", "vod3429-aliyun04-vip-lixian.xunlei.com", "vod3458-aliyun04-vip-lixian.xunlei.com", "vod3459-aliyun04-vip-lixian.xunlei.com", "vod3496-aliyun04-vip-lixian.xunlei.com", "vod3497-aliyun04-vip-lixian.xunlei.com", "vod3498-aliyun04-vip-lixian.xunlei.com", "vod3499-aliyun04-vip-lixian.xunlei.com", "vod3500-aliyun04-vip-lixian.xunlei.com", "vod3501-aliyun04-vip-lixian.xunlei.com", "vod3522-aliyun04-vip-lixian.xunlei.com", "vod3523-aliyun04-vip-lixian.xunlei.com", "vod3533-aliyun04-vip-lixian.xunlei.com", "vod3534-aliyun04-vip-lixian.xunlei.com", "vod3535-aliyun04-vip-lixian.xunlei.com", "vod3536-aliyun04-vip-lixian.xunlei.com", "vod3549-aliyun04-vip-lixian.xunlei.com", "vod3550-aliyun04-vip-lixian.xunlei.com", "vod3551-aliyun04-vip-lixian.xunlei.com", "vod3552-aliyun04-vip-lixian.xunlei.com", "vod3553-aliyun04-vip-lixian.xunlei.com", "vod3554-aliyun04-vip-lixian.xunlei.com", "vod3555-aliyun04-vip-lixian.xunlei.com", "vod0551.aliyun06.vip.lixian.xunlei.com", "vod0552.aliyun06.vip.lixian.xunlei.com", "vod0553.aliyun06.vip.lixian.xunlei.com", "vod0554.aliyun06.vip.lixian.xunlei.com", "vod0555.aliyun06.vip.lixian.xunlei.com", "vod0556.aliyun06.vip.lixian.xunlei.com", "vod0686.aliyun08.vip.lixian.xunlei.com", "vod0687.aliyun08.vip.lixian.xunlei.com", "vod0688.aliyun08.vip.lixian.xunlei.com", "vod0689.aliyun08.vip.lixian.xunlei.com", "vod0724.aliyun08.vip.lixian.xunlei.com", "vod0725.aliyun08.vip.lixian.xunlei.com", "vod0726.aliyun08.vip.lixian.xunlei.com", "vod0727.aliyun08.vip.lixian.xunlei.com", "vod0728.aliyun08.vip.lixian.xunlei.com", "vod0759.aliyun04.vip.lixian.xunlei.com", "vod0760.aliyun04.vip.lixian.xunlei.com", "vod0769.aliyun04.vip.lixian.xunlei.com", "vod0770.aliyun04.vip.lixian.xunlei.com", "vod0771.aliyun04.vip.lixian.xunlei.com", "vod0772.aliyun04.vip.lixian.xunlei.com", "vod0773.aliyun04.vip.lixian.xunlei.com", "vod0774.aliyun04.vip.lixian.xunlei.com", "vod0775.aliyun04.vip.lixian.xunlei.com", "vod0776.aliyun04.vip.lixian.xunlei.com", "vod0777.aliyun04.vip.lixian.xunlei.com", "vod0778.aliyun04.vip.lixian.xunlei.com", "vod0779.aliyun04.vip.lixian.xunlei.com", "vod0780.aliyun04.vip.lixian.xunlei.com", "vod0781.aliyun04.vip.lixian.xunlei.com", "vod3522.aliyun04.vip.lixian.xunlei.com", "vod3523.aliyun04.vip.lixian.xunlei.com", "vod3533.aliyun04.vip.lixian.xunlei.com", "vod3535.aliyun04.vip.lixian.xunlei.com", "vod3550.aliyun04.vip.lixian.xunlei.com", "vod3551.aliyun04.vip.lixian.xunlei.com", "vod3552.aliyun04.vip.lixian.xunlei.com", "vod3553.aliyun04.vip.lixian.xunlei.com", "vod3554.aliyun04.vip.lixian.xunlei.com", "vod3555.aliyun04.vip.lixian.xunlei.com"
  321. ],
  322. getLink: "https://api-pan.xunlei.com/drive/v1/files/"
  323. },
  324. mount: {
  325. home: "[class^=\"FileMenu__menu--\"]",
  326. share: "[class^=\"Share__batchActionBox--\"]"
  327. }
  328. },
  329. $quark: {
  330. api: {
  331. ua: {
  332. downloadLink: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) quark-cloud-drive/2.5.20 Chrome/100.0.4896.160 Electron/18.3.5.4-b478491100 Safari/537.36 Channel/pckk_other_ch"
  333. },
  334. getLink: "https://drive.quark.cn/1/clouddrive/file/download?pr=ucpro&fr=pc"
  335. },
  336. mount: {
  337. home: ".btn-operate .btn-main",
  338. share: ".share-btns"
  339. }
  340. },
  341. $uc: {
  342. api: {
  343. ua: {
  344. downloadLink: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) uc-cloud-drive/2.5.20 Chrome/100.0.4896.160 Electron/18.3.5.4-b478491100 Safari/537.36 Channel/pckk_other_ch"
  345. },
  346. getLink: "https://pc-api.uc.cn/1/clouddrive/file/download?pr=UCBrowser&fr=pc"
  347. },
  348. mount: {
  349. home: ".btn-operate .btn-main",
  350. share: ".file-info-share-buttom"
  351. }
  352. },
  353. $123pan: {
  354. api: {
  355. getLink: "https://www.123pan.com/api/file/download_info",
  356. getShareLink: "https://www.123pan.com/api/share/download/info"
  357. },
  358. mount: {
  359. home: "main.ant-layout-content .site-layout-background .homeClass .wenserh",
  360. share: ".conter .rightInfo .qrcode_btn"
  361. }
  362. }
  363. }
  364.  
  365. /**
  366. * 基础工具集合
  367. * @author 油小猴
  368. * @author hmjz100
  369. */
  370. let base = {
  371. /**
  372. * 生成 Aria2 下载命令
  373. * @author 油小猴
  374. * @author hmjz100
  375. * @description 将链接转换为 Aria2 格式命令,自动处理文件名特殊字符
  376. * @param {string} link - 下载链接
  377. * @param {string} filename - 文件名
  378. * @param {string} [header] - 自定义请求头参数(可选)
  379. * @returns {string} 编码后的 aria2c 命令字符串
  380. */
  381. convertLinkToAria(link, filename, header) {
  382. filename = base.fixFilename(filename);
  383. return encodeURIComponent(`aria2c "${link}" --out "${filename}"${header ? (" " + header) : ""}`);
  384. },
  385.  
  386. /**
  387. * 生成 BC 协议下载链接
  388. * @author 油小猴
  389. * @author hmjz100
  390. * @description 将链接转换为 BC 协议格式,自动处理 URL 编码
  391. * @param {string} link - 下载链接
  392. * @param {string} filename - 文件名
  393. * @param {string} [header] - 自定义请求头参数(可选)
  394. * @returns {string} 编码后的 BC 协议 URL
  395. */
  396. convertLinkToBC(link, filename, header) {
  397. let bc = `AA/${encodeURIComponent(filename)}/?url=${encodeURIComponent(link)}${header ? ("&" + header) : ""}ZZ`;
  398. return encodeURIComponent(`bc://http/${base.encodeBase(bc)}`);
  399. },
  400.  
  401. /**
  402. * 生成 cURL 下载命令
  403. * @author 油小猴
  404. * @author hmjz100
  405. * @description 根据终端类型生成对应 curl 命令,支持断点续传,自动处理文件名特殊字符
  406. * @param {string} link - 下载链接
  407. * @param {string} filename - 文件名
  408. * @param {string} [header] - 自定义请求头参数(可选)
  409. * @returns {string} 编码后的 curl 命令字符串
  410. */
  411. convertLinkToCurl(link, filename, header) {
  412. let terminal = base.getValue('setting_terminal_type');
  413. filename = base.fixFilename(filename);
  414. return encodeURIComponent(`${terminal !== 'wp' ? 'curl' : 'curl.exe'} -L -C - "${link}" -o "${filename}"${header ? (" " + header) : ""}`);
  415. },
  416.  
  417. /**
  418. * 发送链接到 RPC 下载器
  419. * @author 油小猴
  420. * @author hmjz100
  421. * @description RPC 下载必备
  422. * @param {string} link - 下载链接
  423. * @param {string} filename - 文件名
  424. * @param {string} [header] - 自定义请求头参数(可选)
  425. * @returns {Promise<'success'|'fail'>} 发送态结果
  426. */
  427. async sendLinkToRPC(link, filename, header) {
  428. let rpc = {
  429. domain: base.getValue('setting_rpc_domain'),
  430. port: base.getValue('setting_rpc_port'),
  431. path: base.getValue('setting_rpc_path'),
  432. token: base.getValue('setting_rpc_token'),
  433. dir: base.getValue('setting_rpc_dir'),
  434. };
  435.  
  436. let url = `${rpc.domain}:${rpc.port}${rpc.path}`;
  437. let rpcData = {
  438. id: new Date().getTime(),
  439. jsonrpc: '2.0',
  440. method: 'aria2.addUri',
  441. params: [`token:${rpc.token}`, [link], {
  442. dir: rpc.dir,
  443. out: filename,
  444. header
  445. }]
  446. };
  447. try {
  448. let res = await base.post(url, rpcData, {}, '');
  449. if (res.result) return 'success';
  450. return 'fail';
  451. } catch (e) {
  452. return 'fail';
  453. }
  454. },
  455.  
  456. /**
  457. * 注册 GreaseMonkey-Compatible-Manager 扩展菜单命令
  458. * @author 油小猴
  459. * @author hmjz100
  460. * @description 包含 "设置"、"美化"、"更新" 和 "调试" 四个功能入口
  461. */
  462. registerMenuCommand() {
  463. GM_registerMenuCommand('⚙️ 设置', function () {
  464. base.showSetting();
  465. });
  466. GM_registerMenuCommand('🍃️ 美化', function () {
  467. base.showBeautify();
  468. });
  469. GM_registerMenuCommand('📃 更新', function () {
  470. base.showUpdate();
  471. });
  472. GM_registerMenuCommand('🛠️ 调试', function () {
  473. base.showDebug();
  474. });
  475. },
  476.  
  477. /**
  478. * 取消初始化配置并重置相关存储数据
  479. * @author hmjz100
  480. * @param {number} [value=111111] - 要设置的错误 `暗号`
  481. */
  482. unRegisterInit(value = 111111) {
  483. message.warning("正在“注入”设置项目...");
  484. base.setValue('setting_init_code', value);
  485. base.setValue('setting_init_license', value);
  486. base.setValue('baidu_access_token', "");
  487. if (location.host.includes("baidu")) base.setStorage('baiduyunPlugin_BDUSS', "")
  488. history.go(0)
  489. },
  490.  
  491. /**
  492. * 判断 JavaScript 对象类型
  493. * @author 油小猴
  494. * @description 通过 Object.prototype.toString 精确识别对象类型
  495. * @param {*} obj - 待检测对象
  496. * @returns {string} 类型名称(全小写),如:array/number/null/date 等
  497. * @example
  498. * isType([]) // => "array"
  499. * isType(null) // => "null"
  500. */
  501. isType(obj) {
  502. return Object.prototype.toString.call(obj).replace(/^\[object (.+)\]$/, '$1').toLowerCase();
  503. },
  504.  
  505. /**
  506. * 获取 GreaseMonkey-Compatible-Manager 存储的值
  507. * @author 油小猴
  508. * @param {string} name - 存储键名
  509. * @returns {*} 存储的值
  510. */
  511. getValue(name) {
  512. return GM_getValue(name);
  513. },
  514.  
  515. /**
  516. * 设置 GreaseMonkey-Compatible-Manager 存储的值
  517. * @author 油小猴
  518. * @param {string} name - 存储键名
  519. * @param {*} value - 要存储的值
  520. */
  521. setValue(name, value) {
  522. GM_setValue(name, value);
  523. },
  524.  
  525. /**
  526. * 删除 GreaseMonkey-Compatible-Manager 存储的值
  527. * @author 油小猴
  528. * @param {string} name - 要删除的存储键名
  529. */
  530. deleteValue(name) {
  531. GM_deleteValue(name);
  532. },
  533.  
  534. /**
  535. * 从 localStorage 获取存储值
  536. * @description 自动解析 JSON 格式内容
  537. * @author 油小猴
  538. * @param {string} key - 存储键名
  539. * @returns {*} 存储的原始值或解析后的对象
  540. */
  541. getStorage(key) {
  542. try {
  543. return JSON.parse(localStorage.getItem(key));
  544. } catch (e) {
  545. return localStorage.getItem(key);
  546. }
  547. },
  548.  
  549. /**
  550. * 设置 localStorage 存储值
  551. * @author 油小猴
  552. * @description 自动 `JSON.stringify` `对象` `数组` 类型的数据
  553. * @param {string} key - 存储键名
  554. * @param {*} value - 要存储的值
  555. */
  556. setStorage(key, value) {
  557. if (this.isType(value) === 'object' || this.isType(value) === 'array') {
  558. return localStorage.setItem(key, JSON.stringify(value));
  559. }
  560. return localStorage.setItem(key, value);
  561. },
  562.  
  563. /**
  564. * 剪贴板写入
  565. * @author 油小猴
  566. * @param {string} text - 要复制的文本内容
  567. */
  568. setClipboard(text) {
  569. GM_setClipboard(text, 'text');
  570. },
  571.  
  572. /**
  573. * Base64-URI 编码处理
  574. * @author 油小猴
  575. * @author hmjz100
  576. * @description 自动执行 URI 兼容性编码转换
  577. * @param {string} str - 待编码的字符串
  578. * @returns {string} Base64 编码结果字符串
  579. */
  580. encodeBase(str) {
  581. try { str = btoa(str) } catch { }
  582. return str;
  583. },
  584.  
  585. /**
  586. * Base64-URI 解码处理
  587. * @author 油小猴
  588. * @author hmjz100
  589. * @description 自动执行 URI 兼容性解码转换
  590. * @param {string} str - Base64 编码字符串
  591. * @returns {string} 解码后的原始字符串
  592. */
  593. decodeBase(str) {
  594. try { str = decodeURIComponent(str) } catch { }
  595. try { str = atob(str) } catch { }
  596. try { str = decodeURIComponent(str) } catch { }
  597. return str;
  598. },
  599.  
  600. /**
  601. * 数字补零格式化
  602. * @author hmjz100
  603. * @description 对 1-9 的数字自动补前导零
  604. * @param {number} i - 待格式化的数字
  605. * @returns {string} 格式化后的字符串(如"05")
  606. */
  607. timeFormat(i) {
  608. if (i >= 0 && i <= 9) {
  609. return "0" + i;
  610. } else {
  611. return i;
  612. }
  613. },
  614.  
  615. /**
  616. * 获取文件扩展名并转为大写
  617. * @author 油小猴
  618. * @param {string} name - 完整文件名
  619. * @returns {string} 大写的文件扩展名(如 `TXT`)
  620. */
  621. getExtension(name) {
  622. const reg = /(?!\.)\w+$/;
  623. if (reg.test(name)) {
  624. let match = name.match(reg);
  625. return match[0].toUpperCase();
  626. }
  627. return '';
  628. },
  629.  
  630. /**
  631. * 文件大小格式化
  632. * @author hmjz100
  633. * @description 自动转换单位到最合适的存储单位(如 `1.2MB`)
  634. * @param {number} value - 文件字节大小
  635. * @returns {string} 可读格式的大小描述
  636. */
  637. sizeFormat(value) {
  638. if (value === +value) {
  639. let unit = ["字节(B)", "千字节(KB)", "兆字节(MB)", "吉字节(GB)", "太字节(TB)", "拍字节(PB)", "艾字节(EB)", "泽字节(ZB)", "尧字节(YB)"];
  640. if (value === 0) {
  641. return "0字节(B)";
  642. } else {
  643. let index = Math.floor(Math.log(value) / Math.log(1024));
  644. let size = value / Math.pow(1024, index);
  645. size = size.toFixed(1);
  646. return size + unit[index];
  647. }
  648. }
  649. return '';
  650. },
  651.  
  652. /**
  653. * 将剩余时间(秒)格式化为可读的时间字符串
  654. *
  655. * @param {number} remainingTimeSeconds 剩余总秒数(支持小数)
  656. * @returns {string} 格式化后的时间字符串,包含以下可能格式:
  657. * - "X天 HH时:MM分:SS秒"(超过1天)
  658. * - "HH时:MM分:SS秒"(超过1小时)
  659. * - "MM分:SS秒"(超过1分钟)
  660. * - "SS秒"(1分钟内)
  661. * - "即将完成"(0秒时)
  662. * - "计算中..."(无效输入时)
  663. *
  664. * @example
  665. * formatRemainingTime(86400) // "1天 00时:00分:00秒"
  666. * formatRemainingTime(3661.5) // "01时:01分:01秒"
  667. * formatRemainingTime(0) // "即将完成"
  668. * formatRemainingTime(-5) // "计算中..."
  669. * formatRemainingTime(NaN) // "计算中..."
  670. */
  671. rtimeFormat(remainingTimeSeconds) {
  672. if (!Number.isFinite(remainingTimeSeconds) || remainingTimeSeconds < 0) {
  673. return '计算中...';
  674. }
  675.  
  676. let remainingDays = Math.floor(remainingTimeSeconds / (60 * 60 * 24));
  677. remainingTimeSeconds %= (60 * 60 * 24);
  678.  
  679. let remainingHours = Math.floor(remainingTimeSeconds / (60 * 60));
  680. remainingTimeSeconds %= (60 * 60);
  681.  
  682. let remainingMinutes = Math.floor(remainingTimeSeconds / 60);
  683. let remainingSeconds = Math.floor(remainingTimeSeconds % 60);
  684.  
  685. if (remainingDays > 0) {
  686. return `${remainingDays}天 ${base.timeFormat(remainingHours)}时:${base.timeFormat(remainingMinutes)}分:${base.timeFormat(remainingSeconds)}秒`;
  687. } else if (remainingHours > 0) {
  688. return `${base.timeFormat(remainingHours)}时:${base.timeFormat(remainingMinutes)}分:${base.timeFormat(remainingSeconds)}秒`;
  689. } else if (remainingMinutes > 0) {
  690. return `${base.timeFormat(remainingMinutes)}分:${base.timeFormat(remainingSeconds)}秒`;
  691. } else if (remainingSeconds > 0) {
  692. return `${remainingSeconds}秒`;
  693. } else {
  694. return '即将完成';
  695. }
  696. },
  697.  
  698. /**
  699. * 文件列表排序
  700. * @author 油小猴
  701. * @description 按中文拼音顺序对文件数组进行排序
  702. * @param {Array} arr - 包含文件对象的数组
  703. * @param {string} arr[].filename - 文件名属性(兼容 server_filename)
  704. */
  705. sortByName(arr) {
  706. const handle = function () {
  707. return (a, b) => {
  708. const p1 = a.filename ? a.filename : a.server_filename;
  709. const p2 = b.filename ? b.filename : b.server_filename;
  710. return p1.localeCompare(p2, "zh-CN");
  711. };
  712. };
  713. arr.sort(handle());
  714. },
  715.  
  716. /**
  717. * 文件名安全处理
  718. * @author 油小猴
  719. * @description 替换非法字符为下划线
  720. * @param {string} name - 原始文件名
  721. * @returns {string} 修正后的安全文件名
  722. */
  723. fixFilename(name) {
  724. let replace = /[!?&|`"'*\/:<>\\]/g
  725. return name.replace(replace, '_');
  726. },
  727.  
  728. /**
  729. * 获取 Cookie 值
  730. * @author 油小猴
  731. * @param {string} name - Cookie名称
  732. * @returns {string} 对应的 Cookie 值
  733. */
  734. getCookie(name) {
  735. let cname = name + "=";
  736. let ca = document.cookie.split(';');
  737. for (let i = 0; i < ca.length; i++) {
  738. let c = ca[i].trim();
  739. if (c.indexOf(cname) == 0) return c.substring(cname.length, c.length);
  740. }
  741. return "";
  742. },
  743.  
  744. /**
  745. * Blob 文件下载
  746. * @author 油小猴
  747. * @description 通过创建临时链接实现文件下载
  748. * @param {Blob} blob - 要下载的 Blob 对象
  749. * @param {string} filename - 下载时提示保存的文件名
  750. */
  751. blobDownload(blob, filename) {
  752. if (blob instanceof Blob) {
  753. const url = URL.createObjectURL(blob);
  754. const a = document.createElement('a');
  755. a.href = url;
  756. a.download = filename;
  757. a.click();
  758. URL.revokeObjectURL(url);
  759. }
  760. },
  761.  
  762. /**
  763. * 可跨域 xmlhttpRequest 请求
  764. * @author hmjz100
  765. * @description 封装 `GreaseMonkey-Compatible_xmlhttpRequest` 实现的跨域请求,与原始函数参数相同
  766. * @param {Object} option - 请求配置对象
  767. * @returns {XMLHttpRequest} 请求对象实例
  768. */
  769. xmlHttpRequest(option) {
  770. let request = (typeof GM_xmlhttpRequest !== "undefined") ? GM_xmlhttpRequest : GM.xmlHttpRequest;
  771. if (request && typeof request === 'function') {
  772. return request(option);
  773. }
  774. },
  775.  
  776. /**
  777. * 发送 POST 请求
  778. * @author 油小猴
  779. * @author hmjz100
  780. * @description 支持自动重试和数据格式化,内置错误处理,请求数据自动 `JSON.stringify`
  781. * @param {string} url - 请求地址
  782. * @param {Object|string} data - 请求数据
  783. * @param {Object} headers - 请求头配置
  784. * @param {string} [type='json'] - 响应类型(支持 `json`, `blob` 等)
  785. * @param {number} [maxRetries=3] - 最大重试次数
  786. * @param {number} [currentRetry=0] - 已重试次数(内部使用)
  787. * @returns {Promise} 包含响应数据的 `Promise` 对象
  788. */
  789. post(url, data, headers, type, maxRetries = 3, currentRetry = 0) {
  790. if (this.isType(data) === 'object') {
  791. data = JSON.stringify(data);
  792. }
  793. return new Promise((resolve, reject) => {
  794. const sendRequest = function () {
  795. base.xmlHttpRequest({
  796. method: "POST", url, headers, data,
  797. responseType: type || 'json',
  798. onloadstart() {
  799. console.log('【LinkSwift】Post(start)\n请求地址:' + url + '\n请求头部:', headers);
  800. },
  801. onload: function (res) {
  802. // 尝试格式化请求结果以方便调试
  803. if (res.response) {
  804. try {
  805. res.decodedResponse = JSON.parse(res.response);
  806. } catch (e) { }
  807. try {
  808. res.decodedResponse = JSON.parse(base.decodeBase(res.response));
  809. } catch (e) { }
  810. }
  811. if (res.responseText) {
  812. try {
  813. res.decodedResponseText = JSON.stringify(JSON.parse(res.responseText));
  814. } catch (e) { }
  815. try {
  816. res.decodedResponseText = JSON.stringify(base.decodeBase(res.responseText));
  817. } catch (e) { }
  818. }
  819. console.log('【LinkSwift】Post(load)\n请求地址:' + url + '\n请求头部:', headers, '\n请求数据:' + JSON.stringify(data) + '\n请求结果:', res);
  820. type === 'blob' ? resolve(res) : resolve(res.response || res.responseText);
  821. },
  822. onerror: function (err) {
  823. if (currentRetry < maxRetries) {
  824. currentRetry++;
  825. console.error(`【LinkSwiftPost(error)\n请求出现错误,可能是网络问题\n5秒后将重试 (错误次数:${currentRetry}/${maxRetries})...`, err);
  826. setTimeout(function () {
  827. console.log(`【LinkSwiftPost(error)\n重新尝试请求...`);
  828. sendRequest(); // 重新发送请求
  829. }, 5000)
  830. } else {
  831. reject('【LinkSwift】Post(error)\n请求出现错误,可能是网络问题\n无法继续请求,达到最大错误次数。', err); // 达到最大重试次数,拒绝 Promise
  832. }
  833. },
  834. });
  835. };
  836. sendRequest(); // 初始请求
  837. });
  838. },
  839.  
  840. /**
  841. * 发送 GET 请求
  842. * @author 油小猴
  843. * @author hmjz100
  844. * @description 支持进度监控、文件下载和自动重试,可处理被下载工具捕获特殊逻辑
  845. * @param {string} url - 请求地址
  846. * @param {Object} headers - 请求头配置
  847. * @param {string} [type='json'] - 响应类型
  848. * @param {Object} [extra] - 附加参数(需包含 `filename` 和 `index` 属性)
  849. * @param {number} [maxRetries=3] - 最大重试次数
  850. * @param {number} [currentRetry=0] - 已重试次数(内部使用)
  851. * @returns {Promise} 包含响应数据的 `Promise` 对象
  852. */
  853. get(url, headers, type, extra, maxRetries = 3, currentRetry = 0) {
  854. return new Promise((resolve, reject) => {
  855. const sendRequest = function () {
  856. let requestObj = base.xmlHttpRequest({
  857. method: "GET", url, headers,
  858. responseType: type || 'json',
  859. onload: function (res) {
  860. if (res.status === 204) {
  861. console.log('【LinkSwift】Get(load)\n\x1B[31m该请求已被某个下载工具捕获。' + (res.statusText ? ("\n\x1B[0m工具提示:\x1B[31m" + res.statusText) : "") + '\x1B[0m\n请求地址:' + url + '\n请求头部:', headers, '\n请求结果:', res);
  862. requestObj.abort();
  863. idm[extra.index] = true;
  864. return;
  865. }
  866. if (type === 'blob') {
  867. console.log('【LinkSwift】Get(load) Blob\n请求地址:' + url + '\n请求头部:', headers, '\n请求结果:', res);
  868. res.status === 200 && base.blobDownload(res.response, extra.filename);
  869. resolve(res);
  870. } else {
  871. // 尝试格式化请求结果以方便调试
  872. if (res.response) {
  873. try {
  874. res.decodedResponse = JSON.parse(res.response);
  875. } catch (e) { }
  876. try {
  877. res.decodedResponse = JSON.parse(base.decodeBase(res.response));
  878. } catch (e) { }
  879. }
  880. if (res.responseText) {
  881. try {
  882. res.decodedResponseText = JSON.stringify(JSON.parse(res.responseText));
  883. } catch (e) { }
  884. try {
  885. res.decodedResponseText = JSON.stringify(base.decodeBase(res.responseText));
  886. } catch (e) { }
  887. }
  888. console.log('【LinkSwift】Get(load)\n请求地址:' + url + '\n请求头部:', headers, '\n请求结果:', res);
  889. resolve(res.response || res.responseText);
  890. }
  891. },
  892. onprogress: function (res) {
  893. if (res.status === 204) {
  894. console.log('【LinkSwift】Get(progress)\n\x1B[31m该请求已被某个下载工具捕获。' + (res.statusText ? ("\n\x1B[0m工具提示:\x1B[31m" + res.statusText) : "") + '\x1B[0m\n请求地址:' + url + '\n请求头部:', headers, '\n请求结果:', res);
  895. requestObj.abort();
  896. idm[extra.index] = true;
  897. return;
  898. }
  899. if (extra && extra.filename && extra.index) {
  900. res.total > 0 ? progress[extra.index] = (res.loaded * 100 / res.total).toFixed(2) : progress[extra.index] = 0.00;
  901. }
  902. },
  903. onloadstart(res) {
  904. if (res.status === 204) {
  905. console.log('【LinkSwift】Get(start)\n\x1B[31m该请求已被某个下载工具捕获。' + (res.statusText ? ("\n\x1B[0m工具提示:\x1B[31m" + res.statusText) : "") + '\x1B[0m\n请求地址:' + url + '\n请求头部:', headers, '\n请求结果:', res);
  906. requestObj.abort();
  907. idm[extra.index] = true;
  908. return;
  909. }
  910. console.log('【LinkSwift】Get(start)\n请求地址:' + url + '\n请求头部:', headers);
  911. if (extra && extra.filename && extra.index) request[extra.index] = requestObj;
  912. },
  913. onerror: function (err) {
  914. if (currentRetry < maxRetries) {
  915. currentRetry++;
  916. console.error(`【LinkSwiftGet(error)\n请求出现错误,可能是网络问题\n5秒后将重试 (错误次数:${currentRetry}/${maxRetries})...`, err);
  917. setTimeout(function () {
  918. console.log(`【LinkSwiftGet(error)\n重新尝试请求...`);
  919. sendRequest(); // 重新发送请求
  920. }, 5000)
  921. } else {
  922. reject('【LinkSwift】Get(error)\n请求出现错误,可能是网络问题\n无法继续请求,达到最大错误次数。', err); // 达到最大重试次数,拒绝 Promise
  923. }
  924. },
  925. });
  926. };
  927.  
  928. sendRequest(); // 初始请求
  929. });
  930. },
  931.  
  932. /**
  933. * 获取最终重定向 URL(手动处理 30x 跳转)
  934. * @author 油小猴
  935. * @author hmjz100
  936. * @description 手动追踪 HTTP 30x 重定向,返回最终访问地址
  937. * @param {string} url - 初始请求地址
  938. * @param {Object} headers - 请求头配置
  939. * @param {number} [maxRetries=3] - 最大重试次数
  940. * @param {number} [currentRetry=0] - 已重试次数(内部使用)
  941. * @returns {Promise<string>} 最终 URL 地址
  942. */
  943. getFinalUrl(url, headers, maxRetries = 3, currentRetry = 0) {
  944. return new Promise((resolve, reject) => {
  945. // 防止无限递归跳转
  946. if (currentRetry >= maxRetries) {
  947. return reject(new Error(`【LinkSwiftHead(start) FinalUrl\n达到最大重试次数 ${maxRetries},停止跳转。`));
  948. }
  949.  
  950. base.xmlHttpRequest({
  951. method: "HEAD",
  952. url: url,
  953. headers: headers,
  954. onloadstart: () => {
  955. console.log('【LinkSwift】Head(start) FinalUrl\n请求地址:' + url + '\n请求头部:', headers);
  956. },
  957. onload: function (res) {
  958. console.log('【LinkSwift】Head(load) FinalUrl\n请求地址:' + res.finalUrl + '\n响应状态:' + res.status);
  959.  
  960. let status = res.status;
  961. if (status >= 300 && status < 400) {
  962. base.getFinalUrl(res.finalUrl, headers, maxRetries, currentRetry + 1).then(resolve).catch(reject);
  963. } else {
  964. resolve(res.finalUrl);
  965. }
  966. },
  967. onerror: function (err) {
  968. if (currentRetry < maxRetries) {
  969. currentRetry++;
  970. console.error(`【LinkSwiftHead(error) FinalUrl\n请求出现错误,可能是网络问题\n5秒后将重试 (错误次数:${currentRetry}/${maxRetries})...`);
  971. setTimeout(() => {
  972. console.log(`【LinkSwiftHead(error) FinalUrl\n重新尝试请求...`);
  973. base.getFinalUrl(url, headers, maxRetries, currentRetry)
  974. .then(resolve)
  975. .catch(reject);
  976. }, 5000);
  977. } else {
  978. reject('【LinkSwift】Get(error) FinalUrl\n请求出现错误,可能是网络问题\n无法继续请求,达到最大错误次数。', err);
  979. }
  980. }
  981. });
  982. });
  983. },
  984.  
  985. /**
  986. * RPC服务测试
  987. * @author hmjz100
  988. * @description 验证 `JSON-RPC` 接口可用性
  989. * @param {string} domain - 服务域名
  990. * @param {string} port - 服务端口
  991. * @param {string} path - RPC 路径
  992. * @param {string} token - 认证令牌
  993. * @returns {Promise<'success'|'fail'>} 连接状态结果
  994. */
  995. async rpcTest(domain, port, path, token) {
  996. return new Promise((resolve, reject) => {
  997. let rpc = { domain, port, path, token };
  998. let url = `${rpc.domain}:${rpc.port}${rpc.path}`;
  999. let rpcData = {
  1000. id: new Date().getTime(),
  1001. jsonrpc: '2.0',
  1002. method: 'system.listMethods',
  1003. params: [`token:${rpc.token}`],
  1004. };
  1005. base.xmlHttpRequest({
  1006. method: "POST", url, headers: {}, data: JSON.stringify(rpcData),
  1007. responseType: 'json',
  1008. onloadstart() {
  1009. console.log('【LinkSwift】Post(start) RPCTest\n请求地址:' + url + '\n请求内容:', rpcData);
  1010. },
  1011. onload: function (res) {
  1012. console.log('【LinkSwift】Post(load) RPCTest\n请求地址:' + url + '\n请求结果:', res);
  1013. if (res.response) {
  1014. resolve("success");
  1015. } else {
  1016. resolve("fail");
  1017. }
  1018. },
  1019. onerror: function (err) {
  1020. console.error('【LinkSwift】Post(error) RPCTest\n请求失败', err);
  1021. resolve("fail");
  1022. },
  1023. });
  1024. });
  1025. },
  1026.  
  1027. /**
  1028. * 重置请求相关数据
  1029. * @author 油小猴
  1030. * @description 中止所有进行中的请求,清除进度记录和定时器
  1031. */
  1032. _resetAllData() {
  1033. progress = {};
  1034. $.each(request, function (key) {
  1035. (request[key]).abort();
  1036. });
  1037. $.each(ins, function (key) {
  1038. clearInterval(ins[key]);
  1039. });
  1040. idm = {};
  1041. ins = {};
  1042. request = {};
  1043. },
  1044.  
  1045. /**
  1046. * 重置请求相关数据
  1047. * @author 油小猴
  1048. * @description 中止指定的进行中的请求,清除进度记录和定时器
  1049. */
  1050. _resetData(i) {
  1051. ins[i] && clearInterval(ins[i]);
  1052. request[i] && request[i].abort();
  1053. progress[i] = 0;
  1054. idm[i] = false;
  1055. },
  1056.  
  1057. /**
  1058. * 将对象转换为 URL 编码字符串
  1059. * @author 油小猴
  1060. * @description 递归处理嵌套数组,自动进行 URI 编码
  1061. * @param {Object} obj - 待转换的键值对对象
  1062. * @returns {string} URL 编码格式字符串(如`key1=value1&key2=value2`)
  1063. */
  1064. stringify(obj) {
  1065. let str = '';
  1066. for (let key in obj) {
  1067. if (obj.hasOwnProperty(key)) {
  1068. let value = obj[key];
  1069. if (Array.isArray(value)) {
  1070. for (let i = 0; i < value.length; i++) {
  1071. str += encodeURIComponent(key) + '=' + encodeURIComponent(value[i]) + '&';
  1072. }
  1073. } else {
  1074. str += encodeURIComponent(key) + '=' + encodeURIComponent(value) + '&';
  1075. }
  1076. }
  1077. }
  1078. return str.slice(0, -1); // 去掉末尾的 "&"
  1079. },
  1080.  
  1081. /**
  1082. * 动态注入样式表
  1083. * @author 油小猴
  1084. * @author hmjz100
  1085. * @description 支持 `样式标签` `外链CSS` 注入,提供精准的 DOM 定位和插入位置控制
  1086. * @param {string} id - 样式元素 ID
  1087. * @param {'style'|'link'} tag - 标签类型(`style` 或 `link`)
  1088. * @param {string} css - CSS 内容或外链 URL
  1089. * @param {string} [element='.{mount}'] - 定位基准元素选择器
  1090. * @param {'before'|'after'|'prepend'|'append'} [position='append'] - 插入位置
  1091. */
  1092. addStyle(id, tag = 'style', css, element = `.${mount}`, position = "append") {
  1093. base.waitForKeyElements(element, (element) => {
  1094. let $styleDom = $(`#${id}`);
  1095. let $style = $(`<${tag}>`, {
  1096. rel: 'stylesheet',
  1097. id: id
  1098. });
  1099. tag === 'style' ? $style.html(css) : $style.attr('href', css);
  1100. if ($styleDom.length) {
  1101. replacedElements.add(id);
  1102. $styleDom.replaceWith($style);
  1103. return true;
  1104. }
  1105.  
  1106. if (position === "before") {
  1107. element.before($style);
  1108. } else if (position === "after") {
  1109. element.after($style);
  1110. } else if (position === "prepend") {
  1111. element.prepend($style);
  1112. } else {
  1113. element.append($style);
  1114. }
  1115. return true;
  1116. }, true);
  1117. },
  1118.  
  1119. /**
  1120. * 十六进制颜色转 RGBA
  1121. * @author hmjz100
  1122. * @description 支持 4 位和 8 位十六进制格式,自动解析透明度通道
  1123. * @param {string} hex - 十六进制颜色值(如 `#09f` 或 `#0099ffaa` )
  1124. * @returns {string} RGBA 格式字符串(如 `15, 170, 255, 0.67`)
  1125. */
  1126. hexToRgba(hex) {
  1127. // 去掉 # 号
  1128. hex = hex.replace(/^#/, '');
  1129. // 如果是四位十六进制颜色值,转换为八位
  1130. if (hex.length === 4) {
  1131. hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2] + hex[3] + hex[3];
  1132. }
  1133. // 解析 RGB 分量
  1134. let r = parseInt(hex.substring(0, 2), 16);
  1135. let g = parseInt(hex.substring(2, 4), 16);
  1136. let b = parseInt(hex.substring(4, 6), 16);
  1137. let a = '';
  1138. // 如果是八位十六进制颜色值,解析 alpha 通道
  1139. if (hex.length === 8) {
  1140. a = parseInt(hex.substring(6, 8), 16) / 255; // 将 alpha 值转换为 0 到 1 之间的小数
  1141. a = ',' + a
  1142. }
  1143. // 返回 rgba 格式字符串
  1144. return r + ', ' + g + ', ' + b + a;
  1145. },
  1146.  
  1147. /**
  1148. * RGBA 颜色转十六进制
  1149. * @author hmjz100
  1150. * @description 支持透明度转换,自动补全缩写格式
  1151. * @param {string} rgba - RGBA 格式颜色值(如 `rgba(15,170,255,0.67)`)
  1152. * @returns {string} 十六进制颜色值(如 `#09aaffaa`)
  1153. */
  1154. rgbaToHex(rgba) {
  1155. // 去掉前缀 "rgba" 或 "rgb" 并移除空格
  1156. rgba = rgba.replace(/^(rgba?|RGBA?)\(/, '').replace(/\s+/g, '').replace(')', '');
  1157.  
  1158. // 将颜色值分割为数组
  1159. let [r, g, b, a] = rgba.split(',');
  1160.  
  1161. // 将 RGB 转换为十六进制
  1162. r = parseInt(r).toString(16).padStart(2, '0');
  1163. g = parseInt(g).toString(16).padStart(2, '0');
  1164. b = parseInt(b).toString(16).padStart(2, '0');
  1165.  
  1166. // 如果存在 alpha 通道,处理透明度值
  1167. if (a !== undefined) {
  1168. // 将 alpha 转换为 0 到 255 的十六进制
  1169. a = Math.round(parseFloat(a) * 255).toString(16).padStart(2, '0');
  1170. return `#${r}${g}${b}${a}`;
  1171. }
  1172.  
  1173. // 如果没有 alpha 通道,返回标准六位的十六进制颜色
  1174. return `#${r}${g}${b}`;
  1175. },
  1176.  
  1177. /**
  1178. * 批量替换 CSS 颜色值
  1179. * @author hmjz100
  1180. * @description 支持全局样式替换和资源路径修正,处理颜色渐变过渡效果
  1181. * @param {string} cssText - 原始 CSS 内容
  1182. * @param {string} baseURI - 资源基础路径
  1183. * @param {'default'|'other'} type - 替换模式(默认模式包含过渡效果)
  1184. * @param {Array<Array<string>>} colorMap - 颜色映射表(旧颜色 → 新颜色)
  1185. * @returns {string} 处理后的 CSS 内容
  1186. */
  1187. replaceColors(cssText, baseURI, type, colorMap) {
  1188. if (!cssText) return '';
  1189. let colorList = ['#09AAFF', '#cc3235', '#518c17', '#ed944b', '#f969a5', '#bca280', '#574AB8', '#b673ab', '#1d2327', '#18a497', '#637dff', '#0d53ff', '#3181f9', '#f8d800', '#0396ff', '#32ccbc', '#f6416c', '#2271b1', '#59524c', '#ff679a', '#f44236', '#fec107', '#8bc24a', '#2594ed', '#9c28b1']
  1190. colorList.forEach(function (oldColor) {
  1191. cssText = cssText.replace(new RegExp(base.hexToRgba(oldColor), 'ig'), base.hexToRgba(color));
  1192. cssText = cssText.replace(new RegExp(oldColor, 'ig'), color);
  1193. });
  1194. if (type === 'other') {
  1195. // 遍历颜色映射数组,将旧颜色替换为新颜色,并添加过渡效果
  1196. colorMap.forEach(function (colorPair) {
  1197. let oldColor = colorPair[0];
  1198. let newColor = colorPair[1];
  1199. // 判断新颜色是否为 color
  1200. cssText = cssText.replace(new RegExp(oldColor, 'ig'), newColor);
  1201. });
  1202. return cssText;
  1203. }
  1204. if (colorMap) {
  1205. // 遍历颜色映射数组,将旧颜色替换为新颜色,并添加过渡效果
  1206. colorMap.forEach(function (colorPair) {
  1207. let oldColor = colorPair[0];
  1208. let newColor = colorPair[1];
  1209. // 判断新颜色是否为 color
  1210. if (oldColor.includes("#")) {
  1211. cssText = cssText.replace(new RegExp(oldColor + '(.*?)}', 'ig'), newColor + '$1; ' + 'transition: all 0.1s ease;}');
  1212. cssText = cssText.replace(new RegExp(oldColor, 'ig'), newColor);
  1213. } else {
  1214. cssText = cssText.replace(new RegExp(oldColor, 'ig'), newColor);
  1215. }
  1216. });
  1217. };
  1218. if (baseURI) {
  1219. // 替换相对路径资源为绝对路径
  1220. cssText = cssText.replace(/url\(\s*(['"]?)([^'"]*?)\1\s*\)/ig, (match, quote, url) => {
  1221. // 如果 URL 是相对路径,则将其转换为绝对路径
  1222. if (url && !/^(data:|https?:|\/\/)/i.test(url)) {
  1223. try {
  1224. let absoluteURL = new URL(url, baseURI).href;
  1225. return `url(${absoluteURL})`;
  1226. } catch (e) {
  1227. return match;
  1228. }
  1229. }
  1230. return match;
  1231. });
  1232. }
  1233. return cssText;
  1234. },
  1235.  
  1236. /**
  1237. * 全局主题颜色设置
  1238. * @author hmjz100
  1239. * @description 自动遍历并替换 `页面所有样式表` `SVG 元素` 的颜色值
  1240. * @param {Array<Array<string>>} colorMap - 颜色映射表
  1241. * @param {'default'|'other'} type - 替换模式
  1242. */
  1243. setColors(colorMap, type) {
  1244. base.waitForKeyElements(`[id^="${mount}-ColorUI-"]`, function (tag) {
  1245. if (tag.html() === base.replaceColors(tag.text(), '', type, colorMap)) return;
  1246. let cssText = base.replaceColors(tag.text(), '', type, colorMap);
  1247. base.addStyle(tag.attr("id"), 'style', cssText, tag[0]);
  1248. return true;
  1249. }, true)
  1250. let count = 0;
  1251. if (!colored) {
  1252. base.waitForKeyElements('link[rel="stylesheet"]', function (tag) {
  1253. if (!tag.parent().length || !tag.attr('href')) return;
  1254. let href = tag.attr('href');
  1255. try {
  1256. href = new URL(href, location.href).href;
  1257. } catch (e) {
  1258. return;
  1259. }
  1260. fetch(href)
  1261. .then(response => response.text())
  1262. .then(responseText => {
  1263. let id = `${mount}-ColorUI-` + href.replace(/[^\w]/g, "_");
  1264. let cssText = base.replaceColors(responseText, href, type, colorMap);
  1265. if (responseText === base.replaceColors(responseText, '', type, colorMap)) return;
  1266. base.addStyle(id, 'style', cssText, tag[0], "after");
  1267. console.log(`【LinkSwiftUI\n覆盖原始 <link> 样式\n原始:`, tag[0]);
  1268. })
  1269. }, true);
  1270. base.waitForKeyElements(`style:not([id^="${mount}-"],[id^="swal-pub"],[class^="darkreader"])`, function (tag) {
  1271. let id = tag.attr("id");
  1272. let text = tag.html()
  1273. if (tag.data("styles") === text) return;
  1274. tag.data("styles", text);
  1275. // 替换颜色并添加样式
  1276. let cssText = base.replaceColors(text, '', type, colorMap);
  1277. if (text === cssText) return;
  1278. id = id ? id : `${mount}-ColorUI-${count++}`
  1279. base.addStyle(id, 'style', cssText, tag[0], "after");
  1280. console.log(`【LinkSwiftUI\n覆盖原始 <style> 样式\n原始:`, tag[0]);
  1281. }, true)
  1282. base.waitForKeyElements('svg', function (element) {
  1283. element.find('*').each(function () {
  1284. let fill = $(this).attr('fill');
  1285. let stroke = $(this).attr('stroke');
  1286. if (fill) {
  1287. let newFill = base.replaceColors(fill, '', type, colorMap);
  1288. if (newFill !== fill) {
  1289. $(this).attr('fill', newFill);
  1290. console.log(`【LinkSwiftUI\n覆盖 <svg> fill 样式\n原始:`, $(this)[0]);
  1291. }
  1292. }
  1293. if (stroke) {
  1294. let newStroke = base.replaceColors(stroke, '', type, colorMap);
  1295. if (newStroke !== stroke) {
  1296. $(this).attr('stroke', newStroke);
  1297. console.log(`【LinkSwiftUI\n覆盖 <svg> stroke 样式\n原始:`, $(this)[0]);
  1298. }
  1299. }
  1300. });
  1301. }, true);
  1302. colored = true;
  1303. }
  1304. },
  1305.  
  1306. /**
  1307. * 延时执行
  1308. * @author 油小猴
  1309. * @param {number} time - 等待时间(毫秒)
  1310. * @returns {Promise<void>} 延时完成的 `Promise`
  1311. */
  1312. sleep(time) {
  1313. return new Promise(resolve => setTimeout(resolve, time));
  1314. },
  1315.  
  1316. /**
  1317. * 判断版本号新旧
  1318. * @author hmjz100
  1319. * @description 该函数将版本号按 `.` 分割为数字数组,逐段比较大小。
  1320. * 若某段 a 的数字大于 b,则 a 更新;
  1321. * 若所有段均相等,则版本相等(返回 false)。
  1322. * @param {string} a - 待比较的版本号
  1323. * @param {string} b - 基准版本号(如 "1.0.9.7")
  1324. * @returns {boolean} - 若 a 比 b 更新,返回 true;否则返回 false
  1325. */
  1326. isNewerVersion(a, b) {
  1327. const partsA = a.split('.').map(Number);
  1328. const partsB = b.split('.').map(Number);
  1329. const maxLength = Math.max(partsA.length, partsB.length);
  1330. for (let i = 0; i < maxLength; i++) {
  1331. const numA = partsA[i] || 0;
  1332. const numB = partsB[i] || 0;
  1333.  
  1334. if (numA > numB) return true;
  1335. if (numA < numB) return false;
  1336. }
  1337. return false;
  1338. },
  1339.  
  1340. /**
  1341. * 提取版本号主版本
  1342. * @author 油小猴
  1343. * @param {string} version - 完整版本号(如 `1.2.3`)
  1344. * @returns {string|null} 主版本号(如 `1`)或 `null`(格式错误时)
  1345. */
  1346. getMajorVersion(version) {
  1347. const [major] = (version || '').split('.');
  1348. return /^\d+$/.test(major) ? major : null;
  1349. },
  1350.  
  1351. /**
  1352. * 查找 React 组件实例
  1353. * @author 油小猴
  1354. * @description 支持 Fiber 架构遍历,可指定向上查找层级
  1355. * @param {HTMLElement} dom - 起始 DOM 元素
  1356. * @param {number} [traverseUp=0] - 向上遍历层级
  1357. * @returns {Object|null} React 组件实例或 `null`
  1358. */
  1359. findReact(dom, traverseUp = 0) {
  1360. const key = Object.keys(dom).find(key => {
  1361. return key.startsWith("__reactFiber$")
  1362. || key.startsWith("__reactInternalInstance$");
  1363. });
  1364. const domFiber = dom[key];
  1365. if (domFiber == null) return null;
  1366.  
  1367. if (domFiber._currentElement) {
  1368. let compFiber = domFiber._currentElement._owner;
  1369. for (let i = 0; i < traverseUp; i++) {
  1370. compFiber = compFiber._currentElement._owner;
  1371. }
  1372. return compFiber._instance;
  1373. }
  1374.  
  1375. const GetCompFiber = fiber => {
  1376. let parentFiber = fiber.return;
  1377. while (typeof parentFiber.type == "string") {
  1378. parentFiber = parentFiber.return;
  1379. }
  1380. return parentFiber;
  1381. };
  1382. let compFiber = GetCompFiber(domFiber);
  1383. for (let i = 0; i < traverseUp; i++) {
  1384. compFiber = GetCompFiber(compFiber);
  1385. }
  1386. return compFiber.stateNode || compFiber;
  1387. },
  1388.  
  1389. /**
  1390. * 初始化默认配置
  1391. * @author 油小猴
  1392. * @author hmjz100
  1393. * @description 创建基础配置、主题设置等存储项(仅当不存在时)
  1394. */
  1395. initDefaultConfig() {
  1396. let value = [
  1397. // RPC 设置
  1398. {
  1399. name: 'setting_rpc_domain',
  1400. value: 'http://localhost'
  1401. }, {
  1402. name: 'setting_rpc_port',
  1403. value: '16800'
  1404. }, {
  1405. name: 'setting_rpc_path',
  1406. value: '/jsonrpc'
  1407. }, {
  1408. name: 'setting_rpc_token',
  1409. value: ''
  1410. }, {
  1411. name: 'setting_rpc_dir',
  1412. value: 'D:\\Downloads\\'
  1413. },
  1414. // 杂项
  1415. {
  1416. name: 'setting_terminal_type',
  1417. value: 'wc'
  1418. }, {
  1419. name: 'setting_theme_color',
  1420. value: '#574AB8'
  1421. }, {
  1422. name: 'setting_init_code',
  1423. value: ''
  1424. }, {
  1425. name: 'setting_init_license',
  1426. value: ''
  1427. }, {
  1428. name: 'setting_init_version',
  1429. value: ''
  1430. },
  1431. // 额外
  1432. {
  1433. name: 'setting_theme_baidu',
  1434. value: 'no'
  1435. }, {
  1436. name: 'setting_theme_ali',
  1437. value: 'no'
  1438. }, {
  1439. name: 'setting_theme_mcloud',
  1440. value: 'no'
  1441. }, {
  1442. name: 'setting_theme_tcloud',
  1443. value: 'no'
  1444. }, {
  1445. name: 'setting_theme_xunlei',
  1446. value: 'no'
  1447. }, {
  1448. name: 'setting_theme_quark',
  1449. value: 'no'
  1450. }, {
  1451. name: 'setting_theme_uc',
  1452. value: 'no'
  1453. }, {
  1454. name: 'setting_theme_123',
  1455. value: 'no'
  1456. }];
  1457.  
  1458. value.forEach(function (v) {
  1459. // 没有对应的项目就添加上项目
  1460. if (!base.getValue(v.name)) base.setValue(v.name, v.value);
  1461. });
  1462. },
  1463.  
  1464. /**
  1465. * 显示设置界面
  1466. * @author 油小猴
  1467. * @author hmjz100
  1468. * @description 构建包含RPC配置、终端类型等设置项的交互界面
  1469. * @see {@link https://www.youxiaohou.com/zh-cn/motrix.html#使用指南 RPC 配置说明}、{@link https://www.youxiaohou.com/zh-cn/curl.html cURL 使用教程}
  1470. */
  1471. showSetting() {
  1472. let dom = ''
  1473. dom += `<div style="text-align: center;">带星号的设置项目将在网页刷新后生效</div>`
  1474. dom += `<label class="pl-setting-label"><div class="pl-label">RPC主机</div><input type="text" placeholder="主机地址,需带上http(s)://,但不需要写端口与路径" class="swal2-input pl-input listener-rpc-domain" value="${base.getValue('setting_rpc_domain')}"></label>`;
  1475. dom += `<label class="pl-setting-label"><div class="pl-label">RPC端口</div><input type="text" placeholder="端口号,例如:Motrix下载器为16800" class="swal2-input pl-input listener-rpc-port" value="${base.getValue('setting_rpc_port')}"></label>`;
  1476. dom += `<label class="pl-setting-label"><div class="pl-label">RPC路径</div><input type="text" placeholder="路径,默认为/jsonrpc" class="swal2-input pl-input listener-rpc-path" value="${base.getValue('setting_rpc_path')}"></label>`;
  1477. dom += `<label class="pl-setting-label"><div class="pl-label">RPC密钥</div><input type="text" placeholder="无密钥无需填写" class="swal2-input pl-input listener-rpc-token" value="${base.getValue('setting_rpc_token')}"></label>`;
  1478. dom += `<label class="pl-setting-label"><div class="pl-label">RPC保存</div><input type="text" placeholder="文件下载后保存路径,例如:D:\\Downloads\\" class="swal2-input pl-input listener-rpc-dir" value="${base.getValue('setting_rpc_dir')}"></label>`;
  1479. dom += `<label class="pl-setting-label"><div class="pl-label">当前RPC</div><div><span id="pl-rpcDomain">${base.getValue('setting_rpc_domain')}</span>:<span id="pl-rpcPort">${base.getValue('setting_rpc_port')}</span><span id="pl-rpcPath">${base.getValue('setting_rpc_path')}</span><button type="button" class="pl-button-mini swal2-confirm swal2-styled listener-rpc-test">测试</button></div></label>`;
  1480. dom += `<label class="pl-setting-label" style="padding-top:0;flex-direction:row-reverse;text-align:right;"><span><a href="https://www.youxiaohou.com/zh-cn/motrix.html#使用指南" target="_blank" class="pl-a" data-no-instant="1">RPC配置说明</a>,适用于 RPC 下载👆</span></label>`;
  1481.  
  1482. dom += `<label class="pl-setting-label"><div class="pl-label">终端类型</div><select class="swal2-select pl-input listener-terminal">`;
  1483. Object.keys(terminalType).forEach(k => {
  1484. dom += `<option value="${k}" ${base.getValue('setting_terminal_type') === k ? 'selected' : ''}>${terminalType[k]}</option>`;
  1485. });
  1486. dom += `</select></label>`;
  1487. dom += `<label class="pl-setting-label" style="padding-top:0;flex-direction:row-reverse;text-align:right;"><span><a href="https://www.youxiaohou.com/zh-cn/curl.html" target="_blank" class="pl-a" data-no-instant="1">cURL使用教程</a>,适用于 cURL 下载👆</span></label>`;
  1488.  
  1489. dom += `<button type="button" style="margin-top: 30px;" class="pl-button-mini swal2-deny swal2-styled listener-register">熄灭已经点亮的按钮*</button>`
  1490.  
  1491. dom = '<div>' + dom + '</div>';
  1492.  
  1493. Swal.fire({
  1494. title: '助手设置',
  1495. html: dom,
  1496. icon: 'info',
  1497. iconHtml: '⚙︎',
  1498. allowOutsideClick: false,
  1499. showCloseButton: true,
  1500. showConfirmButton: false,
  1501. footer: `<div>&#76;&#105;&#110;&#107;&#83;&#119;&#105;&#102;&#116;&#32;&#30001;&#32;&#104;&#109;&#106;&#122;&#49;&#48;&#48;&#32;&#21046;&#20316;</div><div>${config.base.dom.footer}</div>`,
  1502. ...swalDefault
  1503. });
  1504. doc.on('click', '.listener-register', async function (e) {
  1505. base.unRegisterInit();
  1506. });
  1507. doc.on('click', '.listener-rpc-test', async function (e) {
  1508. e.preventDefault();
  1509. let domain = base.getValue('setting_rpc_domain'),
  1510. port = base.getValue('setting_rpc_port'),
  1511. path = base.getValue('setting_rpc_path'),
  1512. token = base.getValue('setting_rpc_token');
  1513. if (e.target.innerHTML !== "测试") return;
  1514. e.target.innerHTML = "等待";
  1515. e.target.style.opacity = 0.9;
  1516. let result = await base.rpcTest(domain, port, path, token);
  1517. if (result === "success") {
  1518. e.target.innerHTML = "成功";
  1519. e.target.style.backgroundColor = "#52c41a";
  1520. } else {
  1521. e.target.innerHTML = "失败";
  1522. e.target.style.backgroundColor = "#cb1616";
  1523. }
  1524. e.target.style.opacity = "";
  1525. setTimeout(function () {
  1526. e.target.innerHTML = "测试";
  1527. e.target.style.backgroundColor = "";
  1528. }, 3000)
  1529. });
  1530. doc.on('input', '.listener-rpc-domain', async function (e) {
  1531. base.setValue('setting_rpc_domain', e.target.value);
  1532. document.getElementById("pl-rpcDomain").innerHTML = e.target.value;
  1533. });
  1534. doc.on('input', '.listener-rpc-port', async function (e) {
  1535. base.setValue('setting_rpc_port', e.target.value);
  1536. document.getElementById("pl-rpcPort").innerHTML = e.target.value;
  1537. });
  1538. doc.on('input', '.listener-rpc-path', async function (e) {
  1539. base.setValue('setting_rpc_path', e.target.value);
  1540. document.getElementById("pl-rpcPath").innerHTML = e.target.value;
  1541. });
  1542. doc.on('input', '.listener-rpc-token', async function (e) {
  1543. base.setValue('setting_rpc_token', e.target.value);
  1544. });
  1545. doc.on('input', '.listener-rpc-dir', async function (e) {
  1546. base.setValue('setting_rpc_dir', e.target.value);
  1547. });
  1548. doc.on('change', '.listener-terminal', async function (e) {
  1549. base.setValue('setting_terminal_type', e.target.value);
  1550. });
  1551. },
  1552.  
  1553. /**
  1554. * 显示美化设置界面
  1555. * @author hmjz100
  1556. * @description 提供主题颜色选择器和各网盘界面美化配置
  1557. * @fires .listener-color - 主题色选择事件
  1558. * @fires .listener-theme - 网盘主题配置变更事件
  1559. */
  1560. showBeautify() {
  1561. let dom = '',
  1562. btn = '',
  1563. colorList = ['#09AAFF', '#cc3235', '#518c17', '#ed944b', '#f969a5', '#bca280', '#b673ab', '#574AB8', '#1d2327', '#18a497', '#637dff', '#0d53ff', '#3181f9', '#f8d800', '#0396ff', '#32ccbc', '#f6416c', '#2271b1', '#59524c', '#ff679a', '#f44236', '#fec107', '#8bc24a', '#2594ed', '#9c28b1'],
  1564. colorNameList = ['度盘<br/>经典蓝', '度盘<br/>平安红', '度盘<br/>盎然绿', '度盘<br/>周年橙', '度盘<br/>幸会粉', '度盘<br/>午后棕', '度盘<br/>物语紫', '度盘<br/>星空紫', 'OpenAI<br/>默认黑', 'OpenAI<br/>默认青', '度里叁<br/>霞光紫', '夸克<br/>极简蓝', '移动<br/>彩云蓝', '果核<br/>柠檬黄', '果核<br/>默认蓝', '果核<br/>碧波绿', '果核<br/>玫瑰红', '文派<br/>默认蓝', '文派<br/>咖啡灰', '哔哩<br/>少女粉', '哔哩<br/>高能红', '哔哩<br/>咸蛋黄', '哔哩<br/>早苗绿', '哔哩<br/>宝石蓝', '哔哩<br/>罗兰紫'];
  1565. dom += `<div style="text-align: center;">带星号的美化项目将在网页刷新后生效</div>`
  1566.  
  1567. colorList.forEach((v, i) => {
  1568. btn += `<div style="background: ${v};border: 1px solid ${v}" class="pl-color-box ${v === base.getValue('setting_theme_color') ? 'listener-color' : 'listener-color'}">
  1569. <div data-color="${v}" class="pl-mask">${colorNameList[i]} ${v === base.getValue('setting_theme_color') ? '<span id="pl-thisColor">(当前)</span>' : ''}</div>
  1570. </div>`;
  1571. });
  1572.  
  1573. dom += `<label class="pl-setting-label"><div class="pl-label">主题颜色</div> <div class="pl-color">${btn}</div></label>`;
  1574.  
  1575. dom += `<label class="pl-setting-label"><div class="pl-label">[百度网盘]<br/>更换界面为主题颜色*</div><select class="swal2-select pl-input listener-theme" data-type="baidu">`;
  1576. Object.keys(assistantTheme).forEach(value => { dom += `<option value="${value}" ${base.getValue('setting_theme_baidu') === value ? 'selected' : ''}>${assistantTheme[value]}</option>`; });
  1577. dom += `</select></label>`;
  1578. dom += `<label class="pl-setting-label" style="padding-top:0;flex-direction:row-reverse;text-align:right;"><span>旧版页面会美化,新版页面则是更换主题色👆</span></label>`;
  1579.  
  1580. dom += `<label class="pl-setting-label"><div class="pl-label">[阿里云盘]<br/>更换界面为主题颜色*</div><select class="swal2-select pl-input listener-theme" data-type="ali">`;
  1581. Object.keys(assistantTheme).forEach(value => { dom += `<option value="${value}" ${base.getValue('setting_theme_ali') === value ? 'selected' : ''}>${assistantTheme[value]}</option>`; });
  1582. dom += `</select></label>`;
  1583.  
  1584. dom += `<label class="pl-setting-label"><div class="pl-label">[移动云盘]<br/>更换界面为主题颜色*</div><select class="swal2-select pl-input listener-theme" data-type="mcloud">`;
  1585. Object.keys(assistantTheme).forEach(value => { dom += `<option value="${value}" ${base.getValue('setting_theme_mcloud') === value ? 'selected' : ''}>${assistantTheme[value]}</option>`; });
  1586. dom += `</select></label>`;
  1587.  
  1588. dom += `<label class="pl-setting-label"><div class="pl-label">[天翼云盘]<br/>更换界面为主题颜色*</div><select class="swal2-select pl-input listener-theme" data-type="tcloud">`;
  1589. Object.keys(assistantTheme).forEach(value => { dom += `<option value="${value}" ${base.getValue('setting_theme_tcloud') === value ? 'selected' : ''}>${assistantTheme[value]}</option>`; });
  1590. dom += `</select></label>`;
  1591.  
  1592. dom += `<label class="pl-setting-label"><div class="pl-label">[迅雷云盘]<br/>更换界面为主题颜色*</div><select class="swal2-select pl-input listener-theme" data-type="xunlei">`;
  1593. Object.keys(assistantTheme).forEach(value => { dom += `<option value="${value}" ${base.getValue('setting_theme_xunlei') === value ? 'selected' : ''}>${assistantTheme[value]}</option>`; });
  1594. dom += `</select></label>`;
  1595.  
  1596. dom += `<label class="pl-setting-label"><div class="pl-label">[夸克网盘]<br/>更换界面为主题颜色*</div><select class="swal2-select pl-input listener-theme" data-type="quark">`;
  1597. Object.keys(assistantTheme).forEach(value => { dom += `<option value="${value}" ${base.getValue('setting_theme_quark') === value ? 'selected' : ''}>${assistantTheme[value]}</option>`; });
  1598. dom += `</select></label>`;
  1599.  
  1600. dom += `<label class="pl-setting-label"><div class="pl-label">[UC网盘]<br/>更换界面为主题颜色*</div><select class="swal2-select pl-input listener-theme" data-type="uc">`;
  1601. Object.keys(assistantTheme).forEach(value => { dom += `<option value="${value}" ${base.getValue('setting_theme_uc') === value ? 'selected' : ''}>${assistantTheme[value]}</option>`; });
  1602. dom += `</select></label>`;
  1603.  
  1604. dom += `<label class="pl-setting-label"><div class="pl-label">[123云盘]<br/>更换界面为主题颜色*</div><select class="swal2-select pl-input listener-theme" data-type="123">`;
  1605. Object.keys(assistantTheme).forEach(value => { dom += `<option value="${value}" ${base.getValue('setting_theme_123') === value ? 'selected' : ''}>${assistantTheme[value]}</option>`; });
  1606. dom += `</select></label>`;
  1607.  
  1608. dom = '<div>' + dom + '</div>';
  1609.  
  1610. Swal.fire({
  1611. title: '助手美化',
  1612. html: dom,
  1613. icon: 'success',
  1614. iconHtml: '🍃︎',
  1615. allowOutsideClick: false,
  1616. showCloseButton: true,
  1617. showConfirmButton: false,
  1618. footer: `<div>&#76;&#105;&#110;&#107;&#83;&#119;&#105;&#102;&#116;&#32;&#30001;&#32;&#104;&#109;&#106;&#122;&#49;&#48;&#48;&#32;&#21046;&#20316;</div><div>${config.base.dom.footer}</div>`,
  1619. ...swalDefault
  1620. });
  1621. doc.on('click', '.listener-color', async function (e) {
  1622. if (e.target.dataset.color) {
  1623. if (document.getElementById("pl-thisColor")) {
  1624. document.getElementById("pl-thisColor").remove();
  1625. }
  1626. e.target.innerHTML += '<span id="pl-thisColor">(当前)</span>';
  1627. base.setValue('setting_theme_color', e.target.dataset.color);
  1628. base.addPanLinkerStyle();
  1629. }
  1630. });
  1631. doc.on('change', '.listener-theme', async function (e) {
  1632. base.setValue(`setting_theme_${e.target.dataset.type}`, e.target.value);
  1633. });
  1634. },
  1635.  
  1636. /**
  1637. * 显示调试信息面板
  1638. * @description 展示脚本运行时环境、版本信息及依赖状态
  1639. * @author hmjz100
  1640. * @property {string} manageHandler - 外部管理器名称
  1641. * @property {string} manageVersion - 外部管理器版本
  1642. */
  1643. showDebug() {
  1644. let debugInfo = '';
  1645. debugInfo += `<span>以下内容均为脚本自检信息<br/>本页面仅作为调试使用<span>`;
  1646. debugInfo += `<label class="pl-setting-label"><div class="pl-label">[外置]<br/>管理器名称</div>${mhandler ? mhandler : "无法获取"}</label>`;
  1647. debugInfo += `<label class="pl-setting-label"><div class="pl-label">[外置]<br/>管理器版本</div>${mversion ? mversion : "无法获取"}</label>`;
  1648. debugInfo += `<label class="pl-setting-label"><div class="pl-label">[内置]<br/>脚本名称</div>${sname ? sname : "无法获取"}</label>`;
  1649. debugInfo += `<label class="pl-setting-label"><div class="pl-label">[内置]<br/>脚本版本</div>${sversion ? sversion : "无法获取"}</label>`;
  1650. debugInfo += `<label class="pl-setting-label"><div class="pl-label">[内置]<br/>脚本作者</div>${sauthor ? sauthor : "无法获取"}</label>`;
  1651.  
  1652. debugInfo += `<label class="pl-setting-label"><div class="pl-label">[内置]<br/>公众号码</div>${config.base.service.account ? `${config.base.service.account}<img src="${config.base.service.account}"></img>` : "无法获取"}</label>`;
  1653. debugInfo += `<label class="pl-setting-label"><div class="pl-label">[内置]<br/>Toast页脚</div>${config.base.dom.footer ? config.base.dom.footer : "此网站暂无"}</label>`;
  1654.  
  1655.  
  1656. debugInfo = '<div>' + debugInfo + '</div>';
  1657.  
  1658. Swal.fire({
  1659. icon: 'info',
  1660. title: '调试信息',
  1661. html: debugInfo,
  1662. allowOutsideClick: false,
  1663. showCloseButton: true,
  1664. confirmButtonText: '关闭',
  1665. ...swalDefault
  1666. });
  1667. },
  1668.  
  1669. /**
  1670. * 显示版本更新日志
  1671. * @author hmjz100
  1672. * @description 按时间倒序展示所有历史版本更新内容
  1673. */
  1674. showUpdate() {
  1675. Swal.fire({
  1676. icon: 'info',
  1677. title: '更新日志',
  1678. html: `<div class="version-log">
  1679. <div class="block">
  1680. <blockquote>
  1681. <div>风雨送春归,飞雪迎春到。已是悬崖百丈冰,犹有花枝俏。</div>
  1682. <div>俏也不争春,只把春来报。待到山花烂漫时,她在丛中笑。</div>
  1683. </blockquote>
  1684. </div>
  1685. <div class="block">
  1686. <name>V1.1.0.1</name>
  1687. <div>
  1688. <div>1、修复查看 RPC 下载任务的 Bug。</div>
  1689. </div>
  1690. </div>
  1691. <div class="block">
  1692. <name>V1.1.0</name>
  1693. <div>
  1694. <div>1、支持 UC 网盘、123 云盘;</div>
  1695. <div>2、改进了网盘主题的注入方式;</div>
  1696. <div>3、聚合并重构了部分重复函数,对整体脚本逻辑进行了梳理和精简;</div>
  1697. <div>4、将脚本执行阶段从 document-body 适配为 document-start。</div>
  1698. </div>
  1699. </div>
  1700. <hr/>
  1701. <div class="block">
  1702. <name>V1.0.9.7</name>
  1703. <div>
  1704. <div>1、修复移动云盘下载错误;</div>
  1705. <div>2、优化代码,更好的错误识别;</div>
  1706. <div>3、去除了游小猴云服务。</div>
  1707. </div>
  1708. </div>
  1709. <div class="block">
  1710. <name>V1.0.9.6</name>
  1711. <div>
  1712. <div>1、支持在百度网盘中选择文件夹下载;</div>
  1713. <div>2、优化部分提示。</div>
  1714. </div>
  1715. </div>
  1716. <div class="block">
  1717. <name>V1.0.9.5</name>
  1718. <div>
  1719. <div>1、修复因代码逻辑错误而无法获取链接的 Bug。</div>
  1720. </div>
  1721. </div>
  1722. <div class="block">
  1723. <name>V1.0.9.4</name>
  1724. <div>
  1725. <div>1、修复因百度网盘 AccessToken 过期导致无法获取链接的 Bug。</div>
  1726. </div>
  1727. </div>
  1728. <div class="block">
  1729. <name>V1.0.9.3</name>
  1730. <div>
  1731. <div>1、若网盘不支持在分享中下载,将仅显示保存网盘按钮;</div>
  1732. <div>2、优化下载界面,支持选择 Iframe Blob 的方式来下载文件,增加按钮的提示文本;</div>
  1733. <div>3、优化 CSS 样式,统一了 SweetAlert2 按钮样式,同时适配了 Dark Reader 插件,界面更协调;</div>
  1734. <div>4、支持修改油小猴网站主题色;</div>
  1735. <div>5、原有主题相关设置现已移动至助手美化页面中。</div>
  1736. </div>
  1737. </div>
  1738. <div class="block">
  1739. <name>V1.0.9.2</name>
  1740. <div>
  1741. <div>1、修复使用API 下载时有可能会导致IDM无限弹窗的Bug。</div>
  1742. </div>
  1743. </div>
  1744. <div class="block">
  1745. <name>V1.0.9.1</name>
  1746. <div>
  1747. <div>1、修复在百度网盘旧版下脚本无法删除元素的Bug。</div>
  1748. </div>
  1749. </div>
  1750. <div class="block">
  1751. <name>V1.0.9</name>
  1752. <div>
  1753. <div>1、跟进官方V6.2.7,修复因无法进行百度授权而导致获取直链报错 9019 Bug。</div>
  1754. </div>
  1755. </div>
  1756. <hr/>
  1757. <div class="block">
  1758. <name>V1.0.8.9</name>
  1759. <div>
  1760. <div>1、跟进官方V6.2.3,优化保存到网盘提示,修复阿里云盘、移动云盘失效的问题;</div>
  1761. <div>2、优化修改网盘主题的代码,减少对页面的破坏。</div>
  1762. </div>
  1763. </div>
  1764. <div class="block">
  1765. <name>V1.0.8.8</name>
  1766. <div>
  1767. <div>1、修复下载菜单字体过小的Bug。</div>
  1768. </div>
  1769. </div>
  1770. <div class="block">
  1771. <name>V1.0.8.7</name>
  1772. <div>
  1773. <div>1、修复在阿里云盘分享页面下点击“未点亮”按钮时没有任何反应的Bug;</div>
  1774. <div>2、更新并优化网盘界面精简规则;</div>
  1775. <div>3、支持更换 百度网盘、阿里云盘、迅雷云盘、夸克网盘、移动云盘 界面的主题颜色。</div>
  1776. </div>
  1777. </div>
  1778. <div class="block">
  1779. <name>V1.0.8.6</name>
  1780. <div>
  1781. <div>1、新增移动云盘会员中心页面,可在网盘中点击“会员中心”按钮查看(但无法使用第三方支付)。</div>
  1782. </div>
  1783. </div>
  1784. <div class="block">
  1785. <name>V1.0.8.5</name>
  1786. <div>
  1787. <div>1、跟进官方V6.1.6,修复迅雷网盘分享页面无法选中文件,修复移动云盘无法判断页面。</div>
  1788. </div>
  1789. </div>
  1790. <div class="block">
  1791. <name>V1.0.8.4</name>
  1792. <div>
  1793. <div>1、修复因重复绑定按钮而导致命令重复执行的Bug;</div>
  1794. <div>2、优化调试信息界面排版;</div>
  1795. <div>3、移除对百度网盘手机网页版的支持。</div>
  1796. </div>
  1797. </div>
  1798. <div class="block">
  1799. <name>V1.0.8.3</name>
  1800. <div>
  1801. <div>1、适配阿里云盘新域名alipan.com。</div>
  1802. </div>
  1803. </div>
  1804. <div class="block">
  1805. <name>V1.0.8.2</name>
  1806. <div>
  1807. <div>1、更换新图标。</div>
  1808. </div>
  1809. </div>
  1810. <div class="block">
  1811. <name>V1.0.8.1</name>
  1812. <div>
  1813. <div>1、修复因重复绑定按钮而导致RPC 下载会发送多条下载请求的Bug;</div>
  1814. <div>2、选择不使用油小猴服务器时,“用ghproxy连接Github仓库”更换为“用jsdelivr连接Github仓库”;</div>
  1815. <div>3、跟进官方V6.1.4版本,修复移动网盘无法获取链接,支持阿里云盘新域名alipan.com。</div>
  1816. </div>
  1817. </div>
  1818. <hr/>
  1819. <div class="block">
  1820. <name>V1.0.8</name>
  1821. <div>
  1822. <div>1、修复迅雷网盘无法勾选文件。</div>
  1823. </div>
  1824. </div>
  1825. <div class="block">
  1826. <name>V1.0.7.9</name>
  1827. <div>
  1828. <div>1、更新精简网盘元素匹配规则,防止因通知横条而导致不能点到“API 下载”以下的按钮。</div>
  1829. </div>
  1830. </div>
  1831. <div class="block">
  1832. <name>V1.0.7.8</name>
  1833. <div>
  1834. <div>1、跟进官方V6.1.2,加入V2接口。</div>
  1835. <div>2、修复百度网盘下载时因为获取不到accessToken而一直转圈。</div>
  1836. </div>
  1837. </div>
  1838. <div class="block">
  1839. <name>V1.0.7.7</name>
  1840. <div>
  1841. <div>1、修复百度网盘的按钮会因为主题不同而被改变颜色的Bug;</div>
  1842. <div>2、更新夸克网盘按钮与界面。</div>
  1843. </div>
  1844. </div>
  1845. <div class="block">
  1846. <name>V1.0.7.6</name>
  1847. <div>
  1848. <div>1、修复“注入”功能;</div>
  1849. <div>2、黑暗模式支持随设置热切换。</div>
  1850. </div>
  1851. </div>
  1852. <div class="block">
  1853. <name>V1.0.7.5</name>
  1854. <div>
  1855. <div>1、修复阿里云盘下载逻辑;</div>
  1856. <div>2、精简代码;</div>
  1857. <div>3、支持深色模式;</div>
  1858. <div>4、修改部分提示文本;</div>
  1859. <div>5、修改部分CSS;</div>
  1860. <div>6、设置可测试RPC连接。</div>
  1861. </div>
  1862. </div>
  1863. <div class="block">
  1864. <name>V1.0.7.4</name>
  1865. <div>
  1866. <div>1、优化下载逻辑;</div>
  1867. <div>2、修复阿里云盘无法使用API 下载。</div>
  1868. </div>
  1869. </div>
  1870. <div class="block">
  1871. <name>V1.0.7.3</name>
  1872. <div>
  1873. <div>1、如果出现网络请求错误时支持自动重新请求;</div>
  1874. <div>2、可选择是否使用油小猴服务器。</div>
  1875. </div>
  1876. </div>
  1877. <div class="block">
  1878. <name>V1.0.7.2</name>
  1879. <div>
  1880. <div>1、修复使用RPC 下载时会重复发送链接的Bug。</div>
  1881. </div>
  1882. </div>
  1883. <div class="block">
  1884. <name>V1.0.7.1</name>
  1885. <div>
  1886. <div>[实验功能,不影响正常使用]支持百度网盘手机网页版,勾选文件后可在顶栏找到“下载助手”按钮。</div>
  1887. </div>
  1888. </div>
  1889. <div class="block">
  1890. <name>V1.0.7</name>
  1891. <div>
  1892. <div>1、重构夸克网盘、阿里云盘按钮。</div>
  1893. </div>
  1894. </div>
  1895. <hr/>
  1896. <div class="block">
  1897. <name>V1.0.6.9</name>
  1898. <div>
  1899. <div>1、下载窗口加入关闭按钮。</div>
  1900. </div>
  1901. </div>
  1902. <div class="block">
  1903. <name>V1.0.6.8</name>
  1904. <div>
  1905. <div>1、修复夸克网盘按钮错位。</div>
  1906. </div>
  1907. </div>
  1908. <div class="block">
  1909. <name>V1.0.6.7</name>
  1910. <div>
  1911. <div>1、将百度网盘界面修改为主题色,可在设置选择是否修改;</div>
  1912. <div>2、增加主题色名称,更改部分内容颜色;</div>
  1913. <div>3、移动云盘API 下载支持批量复制;</div>
  1914. <div>4、优化控制台输出结果;</div>
  1915. <div>5、百度网盘API 下载不使用IDM时可以显示剩余时间;</div>
  1916. <div>6、“取消点亮按钮”按钮的位置现已移动到设置页面。</div>
  1917. <div>7homo特有的彩蛋又回来力(喜)。</div>
  1918. </div>
  1919. </div>
  1920. <div class="block">
  1921. <name>V1.0.6.6</name>
  1922. <div>
  1923. <div>1、修复暗号错误。</div>
  1924. </div>
  1925. </div>
  1926. <div class="block">
  1927. <name>V1.0.6.5</name>
  1928. <div>
  1929. <div>1、修复即使输入正确暗号也不能成功点亮按钮的服务器错误。</div>
  1930. </div>
  1931. </div>
  1932. <div class="block">
  1933. <name>V1.0.6.4</name>
  1934. <div>
  1935. <div>1、跟进官方V6.1.1版本,修复阿里云盘获取下载链接时的问题。</div>
  1936. </div>
  1937. </div>
  1938. <div class="block">
  1939. <name>V1.0.6.3</name>
  1940. <div>
  1941. <div>1、照顾小屏幕用户,将始终显示复制全部链接的按钮;</div>
  1942. <div>2、增加取消下载时的动画。</div>
  1943. </div>
  1944. </div>
  1945. <div class="block">
  1946. <name>V1.0.6.2</name>
  1947. <div>
  1948. <div>1、修复部分界面错位,实现CSS内置;</div>
  1949. <div>2、百度网盘界面将变得更加简洁。</div>
  1950. </div>
  1951. </div>
  1952. <div class="block">
  1953. <name>V1.0.6.1</name>
  1954. <div>
  1955. <div>1、新增百度云盘API 下载支持复制链接;</div>
  1956. <div>2、为了照顾手机浏览器用户,增大项目之间间隙,新增隐藏IDM提示选项,可在助手设置中启用;</div>
  1957. <div>3、修改CSS,界面会出现更多的主题色;</div>
  1958. <div>4、支持在游小猴官网查看暗号;</div>
  1959. <div>5、修复部分语法错误。</div>
  1960. </div>
  1961. </div>
  1962. <div class="block">
  1963. <name>V1.0.6</name>
  1964. <div>
  1965. <div>1、修复了打开阿里云盘分享连接时因下载移动端广告导致只能点击 API 下载;</div>
  1966. <div>2、跟进官方6.0.4版本,修复夸克网盘获取下载链接失效、支持移动云盘。</div>
  1967. </div>
  1968. </div>
  1969. <hr/>
  1970. <div class="block">
  1971. <name>V1.0.5.5</name>
  1972. <div>
  1973. <div>1、感谢<a href="https://github.com/Night-stars-1">Night-stars-1</a>的帮助,修复因为原作者服务器导致的初始化暗号识别错误;</div>
  1974. <div>2、修改一些文本以及提供给服务器的信息。</div>
  1975. </div>
  1976. </div>
  1977. <div class="block">
  1978. <name>V1.0.5.4</name>
  1979. <div>
  1980. <div>1、小修小改css,让主题色出现在更多地方;</div>
  1981. <div>2、修改下载链接获取失败的提示;</div>
  1982. <div>3、增加更多的主题色,可在助手设置查看;</div>
  1983. <div>4homo彩蛋被删去力(悲)。</div>
  1984. </div>
  1985. </div>
  1986. <div class="block">
  1987. <name>V1.0.5.3</name>
  1988. <div>
  1989. <div>1、修啦修啦,阿里云盘可以摸到下载菜单了。</div>
  1990. </div>
  1991. </div>
  1992. <div class="block">
  1993. <name>V1.0.5.2</name>
  1994. <div>
  1995. <div>1、增加脚本信息菜单(没有用);</div>
  1996. <div>2、优化阿里云盘显示svg图片;</div>
  1997. <div>3、修改弹窗按钮颜色。</div>
  1998. </div>
  1999. </div>
  2000. <div class="block">
  2001. <name>V1.0.5.1</name>
  2002. <div>
  2003. <div>1、修复在切换按钮主题后夸克网盘不能正常显示按钮。</div>
  2004. </div>
  2005. </div>
  2006. <div class="block">
  2007. <name>V1.0.5</name>
  2008. <div>
  2009. <div>1、跟进官方V5.0.4版本;</div>
  2010. <div>2、小改动,照着官方版本更正文件名称检测;</div>
  2011. <div>3、保留彩蛋,但必须舍弃官方暗号。</div>
  2012. </div>
  2013. </div>
  2014. <div class="block">
  2015. <name>V1.0.4</name>
  2016. <div>
  2017. <div>大改!</div>
  2018. <div>1、修复了原作者留下的夸克网盘切换文件夹就多一个“下载助手”按钮的大BUG;</div>
  2019. <div>2、终于来了,在下载菜单增加“助手设置”“更新日志”按钮;</div>
  2020. <div>【再也不用点进油猴管理再进设置了(保留油猴管理内设置)】</div>
  2021. <div>3、修改阿里云盘和夸克网盘下载助手按钮样式;</div>
  2022. <div>4、增加“取消点亮按钮”油猴菜单;</div>
  2023. <div>5、修改部分css,使其与选择的主题更贴切。</div>
  2024. </div>
  2025. </div>
  2026. <hr/>
  2027. <div class="block">
  2028. <name>V1.0.3</name>
  2029. <div>
  2030. <div>1、增加一个小彩蛋; 提示:</div>
  2031. <div>homo(需在未点亮按钮状态触发)</div>
  2032. <div>【需要重新恢复按钮为未点亮状态请进入 已安装脚本->编辑->开发者->重置到出厂->确定】</div>
  2033. <div>2、修改/增加默认主题色。</div>
  2034. </div>
  2035. </div>
  2036. <div class="block">
  2037. <name>V1.0.2</name>
  2038. <div>
  2039. <div>1、修改并加宽界面,调整部分css,使Sweetalert2界面更美观,更与原版相近;</div>
  2040. <div>2、修改部分提示文字,使文字更容易复制。</div>
  2041. </div>
  2042. </div>
  2043. <div class="block">
  2044. <name>V1.0.1</name>
  2045. <div>
  2046. <div>1、去除更新提示;</div>
  2047. <div>2、更新Sweetalert211版本;</div>
  2048. <div>3、部分CDN节点更换为jsdelivr。</div>
  2049. </div>
  2050. </div>
  2051. <div class="block">
  2052. <name>V1.0.0</name>
  2053. <div>
  2054. <div>1、增加“注入”功能(bushi);</div>
  2055. <div>2、去除广告。</div>
  2056. </div>
  2057. </div>
  2058. </div>
  2059. <style>
  2060. div:where(.swal2-container) div:where(.swal2-popup) {
  2061. width: 36em !important;
  2062. }
  2063. .version-log {
  2064. text-align: left;
  2065. }
  2066. .version-log > .block,
  2067. .version-log > hr {
  2068. margin-bottom: 20px;
  2069. }
  2070. .version-log > hr {
  2071. border-style: inset;
  2072. border-width: 1px;
  2073. }
  2074. .version-log .block name {
  2075. display: block;
  2076. margin-bottom: 10px;
  2077. font-size: 1.2em;
  2078. }
  2079. .version-log .block div {
  2080. margin-bottom: 5px;
  2081. }
  2082. .version-log .block blockquote {
  2083. padding: 0.7em;
  2084. border-left: 5px solid #bdbdbd;
  2085. background-color: #f9f9f9;
  2086. }
  2087. @media (prefers-color-scheme: dark) {
  2088. .version-log .block blockquote {
  2089. border-left: 5px solid #7A7C84;
  2090. background-color: #464851;
  2091. }
  2092. }
  2093. </style>`,
  2094. allowOutsideClick: false,
  2095. showCloseButton: true,
  2096. confirmButtonText: '我已阅',
  2097. ...swalDefault
  2098. });
  2099. },
  2100.  
  2101. /**
  2102. * 创建浮动提示框
  2103. * @description 监听元素悬停事件,动态显示带文件大小的提示框
  2104. * @author 油小猴
  2105. * @author hmjz100
  2106. * @fires .listener-tip - 鼠标移动事件触发提示框定位
  2107. * @see {@link color} 使用全局主题色渲染文件大小信息
  2108. */
  2109. createTip() {
  2110. // 添加提示框到 body
  2111. $('html').append('<div class="pl-tooltip"></div>');
  2112.  
  2113. // 监听所有带有 .listener-tip 类的元素的鼠标移动事件
  2114. $(document).on('mousemove', '.listener-tip', function (e) {
  2115. // 获取提示文字
  2116. let tip = e.currentTarget.dataset.title || `${e.currentTarget.innerText} <span style="margin-left: 10px;color: ${color}">${e.currentTarget.dataset.size}</span>`;
  2117.  
  2118. // 设置提示框的位置并使用 fadeIn() 渐变显示
  2119. $('.pl-tooltip').html(tip).css({
  2120. 'left': e.pageX + 10 + 'px',
  2121. 'top': e.pageY + 20 + 'px' // 增加一些间距
  2122. }).show();
  2123. });
  2124.  
  2125. $(document).on('mouseleave', '.listener-tip, .pl-tooltip', function (e) {
  2126. $('.pl-tooltip').hide();
  2127. });
  2128. },
  2129.  
  2130. /**
  2131. * 创建加载状态弹窗
  2132. * @author 油小猴
  2133. * @description 生成包含旋转动画的加载容器
  2134. */
  2135. createLoading() {
  2136. return $('<div class="pl-loading"><div class="pl-loading-box"><div><div></div><div></div></div></div></div>');
  2137. },
  2138.  
  2139. /**
  2140. * 创建用于下载的隐藏 iframe
  2141. * @author 油小猴
  2142. * @description 该方法会创建一个隐藏的 iframe 元素,并将其插入到指定的挂载点中,用于后续的下载操作。
  2143. * iframe 的 src 设置为 "javascript:;" 以避免加载额外资源,提升性能。
  2144. */
  2145. createDownloadIframe() {
  2146. let iframe = $('<iframe style="padding:0;margin:0;display:block;display:none" src="javascript:;" id="downloadIframe"></iframe>');
  2147. base.waitForKeyElements(`.${mount}`, (element) => {
  2148. element.append(iframe);
  2149. }, true)
  2150. },
  2151.  
  2152. /**
  2153. * 获取镜像列表
  2154. * @author 油小猴
  2155. * @description 根据原始链接和镜像域名列表生成多个镜像链接,支持多线程下载。
  2156. * 每个镜像地址会根据 thread 参数生成多个重复链接(通过添加 `&` 符号区分)。
  2157. * @param {string} link - 原始下载链接
  2158. * @param {Array<string>} mirror - 镜像域名数组
  2159. * @param {number} [thread=2] - 每个镜像生成的线程数(链接重复次数),默认为 2
  2160. * @returns {string} 所有镜像链接组成的字符串,每行一个链接
  2161. *
  2162. * @example
  2163. * getMirrorList("https://example.com/file.zip", ["mirror1.com", "mirror2.com"], 2)
  2164. * // 返回:
  2165. * // https://mirror1.com/file.zip
  2166. * // https://mirror1.com/file.zip&
  2167. * // https://mirror2.com/file.zip
  2168. * // https://mirror2.com/file.zip&
  2169. */
  2170. getMirrorList(link, mirror, thread = 2) {
  2171. let host = new URL(link).host;
  2172. let mirrors = [];
  2173. for (let i = 0; i < mirror.length; i++) {
  2174. for (let j = 0; j < thread; j++) {
  2175. let item = link.replace(host, mirror[i]) + '&'.repeat(j);
  2176. mirrors.push(item);
  2177. }
  2178. }
  2179. return mirrors.join('\n');
  2180. },
  2181.  
  2182. /**
  2183. * 创建基础样式
  2184. * @author 油小猴
  2185. * @author hmjz100
  2186. * @description 为组件添加默认的公共样式,包括浅色和深色模式适配样式。
  2187. */
  2188. addPanLinkerStyle() {
  2189. color = base.getValue('setting_theme_color');
  2190.  
  2191. base.addStyle('swal-pub-style', 'style', `@media (prefers-color-scheme: light) {${GM_getResourceText('SwalLigt')}}`);
  2192. base.addStyle('swal-pub-dark-style', 'style', `@media (prefers-color-scheme: dark) {${GM_getResourceText('SwalDark')}}`);
  2193. base.addStyle('swal-pub-custom-style', 'style', `
  2194. .swal2-styled{transition: all 0.2s ease;}
  2195. .swal2-loader{display:none;align-items:center;justify-content:center;width:2.2em;height:2.2em;margin:0 1.875em;-webkit-animation:swal2-rotate-loading 1.5s linear 0s infinite normal;animation:swal2-rotate-loading 1.5s linear 0s infinite normal;border-width:.25em;border-style:solid;border-radius:100%;border-color:${color} transparent }
  2196. .swal2-timer-progress-bar-container{position:absolute;right:0;bottom:0;left:0;grid-column:auto;overflow:hidden;border-bottom-right-radius:5px;border-bottom-left-radius:5px}
  2197. .swal2-timer-progress-bar{width:100%;height:.25em;background:${color}33 }
  2198. .swal2-progress-steps .swal2-progress-step{z-index:20;flex-shrink:0;width:2em;height:2em;border-radius:2em;background:${color};color:#fff;line-height:2em;text-align:center}
  2199. .swal2-progress-steps .swal2-progress-step.swal2-active-progress-step{background:${color} }
  2200. .swal2-progress-steps .swal2-progress-step-line{z-index:10;flex-shrink:0;width:2.5em;height:.4em;margin:0 -1px;background:${color}}
  2201. .swal2-html-container {padding:1em 1.6em 0.3em;margin:0}
  2202.  
  2203.  
  2204. .swal2-close,div:where(.swal2-container) button:where(.swal2-close) {position:absolute;top:1px;right:1px;transition: all 0.2s ease}
  2205. .swal2-close:hover,div:where(.swal2-container) button:where(.swal2-close):hover {color:${color};font-size:60px}
  2206.  
  2207. .swal2-styled.swal2-confirm,div:where(.swal2-container) button:where(.swal2-styled):where(.swal2-confirm){background-color:${color};color:#fff}
  2208.  
  2209. .swal2-styled.swal2-confirm:focus,div:where(.swal2-container) button:where(.swal2-styled):where(.swal2-confirm):focus{box-shadow:0 0 0 3px ${color}80}
  2210. .swal2-styled.swal2-deny:focus,.swal2-close:focus,div:where(.swal2-container) button:where(.swal2-styled):where(.swal2-deny):focus{box-shadow:0 0 0 3px #dc374180}
  2211. .swal2-styled.swal2-cancel:focus,div:where(.swal2-container) button:where(.swal2-styled):where(.swal2-cancel):focus{box-shadow:0 0 0 3px #6e788180}
  2212.  
  2213. .swal2-styled.swal2-confirm,
  2214. .swal2-styled.swal2-deny,
  2215. .swal2-styled.swal2-cancel,
  2216. div:where(.swal2-container) button:where(.swal2-styled):where(.swal2-confirm),
  2217. div:where(.swal2-container) button:where(.swal2-styled):where(.swal2-deny),
  2218. div:where(.swal2-container) button:where(.swal2-styled):where(.swal2-cancel)
  2219. {border-radius:50px}
  2220. div:where(.swal2-container) div:where(.swal2-actions):not(.swal2-loading) .swal2-styled:hover{opacity:0.7}
  2221.  
  2222. .swal2-popup,div:where(.swal2-container) div:where(.swal2-popup){padding-bottom:1em;border-radius:10px}
  2223. div:where(.swal2-container) div:where(.swal2-html-container){padding:1.3em 1.3em 0.3em;margin:0}
  2224. .swal2-footer,div:where(.swal2-container) div:where(.swal2-footer){flex-direction:column;justify-content:center;align-items:center}
  2225. div:where(.swal2-icon) .swal2-icon-content {font-family: sans-serif;}
  2226.  
  2227. .swal2-input, .swal2-file, swal2-select, .swal2-textarea,
  2228. div:where(.swal2-container) input:where(.swal2-input),
  2229. div:where(.swal2-container) input:where(.swal2-file),
  2230. div:where(.swal2-container) input:where(.swal2-select),
  2231. div:where(.swal2-container) textarea:where(.swal2-textarea)
  2232. {box-shadow:none}
  2233.  
  2234. .swal2-input:focus, .swal2-file:focus, .swal2-select:focus, .swal2-textarea:focus,
  2235. .swal2-input:focus-visible, .swal2-file:focus-visible, .swal2-select:focus-visible, .swal2-textarea:focus-visible,
  2236. div:where(.swal2-container) input:where(.swal2-input):focus,
  2237. div:where(.swal2-container) input:where(.swal2-input):focus-visible,
  2238. div:where(.swal2-container) input:where(.swal2-file):focus,
  2239. div:where(.swal2-container) input:where(.swal2-file):focus-visible,
  2240. div:where(.swal2-container) input:where(.swal2-select):focus,
  2241. div:where(.swal2-container) input:where(.swal2-select):focus-visible,
  2242. div:where(.swal2-container) textarea:where(.swal2-textarea):focus,
  2243. div:where(.swal2-container) textarea:where(.swal2-textarea):focus-visible
  2244. {outline:0;border:1px solid ${color};box-shadow:0 0 0 3px ${color}80}
  2245. .swal2-checkbox, .swal2-file, .swal2-input, .swal2-radio, .swal2-select, .swal2-textarea,
  2246. div:where(.swal2-container) input:where(.swal2-input), div:where(.swal2-container) input:where(.swal2-file), div:where(.swal2-container) textarea:where(.swal2-textarea), div:where(.swal2-container) select:where(.swal2-select), div:where(.swal2-container) div:where(.swal2-radio), div:where(.swal2-container) label:where(.swal2-checkbox)
  2247. {margin: 1em 2em}`);
  2248. base.addStyle(`${mount}-main-style`, 'style', `
  2249. ::-webkit-scrollbar{width:8px;height:8px;transition:all 0.2s ease}
  2250. ::-webkit-scrollbar-track{border-radius:10px;background:#fff}
  2251. ::-webkit-scrollbar-thumb,::-webkit-scrollbar-thumb:hover{border-radius:10px}
  2252. ::-webkit-scrollbar-thumb{background-color:${color}90 !important}
  2253. ::-webkit-scrollbar-thumb:hover{background-color:${color}D0 !important}
  2254. .swal2-popup{font-size:16px}
  2255. .pl-a{vertical-align:baseline;color:${color}}
  2256. .pl-a:hover{color:${color}90}
  2257. .pl-popup{font-size:12px;min-width:70%;max-width:95%}
  2258. .pl-popup a:not(.pl-btn-primary){color:${color}}
  2259. .pl-popup a:hover:not(.pl-btn-primary){color:${color}90}
  2260. .pl-header{padding:0;align-items:flex-start;border-bottom:1px solid #eee;margin:0 0 10px;padding:0 0 5px}
  2261. .pl-title{font-size:18px;white-space:nowrap;text-overflow:ellipsis}
  2262. .pl-content{padding:0;font-size:12px}
  2263. .pl-main{background-color:${color}15;overflow:auto;border-radius:10px;max-height:calc(${document.documentElement.clientHeight}px - 250px)}
  2264. .pl-footer{font-size:15px;text-align:center;display:block}
  2265. .pl-item{display:flex;align-items:center;background-color:${color}30;border-radius:5px;margin:8px 6px}
  2266. .pl-item-name{flex:0 0 170px;text-align:left;margin:6px;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;cursor:default;height:30px}
  2267. .pl-item-link{flex:1;text-align:left;white-space:nowrap;text-overflow:ellipsis;cursor:pointer;overflow:hidden}
  2268. .pl-item-tip{display:flex;justify-content:space-between;flex:1}
  2269. .pl-ext{display:inline-block;width:44px;background:#999;color:#fff;height:16px;line-height:16px;font-size:12px;border-radius:3px}
  2270. .pl-retry{padding:3px 10px;background:#cc3235;color:#fff;border-radius:3px;cursor:pointer}
  2271. .pl-item-progress{display:flex;flex:1;align-items:center}
  2272. .pl-progress{display:inline-block;vertical-align:middle;width:100%;box-sizing:border-box;line-height:1;position:relative;height:20px;margin:0 6px;flex:1}
  2273. .pl-progress-outer{height:20px;border-radius:100px;background-color:#c1c1c1a1;overflow:hidden;position:relative;vertical-align:middle}
  2274. .pl-progress-inner{position:absolute;left:0;top:0;background-color:${color};border-radius:100px;line-height:1;white-space:nowrap;transition:width .6s ease;height:20px;display:inline-flex;text-align:center;align-items:center}
  2275. .pl-progress-inner-text{display:inline-block;vertical-align:middle;cursor:default;color:#ffffff;font-size:12px;margin:0 5px;height:20px;width:100%}
  2276. .pl-progress-inner-text:after{display:inline-block;content:"";height:100%;vertical-align:middle}
  2277. .pl-extra{margin-top:10px;background-color:${color}15;border-radius:10px;display:flex}
  2278. .pl-extra button{flex:1}
  2279. .pl-btn-primary{background:${color};border:0;border-radius:50px;color:#ffffff;cursor:pointer;font-size:12px;outline:none;display:flex;align-items:center;justify-content:center;margin:6px 6px;padding:0.625em 1.1em;transition:0.3s opacity}
  2280. .pl-btn-primary:hover{opacity:0.8;transition:0.3s opacity}
  2281. .pl-btn-primary:focus{box-shadow:0 0 0 3px ${color}80}
  2282. .pl-btn-success{background:#55af28}
  2283. .pl-btn-success:focus{box-shadow:0 0 0 3px #55af2880}
  2284. .pl-btn-info{background:#606266}
  2285. .pl-btn-info:focus{box-shadow:0 0 0 3px #60626680}
  2286. .pl-btn-warning{background:#da9328}
  2287. .pl-btn-warning:focus{box-shadow:0 0 0 3px #da932880}
  2288. .pl-btn-danger{background:#cc3235}
  2289. .pl-btn-danger:focus{box-shadow:0 0 0 3px #cc323580}
  2290. @keyframes easeOpacity { from {opacity: 1} 50% {opacity: 0.35} to {opacity: 1} }
  2291. .pl-btn-opacity{animation:easeOpacity 1.2s 2;animation-fill-mode:forwards}
  2292. .pl-button-mini{padding:5px 10px}
  2293. .pl-dropdown-menu-item{height:30px;display:flex;align-items:center;justify-content:center;cursor:pointer;color:${color};transition:all 0.2s ease}
  2294. .pl-dropdown-menu-item:hover{background-color:${color}15}
  2295. .pl-button-mode{padding:0px;padding-left:0px !important;color:${color}!important;cursor:pointer;;transition:all 0.2s ease}
  2296. .pl-button-mode:hover{background-color:${color}33 !important}
  2297. .g-button-menu.pl-button-mode{padding:0px !important}
  2298. .pl-button,.pl-dropdown-menu{transition:all 0.2s ease}
  2299. .pl-button{position:relative}
  2300. .pl-button .pl-dropdown-menu{opacity:0;pointer-events:none}
  2301. .pl-button:hover .pl-dropdown-menu{opacity:1;pointer-events:auto}
  2302. @keyframes easeInitOpacity { from {opacity: 0.5} 50% {opacity: 1} to {opacity: 0.5} }
  2303. .pl-button-init{opacity:0.5;animation:easeInitOpacity 1.2s 5;animation-fill-mode:forwards}
  2304.  
  2305. header[style="display:none;"]~.pl-button{display:inline-block;position:fixed;top:0.6em;left:65%;z-index:99999}
  2306. .baidu-button{background:${color}!important;border-color:${color}!important;border:1px solid ${color}!important;display:inline-flex}
  2307. .baidu-button:hover{background:${color}b0 !important;border-color:${color}!important}
  2308. .ali-button{background:${color};border:0 solid transparent;font-size:14px;margin-left:20px;padding:8px 16px;position:relative;height:32px;border-radius:100px;display:flex;align-items:center;justify-content:center;color:var(--basic_white);cursor:pointer;transition:all .3s ease}
  2309. .ali-button:hover{background:${color}D0}
  2310. .ali-btn-icon{vertical-align:-0.2em}
  2311. .tcloud-button{color:#fff;border:1px solid ${color};background:${color};position:relative;height:30px;padding:0 12px;margin-right:12px;font-size:12px;line-height:28px;cursor:pointer}
  2312. .tcloud-button:hover{border-color:${color}b0;background:${color}b0}
  2313. .mcloud-button{float:left;position:relative;margin:20px 24px 20px 0;width:110px;height:36px;background:${color};border-radius:2px;font-size:14px;color:#fff;line-height:39px;text-align:center;cursor:pointer}
  2314. .mcloud-share-button{display:inline-block;position:relative;font-size:14px;line-height:36px;height:36px;text-align:center;color:#fff;border:1px solid ${color};border-radius:2px;padding:0 24px;background:${color}}
  2315. .mcloud-share-button:hover{background:${color}b0}
  2316. .mcloud-button:hover{background:${color}b0}
  2317. .mcloud-btn{background:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAMAAAC7IEhfAAAAAXNSR0IB2cksfwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAGNQTFRFAAAA////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////mkUNoAAAACF0Uk5TAAbHPP9AMRtr9PwrV8zqXfmNgDODHTLD4iJxhGJJ8Z269m0aDgAAAMZJREFUeJzd0ssOgyAQBVDUK74rWq0PFP3/ryxqTMdGqJtuvGHD5CTDTGDs3nFc17kEPcC7BH3At/Tjvk5AYbBU+NcrwghL4uQDk3gtRSF1KWCCQEpghkd+3jp/ICNQoDANU0AQCJQmWAJ3h8+q3mFdvSywQdttsGvRWGAPLReoHXrbG6WWAzBoJ+3DaCnWI39NLbcvszvLeuTB2fYoqbNBNo7sGjzk31BhMsEJitxmiKk8zSQwE8gFjBGcNuCzOmdqPrib5A2JRQ7qK9g+hQAAAABJRU5ErkJggg==");height:20px;line-height:20px;display:inline-block;background-repeat:no-repeat;background-size:20px 20px;text-indent:25px}
  2318. .xunlei-button{display:inline-flex;align-items:center;justify-content:center;border:0 solid transparent;border-radius:5px;box-shadow:0 0 0 0 transparent;width:fit-content;white-space:nowrap;flex-shrink:0;font-size:14px;line-height:1.5;outline:0;touch-action:manipulation;transition:background .3s ease,color .3s ease,border .3s ease,box-shadow .3s ease;color:#fff;background:${color};margin-left:12px;padding:0px 12px;position:relative;cursor:pointer;height:36px}
  2319. .xunlei-button:hover{background:${color}b0}
  2320. .quark-button,.uc-button{padding:0 14px;background:${color}!important;background-color:${color}!important}
  2321. .uc-button{padding:10px 20px!important}
  2322. .quark-button:hover,.uc-button:hover{background:${color}b0 !important;background-color:${color}b0 !important}
  2323. .quark-btn-icon,.uc-btn-icon{width:20px;height:20px;vertical-align:-0.3em}
  2324.  
  2325. .pointer{cursor:pointer}
  2326. .pl-setting-label{display:flex;align-items:center;justify-content:space-between;padding-top:10px}
  2327. .pl-label{flex:0 0 100px;text-align:left}
  2328. .pl-input{flex:1;padding:8px 10px!important;border:1px solid #c2c2c2;border-radius:5px;font-size:14px!important;min-width:300px;margin:0;}
  2329. .init-input{width:90%;text-align:center;font-family:system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Ubuntu,"Helvetica Neue",sans-serif;font-weight:300}
  2330. .pl-color{flex:1;display:flex;flex-wrap:wrap}
  2331. .pl-color-box{width:55px;height:55px;margin:10px 10px 0 0;box-sizing:border-box;border:1px solid #fff;cursor:pointer}
  2332. .pl-mask{width:53px;height:53px;opacity:0;transition:opacity 0.3s;color:#fff;font-size:13px;display:flex;align-items:center;justify-content:center;flex-direction:column}
  2333. .pl-color-box:hover .pl-mask{opacity:1}
  2334. .pl-close:focus{outline:0;box-shadow:none}
  2335. .tag-danger{color:#cc3235;margin:0 5px}
  2336. .pl-tooltip{position:absolute;color:#ffffff;max-width:600px;font-size:12px;padding:5px 10px;background:#333;border-radius:5px;z-index:110000;line-height:1.3;display:none;word-break:break-all}
  2337. .pl-loading-box>div>div{position:absolute;border-radius:50%}
  2338. .pl-loading-box>div>div:nth-child(1){top:9px;left:9px;width:82px;height:82px;background:#ffffff}
  2339. @keyframes load{ 0% {transform: rotate(0deg)} 100% {transform: rotate(360deg)} }
  2340. .pl-loading-box>div>div:nth-child(2){top:14px;left:38px;width:25px;height:25px;background:#666666;animation:load 1s linear infinite;transform-origin:12px 36px}
  2341. .pl-loading{width:16px;height:16px;display:inline-block;overflow:hidden;background:none}
  2342. .pl-loading-box{width:100%;height:100%;position:relative;transform:translateZ(0) scale(0.16);backface-visibility:hidden;transform-origin:0 0}
  2343. .pl-loading-box div{box-sizing:content-box}
  2344. .pl-dropdown-menu{position:absolute;padding:5px 0;color:${color};background:#fff;z-index:999;width:110px;border-radius:5px;box-shadow:0 1px 6px ${color}33;-webkit-box-shadow:0 1px 6px ${color}33;text-align:center;border:none;transition:all 0.2s ease}
  2345. @media (prefers-color-scheme: dark) { .pl-dropdown-menu{background:#19191a;} }
  2346. .pl-button-save{background-color:${color}!important;color:#fff !important}
  2347. .pl-button-save:hover{background-color:${color}D0 !important;color:#fff !important}
  2348. .swal2-container{z-index:100000}
  2349. body.swal2-height-auto{height:inherit}
  2350. svg.icon-rpc-devices{width:13px;height:13px}
  2351. [class^="swal2-"],[class*="pl-btn"]{transition:all 0.3s ease}
  2352.  
  2353. /* 适配(改)百度网盘会员青春版 */
  2354. a.downloadSubtitle, button.downloadSubtitle{transition:all 0.3s ease;background-color:${color}}
  2355. a.downloadSubtitle:hover, button.downloadSubtitle:hover{background-color:${color}D0}
  2356. a.downloadSubtitle:disabled, button.downloadSubtitle:disabled {background-color: ${color}D0}
  2357.  
  2358. /* RGB! */
  2359. @keyframes RGB{ 0% {filter: hue-rotate()} to {filter: hue-rotate(-360deg)} }
  2360.  
  2361. /* Webkit, Opera, IE9, Chrome*/
  2362. ::selection, ::-webkit-selection, ::-moz-selection, ::-ms-selection,::-edge-selection {background-color:${color}!important;background:${color}!important;color:white!important;}
  2363. `);
  2364.  
  2365. if (/(pan|yun).baidu.com/.test(location.host) && location.pathname !== '/disk/home' && base.getValue('setting_theme_baidu') === 'yes') {
  2366. base.setColors([
  2367. ['#717fff', color],
  2368. ['#717FFF', color],
  2369. ['#06a8ff', color],
  2370. ['#06A8FF', color],
  2371. ['#06a7ff', color],
  2372. ['#06A7FF', color],
  2373. ['#dcdfe6', color],
  2374. ['#DCDFE6', color],
  2375. ['#0095ff', color],
  2376. ['#0095FF', color],
  2377. ['#09aaff', color],
  2378. ['#09AAFF', color],
  2379. ['#0ca6ff', color],
  2380. ['#0CA6FF', color],
  2381. ['#5040ff', color],
  2382. ['#5040FF', color],
  2383. ['#454d5a', color],
  2384. ['#454D5A', color],
  2385. ['#a2abbd', color],
  2386. ['#A2ABBD', color],
  2387. ['#030b1a', color],
  2388. ['#030B1A', color],
  2389. ['#afb3bf', color],
  2390. ['#AFB3BF', color],
  2391. ['#ff436a', color],
  2392. ['#FF436A', color],
  2393. ['#03081a', color],
  2394. ['#03081A', color],
  2395. ['#2974b6', color],
  2396. ['#2974B6', color],
  2397. ['#0596e6', color],
  2398. ['#0596E6', color],
  2399.  
  2400. ['#C3EAFF', color],
  2401. ['#c0d9fe', `${color}50`],
  2402. ['#0098EA', `${color}D0`],
  2403.  
  2404. ['#38b9ff', `${color}D0`],
  2405. ['#38B9FF', `${color}D0`],
  2406. ['#42d8ff', `${color}D0`],
  2407. ['#42D8FF', `${color}D0`],
  2408. ['#a48dff', `${color}D0`],
  2409. ['#A48DFF', `${color}D0`],
  2410. ['#6b79f2', `${color}D0`],
  2411. ['#6B79F2', `${color}D0`],
  2412.  
  2413. ['#9c86f2', `${color}90`],
  2414. ['#9C86F2', `${color}90`],
  2415. ['#83d3ff', `${color}90`],
  2416. ['#83D3FF', `${color}90`],
  2417. ['#C4D8F4', `${color}90`],
  2418.  
  2419. ['#fafafc', `${color}20`],
  2420. ['#FAFAFC', `${color}20`],
  2421. ['#f5fbff', `${color}20`],
  2422. ['#F5FBFF', `${color}20`],
  2423. ['#b4e5ff', `${color}20`],
  2424. ['#B4E5FF', `${color}20`],
  2425. ['#f0faff', `${color}20`],
  2426. ['#F0FAFF', `${color}20`],
  2427. ['#c4d8f4', `${color}20`],
  2428.  
  2429. ['#f1f3f8', `${color}15`],
  2430. ['#F1F3F8', `${color}15`],
  2431.  
  2432. ['#f2faff', `${color}10`],
  2433. ['#F2FAFF', `${color}10`],
  2434. ['#eef9fe', `${color}10`],
  2435. ['#EEF9FE', `${color}10`],
  2436. ['#f7f9fc', `${color}10`],
  2437. ['#F7F9FC', `${color}10`],
  2438. ['#f5f6fa', `${color}10`],
  2439. ['#F5F6FA', `${color}10`],
  2440. ['#b4e5ff', `${color}10`],
  2441. ['#B4E5FF', `${color}10`],
  2442. ['#e6f6ff', `${color}10`],
  2443. ['#E6F6FF', `${color}10`],
  2444.  
  2445. ['0,149,255', base.hexToRgba(color)],
  2446. ['30, 175, 255', base.hexToRgba(color)],
  2447. ['6, 167, 255, 0.1', base.hexToRgba(`${color}1a`)],
  2448. ['6,167,255,.1', base.hexToRgba(`${color}1a`)],
  2449. ['6,167,255,.23', base.hexToRgba(`${color}3b`)],
  2450. ['164,141,255,.2', base.hexToRgba(`${color}30`)],
  2451. ['196,182,255,.2', base.hexToRgba(`${color}20`)],
  2452. ['113,127,255,.2', base.hexToRgba(`${color}40`)],
  2453. ['3,8,26,.6', base.hexToRgba(`${color}D0`)],
  2454. ['255,32,102,.4', base.hexToRgba(`${color}66`)],
  2455. ['72,166,248,.7', base.hexToRgba(`${color}66`)],
  2456. ]);
  2457. };
  2458. if (/(pan|yun).baidu.com/.test(location.host) && base.getValue('setting_theme_baidu') === 'yes') {
  2459. base.addStyle(`${mount}-baidu`, 'style', `
  2460. #layoutMain,
  2461. .DxdbeCb {
  2462. border-radius: 10px;
  2463. border-bottom-left-radius: 0;
  2464. border-bottom-right-radius: 0;
  2465. background: #ffffffA0 !important
  2466. }
  2467. .KPDwCE,
  2468. .DxdbeCb .OFaPaO .tanwePYr,
  2469. .xGLMIab .fufHyA:hover,
  2470. .module-search-timeline .form-box {
  2471. background: #ffffffA0 !important;
  2472. }
  2473. .KPDwCE .JDeHdxb,
  2474. .NHcGw .AuPKyz,
  2475. .xGLMIab .tvPMvPb,
  2476. .xGLMIab .FcQMwt,
  2477. .cazEfA .yfHIsP,
  2478. .hscjZ4QL .bbxnZ0Bq .ehnyLxWZ span,
  2479. .module-topToolBar,
  2480. .module-timeline-view .timeline-title-curday {
  2481. background: transparent !important;
  2482. border-bottom: 0;
  2483. }
  2484. .MdLxwM {
  2485. background :#fff !important;
  2486. }
  2487. .aside-absolute-container {
  2488. position: absolute !important;
  2489. }
  2490. .aside-absolute-container .QGOvsxb .remainingSpaceUi_span {
  2491. background: #8af248 !important;
  2492. border-radius: 10px 0 0 10px;
  2493. border-right: #fff 1px solid;
  2494. border-bottom: #fff 1px solid;
  2495. }
  2496. .xtJbHcb .CDaavKb .KQcHyA {
  2497. background: rgb(244,207,0) !important;
  2498. padding: 8px 15px;
  2499. }
  2500. .xtJbHcb .web-header-nav-new-version-inner {
  2501. background: ${color} !important;
  2502. padding: 8px 15px;
  2503. line-height: 15px;
  2504. width: auto;
  2505. height: auto;
  2506. }
  2507. a {
  2508. transition: all 0.2s ease !important;
  2509. }
  2510. #bd-main .bd-left {
  2511. margin: auto !important;
  2512. }
  2513. .verify-input input {
  2514. padding-left: 0 !important;
  2515. text-align: center !important;
  2516. }
  2517. .verify-input input:focus {
  2518. border: 2px solid ${color} !important;
  2519. }
  2520. [data-theme=light] .vp-video-page-card .vp-video-page-card__video-detail {
  2521. color: #030b1a;
  2522. }
  2523. dt.level-1 {
  2524. background: #fd6d65 !important;
  2525. }
  2526. dt.level-2 {
  2527. background: #f3a723 !important;
  2528. }
  2529. dt.level-1 i.desc-arrow {
  2530. border-bottom: 10px solid #dd6966 !important;
  2531. }
  2532. dt.level-2 i.desc-arrow {
  2533. border-bottom: 10px solid #d29633 !important;
  2534. }
  2535. `, `.${mount}`);
  2536. base.setColors([
  2537. ['#717fff', color],
  2538. ['#717FFF', color],
  2539. ['#06a8ff', color],
  2540. ['#06A8FF', color],
  2541. ['#06a7ff', color],
  2542. ['#06A7FF', color],
  2543. ['#dcdfe6', color],
  2544. ['#DCDFE6', color],
  2545. ['#0095ff', color],
  2546. ['#0095FF', color],
  2547. ['#09aaff', color],
  2548. ['#09AAFF', color],
  2549. ['#0ca6ff', color],
  2550. ['#0CA6FF', color],
  2551. ['#5040ff', color],
  2552. ['#5040FF', color],
  2553. ['#454d5a', color],
  2554. ['#454D5A', color],
  2555. ['#a2abbd', color],
  2556. ['#A2ABBD', color],
  2557. ['#030b1a', color],
  2558. ['#030B1A', color],
  2559. ['#afb3bf', color],
  2560. ['#AFB3BF', color],
  2561. ['#ff436a', color],
  2562. ['#FF436A', color],
  2563. ['#03081a', color],
  2564. ['#03081A', color],
  2565. ['#2974b6', color],
  2566. ['#2974B6', color],
  2567. ['#0596e6', color],
  2568. ['#0596E6', color],
  2569.  
  2570. ['#C3EAFF', color],
  2571. ['#c0d9fe', `${color}50`],
  2572. ['#0098EA', `${color}D0`],
  2573.  
  2574. ['#38b9ff', `${color}D0`],
  2575. ['#38B9FF', `${color}D0`],
  2576. ['#42d8ff', `${color}D0`],
  2577. ['#42D8FF', `${color}D0`],
  2578. ['#a48dff', `${color}D0`],
  2579. ['#A48DFF', `${color}D0`],
  2580. ['#6b79f2', `${color}D0`],
  2581. ['#6B79F2', `${color}D0`],
  2582.  
  2583. ['#9c86f2', `${color}90`],
  2584. ['#9C86F2', `${color}90`],
  2585. ['#83d3ff', `${color}90`],
  2586. ['#83D3FF', `${color}90`],
  2587. ['#C4D8F4', `${color}90`],
  2588.  
  2589. ['#fafafc', `${color}20`],
  2590. ['#FAFAFC', `${color}20`],
  2591. ['#f5fbff', `${color}20`],
  2592. ['#F5FBFF', `${color}20`],
  2593. ['#b4e5ff', `${color}20`],
  2594. ['#B4E5FF', `${color}20`],
  2595. ['#f0faff', `${color}20`],
  2596. ['#F0FAFF', `${color}20`],
  2597. ['#c4d8f4', `${color}20`],
  2598.  
  2599. ['#f1f3f8', `${color}15`],
  2600. ['#F1F3F8', `${color}15`],
  2601.  
  2602. ['#f2faff', `${color}10`],
  2603. ['#F2FAFF', `${color}10`],
  2604. ['#eef9fe', `${color}10`],
  2605. ['#EEF9FE', `${color}10`],
  2606. ['#f7f9fc', `${color}10`],
  2607. ['#F7F9FC', `${color}10`],
  2608. ['#f5f6fa', `${color}10`],
  2609. ['#F5F6FA', `${color}10`],
  2610. ['#b4e5ff', `${color}10`],
  2611. ['#B4E5FF', `${color}10`],
  2612. ['#e6f6ff', `${color}10`],
  2613. ['#E6F6FF', `${color}10`],
  2614.  
  2615. ['0,149,255', base.hexToRgba(color)],
  2616. ['30, 175, 255', base.hexToRgba(color)],
  2617. ['6, 167, 255, 0.1', base.hexToRgba(`${color}1a`)],
  2618. ['6,167,255,.1', base.hexToRgba(`${color}1a`)],
  2619. ['6,167,255,.23', base.hexToRgba(`${color}3b`)],
  2620. ['164,141,255,.2', base.hexToRgba(`${color}30`)],
  2621. ['196,182,255,.2', base.hexToRgba(`${color}20`)],
  2622. ['113,127,255,.2', base.hexToRgba(`${color}40`)],
  2623. ['3,8,26,.6', base.hexToRgba(`${color}D0`)],
  2624. ['255,32,102,.4', base.hexToRgba(`${color}66`)],
  2625. ['72,166,248,.7', base.hexToRgba(`${color}66`)],
  2626. ], "other");
  2627. };
  2628. if (/www.(aliyundrive|alipan).com/.test(location.host) && base.getValue('setting_theme_ali') === 'yes') {
  2629. base.setColors([
  2630. ['#3763ff', color],
  2631. ['#8664ff', `${color}D0`],
  2632. ['99, 125, 255', base.hexToRgba(color)],
  2633. ['132, 133, 141', base.hexToRgba(color)],
  2634. ['112, 136, 255', base.hexToRgba(color)],
  2635. ['97, 122, 250', base.hexToRgba(color)],
  2636. ['68, 109, 255', base.hexToRgba(color)],
  2637. ['82, 110, 250', base.hexToRgba(`${color}20`)],
  2638. ['122, 144, 255', base.hexToRgba(`${color}D0`)],
  2639. ['138, 157, 255', base.hexToRgba(`${color}D0`)],
  2640. //['49, 49, 54', base.hexToRgba(color)],
  2641. ]);
  2642. };
  2643. if (/(yun|caiyun).139.com/.test(location.host) && base.getValue('setting_theme_mcloud') === 'yes') {
  2644. base.setColors([
  2645. ['#3181f9', color],
  2646. ['#5a9afa', color],
  2647. ['#98c0fc', `${color}D0`],
  2648. ['#2d76e5', `${color}D0`],
  2649. ['49,129,249,.08', base.hexToRgba(`${color}20`)],
  2650. ]);
  2651. };
  2652. if (/cloud.189.cn/.test(location.host) && base.getValue('setting_theme_tcloud') === 'yes') {
  2653. base.setColors([
  2654. ['#2b89ea', color],
  2655. ['#1874d3', `${color}F0`],
  2656. ['#1890ff', color],
  2657. ['#388fc9', color],
  2658. ['#0087ff', color],
  2659. ['#255697', color],
  2660. ['#3ea6ff', `${color}80`],
  2661. ['#1d52f2', color],
  2662. ['#3699ff', `${color}D0`],
  2663. ['#f4f9fe', `${color}10`],
  2664. ['#eaf5ff', `${color}20`],
  2665. ], "other");
  2666. }
  2667. if (/pan.xunlei.com/.test(location.host) && base.getValue('setting_theme_xunlei') === 'yes') {
  2668. base.setColors([
  2669. ['#3f85ff', color],
  2670. ['63,133,255,.1', base.hexToRgba(`${color}20`)],
  2671. ['#2670ea', `${color}D0`],
  2672. ['#619bff', `${color}D0`],
  2673. ['#ecf3ff', `${color}10`],
  2674. ['#f6faff', `${color}10`],
  2675. ['#1a2845', `${color}20`],
  2676. ['#0f2035', `${color}20`],
  2677. ['#308bfd', `${color}20`],
  2678. ['#eee', `${color}20`],
  2679. ], "other");
  2680. base.addStyle(`${mount}-xunlei`, 'style', `
  2681. .web-header {
  2682. background: linear-gradient(0deg,${color}D0,${color})
  2683. }
  2684. `);
  2685. };
  2686. if (/pan.quark.cn/.test(location.host) && base.getValue('setting_theme_quark') === 'yes') {
  2687. base.setColors([
  2688. ['#0d53ff', color],
  2689. ['#e6f1ff', `${color}20`],
  2690. ['#f0faff', `${color}20`],
  2691. ['#7da3ff', `${color}D0`],
  2692. ['#ddd', `${color}D0`],
  2693. ['17,17,17,.9', base.hexToRgba(`${color}D0`)],
  2694. ['40,40,255,.04', base.hexToRgba(`${color}20`)],
  2695. ['#f7f7ff', 'transparent'],
  2696. ['238,247,255,0', base.hexToRgba(`${color}00`)],
  2697. ]);
  2698. base.addStyle(`${mount}-quark`, 'style', `
  2699. .file-list .hover-oper .hover-transparent-bg {
  2700. background: transparent !important;
  2701. }
  2702. .ant-checkbox-wrapper .ant-checkbox-checked .ant-checkbox-inner,
  2703. .ant-checkbox-wrapper .ant-checkbox-indeterminate .ant-checkbox-inner:after {
  2704. background-color: ${color}!important;
  2705. }
  2706. `);
  2707. };
  2708. if (/drive.uc.cn/.test(location.host) && base.getValue('setting_theme_uc') === 'yes') {
  2709. base.setColors([
  2710. ['#12161a', color],
  2711. ['#e6f1ff', `${color}20`],
  2712. ['#f0faff', `${color}20`],
  2713. ['#7da3ff', `${color}D0`],
  2714. ['#ddd', `${color}D0`],
  2715. ['17,17,17,.9', base.hexToRgba(`${color}D0`)],
  2716. ['40,40,255,.04', base.hexToRgba(`${color}20`)],
  2717. ['#f7f7ff', 'transparent'],
  2718. ['238,247,255,0', base.hexToRgba(`${color}00`)],
  2719. ]);
  2720. base.addStyle(`${mount}-uc`, 'style', `
  2721. .file-list .hover-oper .hover-transparent-bg {
  2722. background: transparent !important;
  2723. }
  2724. `);
  2725. };
  2726. if (/www.(123(pan|684|865|952|912).com|123pan.cn)/.test(location.host) && base.getValue('setting_theme_123') === 'yes') {
  2727. base.setColors([
  2728. ['#597dfc', color],
  2729. ['#5a7cfc', color],
  2730. ['#2A82E4', color],
  2731. ['#51a1f0', color],
  2732. ['#597DFC', color],
  2733. ['#40a9ff', color],
  2734. ['#3c80ff', color],
  2735. ['#3C80FF', color],
  2736. ['#1890ff', color],
  2737. ['#f0f9ff', `${color}20`],
  2738. ['#F2F5FF', `${color}20`],
  2739. ['#325cf0', `${color}D0`],
  2740. ['60, 128, 255', base.hexToRgba(color)],
  2741. ['42, 130, 228', base.hexToRgba(color)],
  2742. ]);
  2743. }
  2744. if (/.*.youxiaohou.com/.test(location.host)) {
  2745. base.setColors([
  2746. ['#00aefe', color],
  2747. ['#4e6e8e', color],
  2748. ['#009fe8', color],
  2749. ['#008fd1', color],
  2750. ['#05b0ff', `${color}D0`],
  2751. ], "other");
  2752. base.addStyle(`${mount}-youxiaohou`, 'style', `
  2753. a[aria-label="View source on GitHub"] svg[style^="fill"] {
  2754. fill: ${color} !important;
  2755. }
  2756. `);
  2757. };
  2758. },
  2759.  
  2760. /**
  2761. * 初始化引导弹窗
  2762. * @author 油小猴
  2763. * @author hmjz100
  2764. * @description 显示初始化对话框,引导用户进行配置或跳过流程。
  2765. * 支持输入特定数字触发彩蛋,并自动注入默认设置点亮功能。
  2766. * @returns {Promise<void>} 弹窗关闭后返回空值,可能触发页面刷新
  2767. */
  2768. async initDialog() {
  2769. let dialog = await Swal.fire({
  2770. title: `请阅读完以下全文再继续`,
  2771. allowOutsideClick: false,
  2772. showCloseButton: true,
  2773. showDenyButton: true,
  2774. confirmButtonText: '确认',
  2775. denyButtonText: '懒得输入...我要直接点亮!',
  2776. html: `
  2777. <div style="display:flex;flex-direction:column;align-items:center">
  2778. ${config.base.service.account ? `<img style="width: 250px;margin-bottom: 10px;" src="${config.base.service.account}">` : ``}
  2779. <input class="swal2-input init-input" id="init" type="text" placeholder="输入内容..."></div>
  2780. <div><span>你心想道: “没什么可以输入的”</span>
  2781. </div>
  2782. <div>↓</div>
  2783. <div>但你不知道的是...</div>
  2784. <div>你可以直接按下下方的<span style="color:red;font-style:italic;vertical-align:unset;">红色按钮</span>来跳过这一有趣的流程</div>
  2785. <div>或者继续流程,输入某些<span style="font-style:italic;vertical-align:unset;">恶臭的数字</span>查看彩蛋并点亮</div>
  2786. <div>↓</div>
  2787. <div><a target="_blank" href="https://www.youxiaohou.com" style="vertical-align:unset;">原作者</a>开发很辛苦,所以请有能力的你请支持下他的公众号</div>
  2788. <div>又或者...来给这个改版点个<a href="https://github.com/hmjz100/LinkSwift/" style="vertical-align:unset;">Star</a>?</div>
  2789. <div>点亮后不仅能精简网盘界面 还能改变众多网盘主题色哦!</div>
  2790. `,
  2791. ...swalDefault
  2792. });
  2793. if (dialog.isDenied) {
  2794. console.log("【LinkSwift】\n正在“注入”点亮按钮设置项目...");
  2795. message.warning("正在“注入”设置项目...");
  2796. await base.sleep(2500);
  2797. base.setValue('setting_init_code', config.base.num);
  2798. base.setValue('setting_init_license', config.base.license);
  2799. message.success("“注入”成功了!");
  2800. await base.sleep(1500);
  2801. location.reload();
  2802. };
  2803. if (dialog.isConfirmed) {
  2804. if ($('#init').val() === '114514' || $('#init').val() === '1919810' || $('#init').val() === '1145141919810') {
  2805. await Swal.fire({
  2806. icon: 'error',
  2807. title: '1145141919810',
  2808. html: '<span>homo特有的数字当然不行啦<br/>哼哼哼啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊</span>',
  2809. timer: 4000,
  2810. imageUrl: 'https://pic1.zhimg.com/v2-1b97a088e156c015108dec663bba8b04.avis',
  2811. allowOutsideClick: false,
  2812. timerProgressBar: true,
  2813. showConfirmButton: false,
  2814. showDenyButton: true,
  2815. denyButtonText: '哼哼哼啊啊啊啊啊啊啊啊啊啊',
  2816. ...swalDefault
  2817. });
  2818. message.info("成就:你触发了一个homo特有的彩蛋!");
  2819. await base.sleep(4000)
  2820. Swal.fire({
  2821. title: '1145141919810',
  2822. text: 'homo特有的数字当然不行啦...吗?',
  2823. icon: 'question',
  2824. imageUrl: 'https://lh1.hetaousercontent.com/img/7d4c1c0b4adb0e95.jpg',
  2825. showConfirmButton: false,
  2826. allowOutsideClick: false,
  2827. ...swalDefault
  2828. });
  2829. await base.sleep(3000)
  2830. base.setValue('setting_init_code', config.base.num);
  2831. base.setValue('setting_init_license', config.base.license);
  2832. message.success("成就:哼哼哼啊啊啊啊啊啊啊啊地注入成功(喜)");
  2833. await base.sleep(1500)
  2834. location.reload();
  2835. } else {
  2836. console.log("【LinkSwift】\n暗号错误")
  2837. await this.initDialog();
  2838. return;
  2839. };
  2840. }
  2841. },
  2842.  
  2843. /**
  2844. * 显示主对话框
  2845. * @author 油小猴
  2846. * @author hmjz100
  2847. * @description 使用 SweetAlert2 显示一个自定义样式的对话框,用于展示信息或操作提示。
  2848. * @param {string} title - 对话框标题
  2849. * @param {string} html - 对话框内容的 HTML 字符串
  2850. * @param {string} footer - 对话框底部说明文字
  2851. */
  2852. showMainDialog(title, html, footer) {
  2853. Swal.fire({
  2854. title,
  2855. html,
  2856. footer: `<span>${footer}</span>`,
  2857. customClass,
  2858. confirmButtonText: '关闭',
  2859. showCloseButton: true,
  2860. allowOutsideClick: false,
  2861. allowEscapeKey: false,
  2862. allowEnterKey: false,
  2863. willClose: function () {
  2864. base._resetAllData();
  2865. },
  2866. ...swalDefault
  2867. }).then(function () {
  2868. base._resetAllData();
  2869. });
  2870. },
  2871. /**
  2872. * 等待指定元素加载完成并执行回调
  2873. * @author hmjz100
  2874. * @description 监听 DOM 元素是否出现,若未出现则每隔一段时间重试,直到找到为止。
  2875. * 支持在 iframe 内部查找元素,适用于异步加载场景。
  2876. * @param {string} selectorElem - 要等待的目标元素选择器
  2877. * @param {Function} actionFunction - 找到元素后执行的回调函数,接收 jQuery 元素作为参数,返回 true 可以不再继续寻找
  2878. * @param {boolean} [bWaitOnce=false] - 是否只执行一次回调,默认为 false,如果不设置为 true 的话需要自行判断是否对元素进行操作
  2879. * @param {string} [iframeSelector] - 若目标元素位于 iframe 中,传入 iframe 的选择器
  2880. * @param {string} [controlKey] - 控制唯一性的键名,用于避免重复处理
  2881. */
  2882. waitForKeyElements(selectorElem, actionFunction, bWaitOnce, iframeSelector, controlKey) {
  2883. if (!this.waitForKeyElements.controlObj) {
  2884. this.waitForKeyElements.controlObj = {};
  2885. }
  2886. if (!this.waitForKeyElements.instanceCounter) {
  2887. this.waitForKeyElements.instanceCounter = 0;
  2888. }
  2889.  
  2890. let targetNodes;
  2891. if (typeof iframeSelector === "undefined") {
  2892. targetNodes = $(selectorElem);
  2893. } else {
  2894. targetNodes = $(iframeSelector).contents().find(selectorElem);
  2895. }
  2896.  
  2897. let controlKeyNew = controlKey || `wkfe_${this.waitForKeyElements.instanceCounter++}`;
  2898. let btargetsFound = false;
  2899. if (targetNodes && targetNodes.length > 0) {
  2900. targetNodes.each(function () {
  2901. let jThis = $(this);
  2902. let alreadyFound = jThis.data(controlKeyNew) || false;
  2903.  
  2904. if (!alreadyFound) {
  2905. var cancelFound = actionFunction(jThis);
  2906. if (cancelFound) {
  2907. btargetsFound = true;
  2908. } else if (bWaitOnce) {
  2909. jThis.data(controlKeyNew, true);
  2910. }
  2911. }
  2912. });
  2913. }
  2914.  
  2915. let controlObj = this.waitForKeyElements.controlObj;
  2916. let timeControl = controlObj[controlKeyNew];
  2917.  
  2918. if (btargetsFound && bWaitOnce && timeControl) {
  2919. clearInterval(timeControl);
  2920. delete controlObj[controlKeyNew];
  2921. } else if (!timeControl) {
  2922. timeControl = setInterval(() => {
  2923. this.waitForKeyElements(selectorElem, actionFunction, bWaitOnce, iframeSelector, controlKeyNew);
  2924. }, 1000);
  2925. controlObj[controlKeyNew] = timeControl;
  2926. }
  2927.  
  2928. this.waitForKeyElements.controlObj = controlObj;
  2929. },
  2930. };
  2931.  
  2932. /**
  2933. * 百度网盘
  2934. * @author 油小猴
  2935. * @author hmjz100
  2936. */
  2937. let $baidu = {
  2938. _getExtra() {
  2939. let seKey = decodeURIComponent(base.getCookie('BDCLND'));
  2940. return '{' + '"sekey":"' + seKey + '"' + "}";
  2941. },
  2942.  
  2943. _getSurl() {
  2944. let reg = /(?<=s\/|surl=)([a-zA-Z0-9_-]+)/g;
  2945. if (reg.test(location.href)) {
  2946. return location.href.match(reg)[0];
  2947. }
  2948. return '';
  2949. },
  2950.  
  2951. setBDUSS(custom) {
  2952. if (custom) {
  2953. base.setStorage("baiduyunPlugin_BDUSS", { BDUSS: custom });
  2954. return;
  2955. }
  2956. try {
  2957. GM_cookie('list', { name: 'BDUSS' }, (cookies, error) => {
  2958. if (!error) {
  2959. let BDUSS = cookies?.[0]?.value;
  2960. if (BDUSS) {
  2961. base.setStorage("baiduyunPlugin_BDUSS", { BDUSS });
  2962. }
  2963. } else {
  2964. throw new Error(error)
  2965. }
  2966. });
  2967. } catch (e) {
  2968. console.error("【LinkSwift】\nsetBDUSS\n错误信息:", e)
  2969. try {
  2970. let BDUSS = document.cookie.match(/BDUSS=(.*?)(;|$)/);
  2971. if (!!BDUSS || BDUSS === null) throw new Error("document.cookie dosen't had cookie")
  2972. base.setStorage("baiduyunPlugin_BDUSS", { BDUSS: BDUSS });
  2973. } catch (e) {
  2974. console.error("【LinkSwift】\nsetBDUSS\n错误信息:", e)
  2975. }
  2976. }
  2977. },
  2978.  
  2979. getBDUSS() {
  2980. doc.find('.loading-popup .loading-title').html(`凭证获取中`);
  2981. doc.find('.loading-popup .swal2-html-container').html(`<div>正在获取当前账号凭证~</div>`);
  2982. let baiduyunPlugin_BDUSS = base.getStorage('baiduyunPlugin_BDUSS') ? base.getStorage('baiduyunPlugin_BDUSS') : { BDUSS: '' };
  2983. return baiduyunPlugin_BDUSS.BDUSS || '';
  2984. },
  2985.  
  2986. async getToken() {
  2987. try {
  2988. doc.find('.loading-popup .loading-title').html(`令牌获取中`);
  2989. doc.find('.loading-popup .swal2-html-container').html(`<div>正在获取授权状态~</div>`);
  2990.  
  2991. // 获取授权状态
  2992. let authorize = await base.getFinalUrl(config.$baidu.api.getAccessToken);
  2993.  
  2994. // 判断授权情况
  2995. if (authorize.includes('authorize')) {
  2996. // 没授权,先获取授权的页面
  2997. doc.find('.loading-popup .loading-title').html(`授权获取中`);
  2998. doc.find('.loading-popup .swal2-html-container').html(`<div>正在获取授权页面~</div>`);
  2999.  
  3000. let html = await base.get(config.$baidu.api.getAccessToken, {}, 'text');
  3001.  
  3002. // 提取页面的发送确认授权的参数
  3003. let bdstoken = html.match(/name="bdstoken"\s+value="([^"]+)"/)?.[1];
  3004. let client_id = html.match(/name="client_id"\s+value="([^"]+)"/)?.[1];
  3005. let data = {
  3006. grant_permissions_arr: 'netdisk',
  3007. bdstoken: bdstoken,
  3008. client_id: client_id,
  3009. response_type: "token",
  3010. display: "page",
  3011. grant_permissions: "basic,netdisk"
  3012. };
  3013.  
  3014. doc.find('.loading-popup .loading-title').html(`授权获取中`);
  3015. doc.find('.loading-popup .swal2-html-container').html(`<div>正在自动确认授权~</div>`);
  3016. // 发送请求达到自动进行授权
  3017. await base.post(config.$baidu.api.getAccessToken, base.stringify(data), {
  3018. 'Content-Type': 'application/x-www-form-urlencoded',
  3019. });
  3020.  
  3021. // 再次获取授权状态
  3022. let res2 = await base.getFinalUrl(config.$baidu.api.getAccessToken);
  3023. let accessToken = res2.match(/access_token=([^&]+)/)?.[1];
  3024. if (!!accessToken) {
  3025. doc.find('.loading-popup .loading-title').html(`令牌获取中`);
  3026. doc.find('.loading-popup .swal2-html-container').html(`<div>授权成功,令牌已缓存~</div>`);
  3027. base.setValue('baidu_access_token', accessToken);
  3028. return accessToken;
  3029. } else {
  3030. doc.find('.loading-popup .loading-title').html(`令牌获取中`);
  3031. doc.find('.loading-popup .swal2-html-container').html(`<div>授权失败,等待下一步操作~</div>`);
  3032. return '';
  3033. }
  3034. } else if (authorize.includes('access_token=')) {
  3035. let accessToken = authorize.match(/access_token=([^&]+)/)?.[1];
  3036. if (!!accessToken) {
  3037. doc.find('.loading-popup .loading-title').html(`令牌获取中`);
  3038. doc.find('.loading-popup .swal2-html-container').html(`<div>授权成功,令牌已缓存~</div>`);
  3039. base.setValue('baidu_access_token', accessToken);
  3040. return accessToken;
  3041. } else {
  3042. doc.find('.loading-popup .loading-title').html(`令牌获取中`);
  3043. doc.find('.loading-popup .swal2-html-container').html(`<div>授权失败,等待下一步操作~</div>`);
  3044. return '';
  3045. }
  3046. } else {
  3047. return '';
  3048. }
  3049. } catch (error) {
  3050. return '';
  3051. }
  3052. },
  3053.  
  3054. addPageListener() {
  3055. /*
  3056. 防止代码因其他原因被执行多次
  3057. 这段代码出自 Via轻插件,作者谷花泰
  3058. */
  3059. const key = encodeURIComponent('LinkSwift:百度网盘');
  3060. if (window[key]) return;
  3061. window[key] = true;
  3062.  
  3063. function _factory(e) {
  3064. let target = $(e.target);
  3065. let item = target.parents('.pl-item');
  3066. let link = item.find('.pl-item-link.blob');
  3067. let directLink = item.find('.pl-item-link.browser');
  3068. let progress = item.find('.pl-item-progress');
  3069. let tip = item.find('.pl-item-tip');
  3070. let copy = item.find('.pl-item-copy');
  3071. let back = item.find('.pl-progress-back');
  3072. let stop = item.find('.pl-progress-stop');
  3073. return {
  3074. item, link, directLink, progress, tip, copy, back, stop, target,
  3075. };
  3076. }
  3077.  
  3078. doc.on('mouseenter mouseleave click', '.pl-button.g-dropdown-button', function (e) {
  3079. if (e.type === 'mouseleave') {
  3080. $(e.currentTarget).removeClass('button-open');
  3081. } else {
  3082. $(e.currentTarget).addClass('button-open');
  3083. $(e.currentTarget).find('.pl-dropdown-menu').show();
  3084. }
  3085. });
  3086. doc.on('mouseleave', '.pl-button.g-dropdown-button .pl-dropdown-menu', function (e) {
  3087. $(e.currentTarget).hide();
  3088. });
  3089.  
  3090. doc.on('click', '.pl-button-mode', function (e) {
  3091. mode = e.target.dataset.mode;
  3092. if (!mode) return;
  3093. $baidu.getLink();
  3094. });
  3095. doc.on('click', '.pl-button-save', async function (e) {
  3096. e.preventDefault();
  3097. selectList = $baidu.getSelectedList();
  3098. if (selectList.length === 0) {
  3099. return message.error('提示:<br/>请勾选要保存到网盘的文件哦~');
  3100. }
  3101. message.info('提示:<br/>因网盘限制,请保存到自己网盘后再去下载哦~');
  3102. await base.sleep(500);
  3103. document.querySelector('.tools-share-save-hb').click();
  3104. });
  3105. doc.on('click', '.listener-link-api.browser', async function (e) {
  3106. e.preventDefault();
  3107. let dataset = e.currentTarget.dataset;
  3108. let href = dataset.link;
  3109. $('#downloadIframe').attr('src', href);
  3110. });
  3111. doc.on('click', '.listener-link-api.blob', async function (e) {
  3112. e.preventDefault();
  3113.  
  3114. const o = _factory(e);
  3115. const $width = o.item.find('.pl-progress-inner');
  3116. const $text = o.item.find('.pl-progress-inner-text');
  3117. const filename = o.link[0].dataset.filename;
  3118. const index = o.link[0].dataset.index;
  3119. const size = Number(o.link[0].dataset.size) || 0;
  3120.  
  3121. base._resetData(index);
  3122. base.get(o.link[0].dataset.link, { "User-Agent": config.$baidu.api.ua.downloadLink }, 'blob', { filename, index });
  3123.  
  3124. let startTime = Date.now();
  3125. let prevLoaded = 0;
  3126. let prevTime = startTime;
  3127.  
  3128. ins[index] = setInterval(async function () {
  3129. const prog = +progress[index] || 0;
  3130. const isIDM = !!idm[index];
  3131.  
  3132. if (isIDM) {
  3133. // IDM 捕获处理逻辑
  3134. o.tip.hide();
  3135. o.progress.hide();
  3136. o.copy.show();
  3137. o.directLink.show();
  3138. o.link
  3139. .text('链接已被IDM捕获~请查看IDM下载窗口哦!')
  3140. .animate({ opacity: '0.5' }, "slow")
  3141. .show();
  3142.  
  3143. clearInterval(ins[index]);
  3144. await base.sleep(2000);
  3145. o.link.text('增强下载(基于浏览器文件流)').animate({ opacity: '1' }, "slow");
  3146. idm[index] = false;
  3147. return;
  3148. }
  3149.  
  3150. const currentTime = Date.now();
  3151. const elapsedTime = currentTime - startTime;
  3152. const loaded = prog * size / 100;
  3153. const timeDiff = Math.max(currentTime - prevTime, 1); // 避免除零
  3154. const speed = ((loaded - prevLoaded) / (timeDiff / 1000)) || 0;
  3155.  
  3156. // 计算剩余时间(保护除零)
  3157. const totalProgress = Math.max(prog / 100, 0.01);
  3158. const totalElapsedSeconds = elapsedTime / 1000;
  3159. const estTotalTime = totalElapsedSeconds / totalProgress;
  3160. const remainingTime = estTotalTime - totalElapsedSeconds;
  3161.  
  3162. // 更新界面状态
  3163. o.link.hide();
  3164. o.directLink.hide();
  3165. o.tip.hide();
  3166. o.stop.show();
  3167. o.copy.hide();
  3168. o.progress.show();
  3169.  
  3170. // 更新进度条
  3171. $width.css('width', `${prog}%`);
  3172. $text.text(`${prog.toFixed(1)}% | 速度:${base.sizeFormat(speed)} | 剩余:${base.rtimeFormat(remainingTime)}`);
  3173.  
  3174. // 更新历史值
  3175. prevLoaded = loaded;
  3176. prevTime = currentTime;
  3177.  
  3178. // 下载完成
  3179. if (prog >= 100) {
  3180. await base.sleep(1000);
  3181. clearInterval(ins[index]);
  3182. progress[index] = 0;
  3183. o.item.find('.pl-progress-stop').hide();
  3184. $text.text('下载完成~ 浏览器下载框应该弹出来了哦~');
  3185. o.back.show();
  3186. await base.sleep(3000);
  3187. o.link.text('增强下载(基于浏览器文件流)').animate({ opacity: '1' }, "slow");
  3188. }
  3189. }, 500);
  3190. });
  3191. doc.on('click', '.listener-retry', async function (e) {
  3192. let o = _factory(e);
  3193. o.tip.hide();
  3194. o.link.show();
  3195. o.directLink.show();
  3196. });
  3197. doc.on('click', '.listener-stop', async function (e) {
  3198. let o = _factory(e);
  3199. let index = o.link[0].dataset.index;
  3200. if (request[index]) {
  3201. request[index].abort();
  3202. clearInterval(ins[index]);
  3203. o.item.find('.pl-progress-inner-text').text('正在取消...');
  3204. o.item.find('.pl-progress-inner').css('width', 100 + '%');
  3205. setTimeout(function () {
  3206. o.tip.hide();
  3207. o.back.hide();
  3208. o.link.show();
  3209. o.directLink.show();
  3210. o.copy.show();
  3211. o.progress.hide();
  3212. o.stop.hide();
  3213. }, 1050)
  3214. }
  3215. });
  3216. doc.on('click', '.listener-back', async function (e) {
  3217. let o = _factory(e);
  3218. o.progress.hide();
  3219. o.tip.hide();
  3220. o.link.show();
  3221. o.directLink.show();
  3222. o.copy.show();
  3223. o.stop.hide();
  3224. o.back.hide();
  3225. });
  3226. doc.on('click', '.listener-download-all', function (e) {
  3227. $('.pl-item-link.blob').each(function () {
  3228. if ($(this).css('display') !== 'none') {
  3229. $(this).click();
  3230. }
  3231. });
  3232. $(e.target).text('下载开始,下载进度见上方按钮哦~').animate({ opacity: '0.5' }, "slow");
  3233. setTimeout(function () {
  3234. $(e.target).text('全部增强下载').animate({ opacity: '1' }, "slow");
  3235. }, 2000)
  3236. });
  3237. doc.on('click', '.listener-link-aria, .listener-copy-all', function (e) {
  3238. e.preventDefault();
  3239. if (!e.target.dataset.link) {
  3240. $(e.target).removeClass('listener-copy-all').addClass('pl-btn-danger').html(`${config.base.assistant.message}👉<a href="${config.base.assistant.link}" target="_blank" class="pl-a">点击此处安装</a>👈`);
  3241. } else {
  3242. base.setClipboard(decodeURIComponent(e.target.dataset.link));
  3243. $(e.target).text('复制成功').animate({ opacity: '0.5' }, "slow");
  3244. setTimeout(
  3245. function () {
  3246. $(e.target).text('重新复制').animate({ opacity: '1' }, "slow");
  3247. }, 2000
  3248. )
  3249. }
  3250. });
  3251. doc.on('click', '.listener-link-rpc', async function (e) {
  3252. let target = $(e.currentTarget);
  3253.  
  3254. target.find('.icon-rpc-devices').remove();
  3255. target.find('.pl-loading').remove();
  3256. target.prepend(base.createLoading());
  3257.  
  3258. let BDUSS = $baidu.getBDUSS();
  3259. let res = await base.sendLinkToRPC(e.currentTarget.dataset.link, e.currentTarget.dataset.filename, [`User-Agent: ${config.$baidu.api.ua.downloadLink}`, `Cookie: BDUSS=${BDUSS}`]);
  3260. if (res === 'success') {
  3261. $('.listener-rpc-task').show();
  3262. target.removeClass('pl-btn-danger').html('发送成功了!快去看看吧~').animate({ opacity: '0.5' }, "slow");
  3263. } else if (res === 'assistant') {
  3264. target.addClass('pl-btn-danger').html(`${config.base.assistant.message}👉<a href="${config.base.assistant.link}" target="_blank" class="pl-a">点击此处安装</a>👈`);
  3265. } else {
  3266. target.addClass('pl-btn-danger').text('发送失败,检查一下您的RPC配置信息哦!').animate({ opacity: '0.5' }, "slow");
  3267. }
  3268. });
  3269. doc.on('click', '.listener-send-rpc', function (e) {
  3270. $('.listener-link-rpc').click();
  3271. $(e.target).text('发送完成,发送结果见上方按钮哦~').animate({ opacity: '0.5' }, "slow");
  3272. });
  3273. doc.on('click', '.listener-open-setting', function () {
  3274. base.showSetting();
  3275. });
  3276. doc.on('click', '.listener-open-updatelog', function () {
  3277. base.showUpdate();
  3278. });
  3279. doc.on('click', '.listener-open-beautify', function () {
  3280. base.showBeautify();
  3281. });
  3282. doc.on('click', '.listener-rpc-task', function (e) {
  3283. e.preventDefault();
  3284. let rpc = JSON.stringify({
  3285. domain: base.getValue('setting_rpc_domain'),
  3286. port: base.getValue('setting_rpc_port'),
  3287. }), url = `${config.base.service.rpc}/?rpc=${base.encodeBase(rpc)}#${base.getValue('setting_rpc_token')}`;
  3288. GM_openInTab(url, { active: true });
  3289. });
  3290. document.documentElement.addEventListener('mouseup', function (e) {
  3291. if (e.target.nodeName === 'A' && ~e.target.className.indexOf('pl-a')) {
  3292. e.stopPropagation();
  3293. }
  3294. }, true);
  3295. },
  3296.  
  3297. greenerPage() {
  3298. page = $baidu.detectPage();
  3299. base.waitForKeyElements(".wp-s-header-user__vip-center", function (tag) {
  3300. tag.remove();
  3301. }, true);
  3302. base.waitForKeyElements(".wp-s-header-user__create-team-content", function (tag) {
  3303. tag.remove();
  3304. }, true);
  3305. base.waitForKeyElements(".app-user-vip-center-box.vip-center-type-2", function (tag) {
  3306. tag.remove();
  3307. }, true);
  3308. base.waitForKeyElements(".wp-s-header__vip-btn-tip", function (tag) {
  3309. tag.fadeOut();
  3310. }, true);
  3311. base.waitForKeyElements(".app-user-vip-center-tip", function (tag) {
  3312. tag.fadeOut();
  3313. }, true);
  3314. base.waitForKeyElements("#web-header-text-s-45", function (tag) {
  3315. tag.fadeOut();
  3316. }, true);
  3317. base.waitForKeyElements(".wp-s-header__vip-btn", function (tag) {
  3318. tag.text("会员中心")
  3319. }, true);
  3320. base.waitForKeyElements(".KQcHyA", function (tag) {
  3321. tag.text("会员中心")
  3322. }, true);
  3323. base.waitForKeyElements(".gOIbzPb", function (tag) {
  3324. tag.fadeOut();
  3325. }, true);
  3326. base.waitForKeyElements(".wp-s-header-user__create-team-title", function (tag) {
  3327. tag.fadeOut();
  3328. }, true);
  3329. base.waitForKeyElements(".web-header-ad-item", function (tag) {
  3330. tag.fadeOut();
  3331. });
  3332. base.waitForKeyElements(".wp-s-header__game-entry", function (tag) {
  3333. tag.fadeOut();
  3334. }, true)
  3335. base.waitForKeyElements(".bd-aside-ad", function (tag) {
  3336. tag.fadeOut();
  3337. }, true)
  3338. base.waitForKeyElements(".btn-img-tips", function (tag) {
  3339. tag.fadeOut();
  3340. }, true)
  3341. base.waitForKeyElements(".nd-operate-guidance", function (tag) {
  3342. tag.fadeOut();
  3343. }, true)
  3344. base.waitForKeyElements(".module-operation-content", function (tag) {
  3345. tag.fadeOut();
  3346. document.querySelector(".operate-guide-close").click();
  3347. document.querySelector(".module-canvas").click();
  3348. }, true)
  3349. base.waitForKeyElements("[class*='module-'][class*='-box']:not(.module-box), [class*='module-'][class*='-mask']", function (tag) {
  3350. tag.fadeOut();
  3351. tag.find(".close-mask").click();
  3352. }, true)
  3353. base.waitForKeyElements(".newIcon", function (tag) {
  3354. tag.fadeOut();
  3355. }, true);
  3356. base.waitForKeyElements(".u-badge__content.is-dot", function (tag) {
  3357. tag.fadeOut();
  3358. }, true);
  3359. base.waitForKeyElements(".wp-side-options.g-clearfix", function (tag) {
  3360. tag.fadeOut();
  3361. }, true);
  3362. base.waitForKeyElements(".wp-s-header-user__drop-channel", function (tag) {
  3363. tag.fadeOut();
  3364. }, true);
  3365. base.waitForKeyElements(".app-download", function (tag) {
  3366. tag.fadeOut();
  3367. }, true);
  3368. base.waitForKeyElements('.g-button[title*="手机"]', function (tag) {
  3369. tag.fadeOut();
  3370. }, true)
  3371. base.waitForKeyElements('.yike-entrance', function (tag) {
  3372. tag.remove();
  3373. }, true)
  3374. base.waitForKeyElements("a.tools__item", function (tag) {
  3375. if (tag.attr("linked")) return;
  3376. if (tag.attr('href')) {
  3377. try {
  3378. let url = new URL(tag.closest('a').attr('href'));
  3379. url.search = "";
  3380. url.hash = url.hash.replace(/\?(.*?)(#|$)/, '$2')
  3381. tag.attr('href', url.href)
  3382. } catch (e) { }
  3383. }
  3384. tag.attr("linked", true)
  3385. }, true);
  3386. base.waitForKeyElements("p.wp-s-aside-nav__main-item-text", function (tag) {
  3387. if (tag.attr("linked")) return;
  3388. if (tag.closest('a').attr('href')) {
  3389. try {
  3390. let url = new URL(tag.closest('a').attr('href'));
  3391. url.search = "";
  3392. url.hash = url.hash.replace(/\?(.*?)(#|$)/, '$2')
  3393. tag.closest('a').attr('href', url.href)
  3394. } catch (e) { }
  3395. }
  3396. if (tag.is(":contains('插件'), :contains('相册'), :contains('笔记')") && tag.closest('a').attr('target') !== "_blank") {
  3397. tag.closest('a').fadeOut();
  3398. } else {
  3399. tag.text(tag.text().replace("百度", ""));
  3400. }
  3401. tag.attr("linked", true)
  3402. }, true);
  3403. base.waitForKeyElements('dd[node-type="header-link"]', function (tag) {
  3404. tag.children().each(function () {
  3405. let tag = $(this);
  3406. if (!tag.attr("node-type")) return;
  3407. let type = tag.attr("node-type");
  3408. if (
  3409. type !== "disk-home" &&
  3410. type !== "mbox-homepage" &&
  3411. type !== "find-apps"
  3412. ) {
  3413. tag.fadeOut();
  3414. }
  3415. });
  3416. }, true);
  3417. base.waitForKeyElements(".__yunguanjia", function (tag) {
  3418. tag.html(`<div class="yunguanjia-list __yunguanjia row g-clearfix _item sel">
  3419. <span type="radio" class="radio-box _radioInput __yunguanjiaRadio">
  3420. <span class="device-name">添加我的电脑</span>
  3421. </span>
  3422. <div class="__yunguanjiaTips radio-tips" style="display: block;">
  3423. 用电脑下载并登录最新百度网盘客户端,即自动完成添加。
  3424. <a href="//pan.baidu.com/download" target="_blank">下载百度网盘客户端</a>
  3425. <br/>由 <a href="https://github.com/hmjz100/LinkSwift/" target="_blank">LinkSwift</a> 修复该选项
  3426. </div>
  3427. </div>`);
  3428. }, true)
  3429. // 美化分享页面
  3430. if (page === 'share') {
  3431. base.waitForKeyElements(`iframe[src^="/buy/ad"]`, function (tag) {
  3432. tag.fadeOut();
  3433. }, true)
  3434. base.addStyle(`${mount}-baiduShare`, 'style', `
  3435. body, .theme-white.init-new, #layoutApp {
  3436. background-color: #DCEFFE !important;
  3437. background: #DCEFFE url(https://nd-static.bdstatic.com/m-static/disk-share/widget/pageModule/init-new/image/init-bg_1708266.png) no-repeat center center;
  3438. }
  3439. #bd-main .bd-left {
  3440. background: #ffffffC0;
  3441. border-radius: 10px;
  3442. }
  3443. iframe[src="/buy/ad/home"] {
  3444. display: none !important;
  3445. }
  3446. `, `.${mount}`);
  3447. base.waitForKeyElements(`.KPDwCE`, function (tag) {
  3448. tag.css('background', 'transparent');
  3449. }, true);
  3450. base.waitForKeyElements('.share-list .KPDwCE .AuPKyz', function (tag) {
  3451. tag.css('background', 'transparent');
  3452. }, true);
  3453. base.waitForKeyElements(`#layoutMain`, function (tag) {
  3454. tag.css({ "border-radius": "24px" });
  3455. }, true)
  3456. base.waitForKeyElements(".frame-content", function (tag) {
  3457. tag.css({ "margin": "auto" });
  3458. }, true)
  3459. }
  3460. },
  3461.  
  3462. addButton() {
  3463. base.waitForKeyElements(config.$baidu.mount.home, (element) => {
  3464. page = $baidu.detectPage();
  3465. if ($(".pl-button").length > 0 || !page || page !== 'home') return;
  3466. let $button = $(`
  3467. <div class="g-dropdown-button pointer pl-button">
  3468. <div class="baidu-button g-button g-button-blue"><span class="g-button-right"><em class="icon icon-download" style="color:#fff;"></em><span class="text" style="width: 60px;">下载助手</span></span></div>
  3469. <div class="menu" style="color: ${color};border-color: ${color};width:auto;z-index:41;">
  3470. <div class="g-button-menu pl-button-mode" data-mode="api">API 下载</div>
  3471. <div class="g-button-menu pl-button-mode" data-mode="aria">Aria 下载</div>
  3472. <div class="g-button-menu pl-button-mode" data-mode="rpc">RPC 下载</div>
  3473. <div class="g-button-menu pl-button-mode" data-mode="curl">cURL 下载</div>
  3474. <div class="g-button-menu pl-button-mode" data-mode="bc">BC 下载</div>
  3475. <div class="g-button-menu pl-button-mode listener-open-setting">助手设置</div>
  3476. <div class="g-button-menu pl-button-mode listener-open-beautify">助手美化</div>
  3477. <div class="g-button-menu pl-button-mode listener-open-updatelog">更新日志</div>
  3478. </div>
  3479. </div>
  3480. `);
  3481. element.prepend($button);
  3482. })
  3483. base.waitForKeyElements(config.$baidu.mount.main, (element) => {
  3484. page = $baidu.detectPage();
  3485. if ($(".pl-button").length > 0 || !page || page !== 'main') return;
  3486. let $button = $(`
  3487. <div class="wp-s-agile-tool-bar__h-group pl-button">
  3488. <div class="wp-s-agile-tool-bar__h-action is-need-left-sep is-main">
  3489. <button type="button" class="u-button nd-file-list-toolbar-action-item u-button--primary u-button--small is-round is-has-icon pl-button baidu-button">
  3490. <i class="u-icon u-icon-download"></i>
  3491. <span>下载助手</span>
  3492. </button>
  3493. <ul class="dropdown-list nd-common-float-menu pl-dropdown-menu">
  3494. <li class="sub cursor-p pl-button-mode" data-mode="api">API 下载</li>
  3495. <li class="sub cursor-p pl-button-mode" data-mode="aria">Aria 下载</li>
  3496. <li class="sub cursor-p pl-button-mode" data-mode="rpc">RPC 下载</li>
  3497. <li class="sub cursor-p pl-button-mode" data-mode="curl">cURL 下载</li>
  3498. <li class="sub cursor-p pl-button-mode" data-mode="bc">BC 下载</li>
  3499. <li class="sub cursor-p pl-button-mode listener-open-setting">助手设置</li>
  3500. <li class="sub cursor-p pl-button-mode listener-open-beautify">助手美化</li>
  3501. <li class="sub cursor-p pl-button-mode listener-open-updatelog">更新日志</li>
  3502. </ul>
  3503. </div>
  3504. </div>`);
  3505. element.prepend($button);
  3506. })
  3507. base.waitForKeyElements(config.$baidu.mount.main, (element) => {
  3508. page = $baidu.detectPage();
  3509. if ($(".pl-button").length > 0 || !page || page !== 'youth') return;
  3510. let $button = $(`
  3511. <div class="wp-s-agile-tool-bar__h-group pl-button">
  3512. <div class="wp-s-agile-tool-bar__h-action is-need-left-sep is-main">
  3513. <button type="button" class="u-button nd-file-list-toolbar-action-item u-button--primary u-button--small is-round is-has-icon pl-button baidu-button" style="font-size:14px;font-weight:700">
  3514. <i class="u-icon u-icon-more"></i>
  3515. <span>网盘助手</span>
  3516. </button>
  3517. <ul class="dropdown-list nd-common-float-menu pl-dropdown-menu">
  3518. <li class="sub cursor-p pl-button-mode listener-open-setting">助手设置</li>
  3519. <li class="sub cursor-p pl-button-mode listener-open-beautify">助手美化</li>
  3520. <li class="sub cursor-p pl-button-mode listener-open-updatelog">更新日志</li>
  3521. </ul>
  3522. </div>
  3523. </div>`);
  3524. element.prepend($button);
  3525. })
  3526. base.waitForKeyElements(config.$baidu.mount.share, (element) => {
  3527. page = $baidu.detectPage();
  3528. if ($(".pl-button").length > 0 || !page || page !== 'share') return;
  3529. /*let $button = $(`
  3530. <div class="g-dropdown-button pointer pl-button">
  3531. <div class="baidu-button g-button g-button-blue"><span class="g-button-right"><em class="icon icon-download" style="color:#fff;"></em><span class="text" style="width: 60px;">下载助手</span></span></div>
  3532. <div class="menu" style="color: ${color};border-color: ${color};width:auto;z-index:41;">
  3533. <div class="g-button-menu pl-button-mode pl-button-save"><em class="icon icon-save-disk" title="保存到网盘"></em><span style="margin-left: 3px;">保存后下载</span></div>
  3534. <div class="g-button-menu pl-button-mode listener-open-setting">助手设置</div>
  3535. <div class="g-button-menu pl-button-mode listener-open-beautify">助手美化</div>
  3536. <div class="g-button-menu pl-button-mode listener-open-updatelog">更新日志</div>
  3537. </div>
  3538. </div>
  3539. `);*/
  3540. let $button = $(`<a class="g-button tools-share-V20-btn save_btn pl-button" style="padding:0;">
  3541. <span class="g-button-right" style="padding-left:10px">
  3542. <em class="icon icon-download" style="color:#fff;line-height:27px"></em>
  3543. <span class="text" style="width: auto;">下载助手</span>
  3544. </span>
  3545. <ul class="dropdown-list nd-common-float-menu pl-dropdown-menu">
  3546. <li class="sub cursor-p pl-button-mode pl-button-save"><em class="icon noicon-zhuancun_bai"></em>保存后下载</li>
  3547. <li class="sub cursor-p pl-button-mode listener-open-setting">助手设置</li>
  3548. <li class="sub cursor-p pl-button-mode listener-open-beautify">助手美化</li>
  3549. <li class="sub cursor-p pl-button-mode listener-open-updatelog">更新日志</li>
  3550. </ul>
  3551. </a>`)
  3552. element.after($button);
  3553. })
  3554. this.setBDUSS();
  3555. },
  3556.  
  3557. addInitButton() {
  3558. base.waitForKeyElements(config.$baidu.mount.home, (element) => {
  3559. page = $baidu.detectPage();
  3560. if ($(".pl-button-init").length > 0 || !page || page !== 'home') return;
  3561. let $button = $(`<div class="g-dropdown-button pointer pl-button-init" style="opacity:0.5"><div style="color:#fff;" class="g-button g-button-blue"><span class="g-button-right"><em class="icon icon-download" style="color:#fff;"></em><span class="text" style="width: 60px;">点我点亮</span></span></div></div>`);
  3562. $button.click(function () { base.initDialog() });
  3563. element.prepend($button);
  3564. })
  3565. base.waitForKeyElements(config.$baidu.mount.main, (element) => {
  3566. page = $baidu.detectPage();
  3567. if ($(".pl-button-init").length > 0 || !page || (page !== 'main' && page !== 'youth')) return;
  3568. let $button = $(`<div class="wp-s-agile-tool-bar__h-group pl-button-init">
  3569. <div class="wp-s-agile-tool-bar__h-action is-need-left-sep is-main">
  3570. <button type="button" class="u-button nd-file-list-toolbar-action-item u-button--primary u-button--small is-round is-has-icon pl-button baidu-button" style="font-size:14px;font-weight:700">
  3571. <i class="u-icon u-icon-download"></i>
  3572. <span>点我点亮</span>
  3573. </button>
  3574. </div>
  3575. </div>`);
  3576. $button.click(function () { base.initDialog() });
  3577. element.prepend($button);
  3578. })
  3579. base.waitForKeyElements(config.$baidu.mount.share, (element) => {
  3580. page = $baidu.detectPage();
  3581. if ($(".pl-button").length > 0 || !page || page !== 'share') return;
  3582. let $button = $(`<a class="g-button tools-share-V20-btn save_btn pl-button-init" href="javascript:;">
  3583. <span class="g-button-right">
  3584. <em class="icon icon-download" style="color:#fff;line-height:27px"></em>
  3585. <span class="text" style="width: auto;">点我点亮</span>
  3586. </span>
  3587. </a>`)
  3588. $button.click(function () { base.initDialog() });
  3589. element.after($button);
  3590. })
  3591. },
  3592.  
  3593. async getLink() {
  3594. Swal.fire({
  3595. showConfirmButton: false,
  3596. allowOutsideClick: false,
  3597. allowEscapeKey: false,
  3598. allowEnterKey: false,
  3599. title: "获取中",
  3600. html: `...`,
  3601. footer: "如果选的文件较多,请耐心等待获取完成哦!",
  3602. customClass: {
  3603. popup: 'loading-popup',
  3604. header: 'loading-header',
  3605. title: 'loading-title',
  3606. content: 'loading-content',
  3607. input: 'loading-input',
  3608. footer: 'loading-footer'
  3609. },
  3610. willOpen: function () {
  3611. Swal.showLoading();
  3612. },
  3613. ...swalDefault
  3614. });
  3615. // 获取选择的文件列表
  3616. selectList = this.getSelectedList();
  3617. let BDUSS = this.getBDUSS(), accessToken = (base.getValue('baidu_access_token') || await $baidu.getToken());
  3618.  
  3619. if (!accessToken) {
  3620. message.info('提示:<br/>稍后请在新标签页中授权助手哦~');
  3621. base.deleteValue('baidu_access_token');
  3622. setTimeout(() => {
  3623. GM_openInTab(config.$baidu.api.getAccessToken, { active: true, insert: true, setParent: true })
  3624. let attempts = 0;
  3625. let interval = setInterval(function () {
  3626. if (!!base.getValue('baidu_access_token')) {
  3627. clearInterval(interval);
  3628. accessToken = base.getValue('baidu_access_token')
  3629. }
  3630. attempts++;
  3631. if (attempts > 120) {
  3632. clearInterval(interval);
  3633. return message.error('提示:<br/>时间太长,我先撤下啦~');
  3634. }
  3635. }, 1000);
  3636. }, 3300);
  3637. return;
  3638. }
  3639.  
  3640. if (!BDUSS) {
  3641. async function getBDUSS() {
  3642. let dialog = await Swal.fire({
  3643. icon: 'info',
  3644. title: `提示`,
  3645. html: '你好呀,为了获取百度网盘文件的下载直链<br/>我们需要您安装原作者的辅助扩展<br/>来让 “下载助手” 读取您的网盘账号凭证 (BDUSS)<br/>获取到的凭证仅用于生成直链,请放心安装\\(≧▽≦*)o<br/><br/>不知道如何安装第三方扩展?<a class="pl-a" target="_blank" href="https://www.youxiaohou.com/zh-cn/crx.html ">点此查看详情</a><br/>如果给浏览器开启了“开发者模式”后频繁提示<br/>“关闭开发者模式”,请使用<a class="pl-a" target="_blank" href="https://wws.lanzoub.com/b00vgnrha ">此补丁</a>隐藏提示。<a class="pl-a" target="_blank" href="https://ooo.0x0.ooo/2022/05/04/zrNGX.png ">界面汉化</a><br/>扩展安装后请刷新本页,以应用最新更改',
  3646. showConfirmButton: true,
  3647. showDenyButton: true,
  3648. showCancelButton: true,
  3649. showCloseButton: true,
  3650. allowOutsideClick: false,
  3651. allowEscapeKey: false,
  3652. allowEnterKey: false,
  3653. confirmButtonText: '前往 Chrome(Crx搜搜)',
  3654. denyButtonText: '前往 Firefox(Crx搜搜)',
  3655. cancelButtonText: '装不了扩展,我要手动输入',
  3656. position: 'center',
  3657. ...swalDefault
  3658. });
  3659.  
  3660. if (dialog.isConfirmed) {
  3661. GM_openInTab('https://www.crxsoso.com/addon/detail/mphijdmblaalbakceeadippfkbgfgaaa ', { active: true });
  3662. }
  3663. if (dialog.isDenied) {
  3664. GM_openInTab('https://www.crxsoso.com/firefox/detail/baidunetdiskisasb ', { active: true });
  3665. }
  3666. if (dialog.isDismissed && dialog.dismiss === Swal.DismissReason.cancel) {
  3667. while (true) {
  3668. let idialog = await Swal.fire({
  3669. title: '手动输入凭证',
  3670. html: `<div class="bduss-box">
  3671. <h3>Via 浏览器获取方法</h3>
  3672. <p>打开任意百度网盘页面 →</p>
  3673. <p>点击地址栏左侧的安全图标 →</p>
  3674. <p>查看 Cookies →</p>
  3675. <p>找到 "BDUSS=" 字段 →</p>
  3676. <p>复制等于号后面的内容直到分号 (不含分号) →</p>
  3677. <p>然后粘贴到这里</p>
  3678. <hr/>
  3679. <h3>桌面端浏览器获取方法</h3>
  3680. <p>打开任意百度网盘页面 →</p>
  3681. <p>F12 打开开发者工具 →</p>
  3682. <p>转到 应用(Application) 标签 →</p>
  3683. <p>Cookies →</p>
  3684. <p>找到 "BDUSS" 字段 →</p>
  3685. <p>复制其值粘贴到这里</p>
  3686. <div class="input-box">
  3687. <input class="swal2-input init-input" id="init" type="text" style="margin-bottom:0" placeholder="输入凭证...">
  3688. </div>
  3689. </div>
  3690. <style>
  3691. .bduss-box > .input-box {
  3692. display:flex;flex-direction:column;align-items:center
  3693. }
  3694. .bduss-box > hr {
  3695. margin-top: 10px;
  3696. }
  3697. .bduss-box > hr {
  3698. border-style: inset;
  3699. border-width: 1px;
  3700. }
  3701. </style>`,
  3702. showConfirmButton: true,
  3703. showDenyButton: true,
  3704. confirmButtonText: '确认',
  3705. denyButtonText: '取消',
  3706. allowOutsideClick: false,
  3707. allowEscapeKey: false,
  3708. allowEnterKey: false,
  3709. focusConfirm: false,
  3710. ...swalDefault
  3711. });
  3712. if (idialog.isConfirmed) {
  3713. let BDUSS = $('#init').val().trim();
  3714. if (BDUSS && BDUSS.length >= 192) {
  3715. $baidu.setBDUSS(BDUSS);
  3716. return message.success('提示:<br/>凭证设置成功<br/>请再获取一次下载链接吧~');
  3717. } else {
  3718. await Swal.fire({
  3719. icon: 'error',
  3720. title: '格式错误',
  3721. text: '请输入有效的 BDUSS(通常长度 ≥ 192 位)',
  3722. confirmButtonText: '确认',
  3723. ...swalDefault
  3724. });
  3725. continue;
  3726. }
  3727. } else if (idialog.isDenied) {
  3728. return await getBDUSS();
  3729. }
  3730. }
  3731. }
  3732. }
  3733. return await getBDUSS();
  3734. }
  3735.  
  3736. if (page === 'home' || page === 'main') {
  3737. if (!selectList.length) {
  3738. return message.error('提示:<br/>先勾选要下载的文件哦~');
  3739. }
  3740.  
  3741. let cnt = 0;
  3742. let processed = selectList.filter(f => !f.isdir).length;
  3743.  
  3744. async function fetchFiles(dirs) {
  3745. let files = [];
  3746.  
  3747. for (let dir of dirs) {
  3748. doc.find('.loading-popup .loading-title').html(`文件获取中`);
  3749. let url = `${config.$baidu.api.getFiles}&dir=${encodeURIComponent(dir.path)}&access_token=${accessToken}`;
  3750. let res = await base.get(url, { "User-Agent": config.$baidu.api.ua.downloadLink });
  3751. cnt++;
  3752.  
  3753. if (res?.list?.length && (res.errno === 0 || res.errmsg === "succ")) {
  3754. let subFiles = res.list.filter(f => !f.isdir);
  3755.  
  3756. processed += subFiles.length;
  3757. doc.find('.loading-popup .swal2-html-container').html(`<div>已获取 ${processed} 个文件~</div><div>${dir.path}</div>`);
  3758.  
  3759. files = files.concat(subFiles);
  3760. if (res.list.some(f => f.isdir)) {
  3761. files = files.concat(await fetchFiles(res.list.filter(f => f.isdir)));
  3762. }
  3763. }
  3764. if (cnt >= 50) {
  3765. doc.find('.loading-popup .swal2-html-container').html(`<div>已获取 ${processed} 个文件~</div><div>休息 3 秒...</div>`);
  3766. await base.sleep(3000);
  3767. cnt = 0;
  3768. }
  3769. }
  3770. return files;
  3771. }
  3772.  
  3773. let files = selectList.filter(f => !f.isdir);
  3774.  
  3775. if (selectList.some(f => f.isdir)) {
  3776. files = files.concat(await fetchFiles(selectList.filter(f => f.isdir)));
  3777. }
  3778. if (!files.length) {
  3779. return message.error('提示:<br/>文件夹是空的哦~');
  3780. }
  3781.  
  3782. doc.find('.loading-popup .loading-title').html(`链接获取中`);
  3783. doc.find('.loading-popup .swal2-html-container').html(`<div>正在获取文件对应的下载链接~</div>`);
  3784.  
  3785. let fidList = files.map(f => f.fs_id);
  3786. let batchSize = 100;
  3787. let linkList = [];
  3788. for (let i = 0; i < fidList.length; i += batchSize) {
  3789. let url = `${config.$baidu.api.getLink}&fsids=${encodeURIComponent(JSON.stringify(fidList.slice(i, i + batchSize)))}&access_token=${accessToken}`;
  3790. let res = await base.get(url, { "User-Agent": config.$baidu.api.ua.downloadLink });
  3791.  
  3792. if (res?.list?.length && (res.errno === 0 || res.errmsg === "succ")) {
  3793. linkList = linkList.concat(res.list);
  3794. doc.find('.loading-popup .swal2-html-container').html(`<div>已获取 ${linkList.length} / ${fidList.length} 个链接~</div>`);
  3795.  
  3796. } else {
  3797. if (res?.errno) {
  3798. if (res.errno === 112) {
  3799. return message.error('提示:<br/>页面过期了,刷新重试下吧~<br/>代码:' + res.errno);
  3800. }
  3801. if (res.errno === 9019) {
  3802. base.deleteValue('baidu_access_token');
  3803. return message.error('提示:<br/>访问令牌已过期,刷新网页后再获取一次吧~<br/>代码:' + res.errno);
  3804. }
  3805.  
  3806. base.deleteValue('baidu_access_token');
  3807. return message.error('提示:<br/>获取下载链接失败,刷新网页后再试试吧~<br/>代码:' + res.errno);
  3808.  
  3809. } else {
  3810. return message.error('提示:<br/>获取下载链接失败,刷新网页后再试试吧~');
  3811. }
  3812. }
  3813. await base.sleep(1000);
  3814. }
  3815.  
  3816. if (linkList.length) {
  3817. base.showMainDialog(config.base.dom.button[mode].title, this.generateDom(linkList), config.base.dom.button[mode].footer);
  3818. } else {
  3819. return message.error('提示:<br/>获取下载链接失败,刷新网页后再试试吧~');
  3820. }
  3821. } else {
  3822. return message.error('提示:<br/>页面错误~');
  3823. }
  3824. },
  3825.  
  3826. generateDom(list) {
  3827. if (!list) {
  3828. return message.error('提示:<br/>获取下载链接失败,刷新网页后再试试吧~');
  3829. }
  3830. let content = '<div class="pl-main">';
  3831. let alinkAllText = '';
  3832. base.sortByName(list);
  3833. list.forEach((v, i) => {
  3834. if (v.isdir === 1) return;
  3835. let filename = v.server_filename || v.filename;
  3836. let size = base.sizeFormat(v.size);
  3837. if (!v?.dlink || !v?.dlink.includes("http")) {
  3838. content += `<div class="pl-item">
  3839. <div class="pl-item-name listener-tip" data-size="${size}">${filename}</div>
  3840. <div class="pl-item-tip">获取下载链接失败,刷新网页后再试试吧~</div>
  3841. </div>`;
  3842. } else {
  3843. let dlink = v.dlink + '&access_token=' + base.getValue('baidu_access_token');
  3844. if (mode === 'api') {
  3845. alinkAllText += dlink + '\r\n';
  3846. content += `<div class="pl-item">
  3847. <div class="pl-item-name listener-tip" data-size="${size}">${filename}</div>
  3848. <button class="pl-item-link listener-tip pl-btn-primary listener-link-api blob" data-title="不建议使用本功能,若文件过大下载完成后有可能不会弹出窗口,此时请换用“RPC 下载 + Mortix”的组合<br/>基于浏览器的 Blob 文件流下载文件,适用于较新的浏览器,可以在此窗口中显示下载剩余时间和下载速度,此方式下载有可能会被 IDM 捕获下载链接" data-filename="${filename}" data-size="${v.size}" data-link="${dlink}" data-index="${i}">增强下载(基于浏览器文件流)</button>
  3849. <button class="pl-item-link listener-tip pl-btn-primary pl-btn-info listener-link-api browser" data-title="不建议使用本功能,若使用后长时间没有弹出下载提示则代表请求失败,请换用“增强下载”<br/>基于浏览器直接打开链接来下载文件,适用于较为古老但支持 iframe 的浏览器,点击“直接下载”后需等待下载提示弹出才能点击下个“直接下载”,否则只会下载后者,此方式下载有可能会被 IDM 捕获下载链接" data-filename="${filename}" data-link="${dlink}">直接下载(基于浏览器链接)</button>
  3850. <button class="pl-item-copy listener-tip pl-btn-primary pl-btn-warning listener-copy-all" href="${dlink}" data-title="不建议使用本功能,在本网盘单独复制链接并粘贴下载可能会导致服务器回报 403 错误" data-filename="${filename}" data-link="${dlink}">复制链接</button>
  3851. <div class="pl-item-tip" style="display: none"></div>
  3852. <div class="pl-item-progress" style="display: none">
  3853. <div class="pl-progress">
  3854. <div class="pl-progress-outer"></div>
  3855. <div class="pl-progress-inner" style="width:5%">
  3856. <div class="pl-progress-inner-text">正在加载进度...0%</div>
  3857. </div>
  3858. </div>
  3859. <button class="pl-btn-primary pl-btn-danger pl-progress-stop listener-stop">取消下载</button>
  3860. <button class="pl-btn-primary pl-btn-info pl-progress-back listener-back" style="display: none">返回</button>
  3861. </div>
  3862. </div>`;
  3863. }
  3864. let BDUSS = this.getBDUSS();
  3865. if (mode === 'aria') {
  3866. let alink = base.convertLinkToAria(dlink, filename, `--header "User-Agent: ${config.$baidu.api.ua.downloadLink}" --header "Cookie: BDUSS=${BDUSS}"`);
  3867. if (typeof (alink) === 'object') {
  3868. content += `<div class="pl-item">
  3869. <div class="pl-item-name listener-tip" data-size="${size}">${filename}</div>
  3870. <a class="pl-item-link pl-a" target="_blank" href="${alink.link}" title="点击复制 aria2c 命令行" data-filename="${filename}" data-link="${alink.link}">${decodeURIComponent(alink.text)}<br/>复制 ${filename} 下载命令行</a>
  3871. </div>`;
  3872. } else {
  3873. alinkAllText += alink + '\r\n';
  3874. content += `<div class="pl-item">
  3875. <div class="pl-item-name listener-tip" data-size="${size}">${filename}</div>
  3876. <a class="pl-item-link pl-a listener-link-aria" href="${alink}" title="点击复制 aria2c 命令行" data-filename="${filename}" data-link="${alink}">${decodeURIComponent(alink)}<br/>复制 ${filename} 下载命令行</a>
  3877. </div>`;
  3878. }
  3879. }
  3880. if (mode === 'rpc') {
  3881. content += `<div class="pl-item">
  3882. <div class="pl-item-name listener-tip" data-size="${size}">${filename}</div>
  3883. <button class="pl-item-link listener-link-rpc pl-btn-primary pl-btn-info" data-filename="${filename}" data-link="${dlink}"><svg class="icon-rpc-devices" viewBox="-10 0 1034 1024"><g transform="matrix(1 0 0 -1 0 960)"><path fill="currentColor" d="M832 -64h-640q-53 0 -90.5 37.5t-37.5 90.5v768q0 53 37.5 90.5t90.5 37.5h640q53 0 90.5 -37.5t37.5 -90.5v-768q0 -53 -37.5 -90.5t-90.5 -37.5zM832 768q0 27 -18.5 45.5t-45.5 18.5h-512q-27 0 -45.5 -18.5t-18.5 -45.5v-320h640v320v0zM832 320h-640v-192q0 -27 18.5 -45.5t45.5 -18.5h512q27 0 45.5 18.5t18.5 45.5v192v0zM512 128q-27 0 -45.5 18.5t-18.5 45.5t18.5 45.5t45.5 18.5t45.5 -18.5t18.5 -45.5t-18.5 -45.5t-45.5 -18.5z" /></g></svg><span style="margin-left: 5px;">将 ${filename} 推送到 RPC 下载器</span></button>
  3884. </div>`;
  3885. }
  3886. if (mode === 'curl') {
  3887. let alink = base.convertLinkToCurl(dlink, filename, `-A "${config.$baidu.api.ua.downloadLink}" -b "BDUSS=${BDUSS}"`);
  3888. if (typeof (alink) === 'object') {
  3889. content += `<div class="pl-item">
  3890. <div class="pl-item-name listener-tip" data-size="${size}">${filename}</div>
  3891. <a class="pl-item-link pl-a" target="_blank" href="${alink.link}" title="点击复制 curl 命令行" data-filename="${filename}" data-link="${alink.link}">${decodeURIComponent(alink.text)}<br/>复制 ${filename} 下载命令行</a>
  3892. </div>`;
  3893. } else {
  3894. alinkAllText += alink + '\r\n';
  3895. content += `<div class="pl-item">
  3896. <div class="pl-item-name listener-tip" data-size="${size}">${filename}</div>
  3897. <a class="pl-item-link pl-a listener-link-aria" href="${alink}" title="点击复制 curl 命令行" data-filename="${filename}" data-link="${alink}">${decodeURIComponent(alink)}<br/>复制 ${filename} 下载命令行</a>
  3898. </div>`;
  3899. }
  3900. }
  3901. if (mode === 'bc') {
  3902. let alink = base.convertLinkToBC(dlink, filename, `cookie=${encodeURIComponent("BDUSS=" + BDUSS)}&user_agent=${encodeURIComponent(config.$baidu.api.ua.downloadLink)}`);
  3903. if (typeof (alink) === 'object') {
  3904. alinkAllText += decodeURIComponent(alink.text) + '\r\n';
  3905. content += `<div class="pl-item">
  3906. <div class="pl-item-name listener-tip" data-size="${size}">${filename}</div>
  3907. <a class="pl-item-link pl-a" href="${decodeURIComponent(alink.link)}" title="点击用比特彗星下载" data-filename="${filename}" data-link="${alink}">${decodeURIComponent(alink.text)}<br/>下载 ${filename}</a>
  3908. </div>`;
  3909. } else {
  3910. alinkAllText += alink + '\r\n';
  3911. content += `<div class="pl-item">
  3912. <div class="pl-item-name listener-tip" data-size="${size}">${filename}</div>
  3913. <a class="pl-item-link pl-a" href="${decodeURIComponent(alink)}" title="点击用比特彗星下载" data-filename="${filename}" data-link="${alink}">${decodeURIComponent(alink)}<br/>下载 ${filename}</a>
  3914. </div>`;
  3915. }
  3916. }
  3917. }
  3918. });
  3919.  
  3920. content += '</div>';
  3921. if (mode === 'rpc') {
  3922. content += `<div class="pl-extra">`
  3923. }
  3924. if (list.length >= 2) {
  3925. if (mode === 'api')
  3926. content += `<div class="pl-extra"><button class="pl-btn-primary listener-tip listener-download-all" data-title="不建议使用本功能,若文件过大下载完成后有可能不会弹出窗口,此时请换用“RPC 下载 + Mortix”的组合<br/>基于浏览器的 Blob 文件流下载文件,适用于较新的浏览器,可以在此窗口中显示下载剩余时间和下载速度,此方式下载有可能会被 IDM 捕获下载链接">全部增强下载</button><button class="pl-btn-primary listener-tip listener-copy-all" data-link="${alinkAllText}" data-title="不建议使用本功能,在本网盘单独复制链接并粘贴下载可能会导致服务器回报 403 错误">复制全部链接</button></div>`;
  3927. if (mode === 'aria')
  3928. content += `<div class="pl-extra"><button class="pl-btn-primary listener-copy-all" data-link="${alinkAllText}">复制全部命令行</button></div>`;
  3929. if (mode === 'rpc') {
  3930. content += `<button class="pl-btn-primary listener-send-rpc">发送全部链接</button>`;
  3931. }
  3932. if (mode === 'curl') {
  3933. content += `<div class="pl-extra"><button class="pl-btn-primary listener-copy-all" data-link="${alinkAllText}">复制全部命令行</button><button class="pl-btn-primary pl-btn-warning listener-open-setting" style="margin-left: 10px;">修改终端类型(${terminalType[base.getValue('setting_terminal_type')]})</button></div>`;
  3934. }
  3935. if (mode === 'bc') {
  3936. content += `<div class="pl-extra"><button class="pl-btn-primary listener-copy-all" data-link="${alinkAllText}">复制全部链接</button></div>`;
  3937. }
  3938. }
  3939. if (mode === 'rpc') {
  3940. let rpc = base.getValue('setting_rpc_domain') + ':' + base.getValue('setting_rpc_port') + base.getValue('setting_rpc_path');
  3941. content += `<button title="${rpc}" class="pl-btn-primary pl-btn-warning listener-open-setting" style="margin-left: 10px">修改 RPC 参数(${rpc})</button>
  3942. <button class="pl-btn-primary pl-btn-success pl-btn-opacity listener-rpc-task" style="margin-left: 10px;display: none">查看下载任务</button>
  3943. </div>`;
  3944. }
  3945. return content;
  3946. },
  3947.  
  3948. getSelectedList() {
  3949. let List, selectList
  3950. try {
  3951. List = require("system-core:context/context.js").instanceForSystem.list;
  3952. selectList = List.getSelected();
  3953. /*if (!selectList.length) {
  3954. selectList = List.getCurrentList();
  3955. }*/
  3956. return selectList;
  3957. } catch (e) { }
  3958. try {
  3959. List = unsafeWindow.document.querySelector('.wp-s-core-pan');
  3960. if (List && List.__vue__.selectedList) {
  3961. selectList = List.__vue__.selectedList;
  3962. return selectList;
  3963. }
  3964. } catch (e) { }
  3965. try {
  3966. List = unsafeWindow.document.querySelector('.file-list');
  3967. if (List && List.__vue__.allFileList) {
  3968. selectList = List.__vue__.allFileList.filter(function (item) { return !!item.selected; });
  3969. return selectList;
  3970. }
  3971. } catch (e) { }
  3972. },
  3973.  
  3974. getLogid() {
  3975. let ut = require("system-core:context/context.js").instanceForSystem.tools.baseService;
  3976. return ut.base64Encode(base.getCookie("BAIDUID"));
  3977. },
  3978.  
  3979. getShareData() {
  3980. let res = unsafeWindow.locals.dump();
  3981. shareParams.shareType = 'secret';
  3982. shareParams.sign = '';
  3983. shareParams.timestamp = '';
  3984. shareParams.bdstoken = res.bdstoken.value;
  3985. shareParams.channel = 'chunlei';
  3986. shareParams.clienttype = 0;
  3987. shareParams.web = 1;
  3988. shareParams.app_id = 250528;
  3989. shareParams.encrypt = 0;
  3990. shareParams.product = 'share';
  3991. shareParams.logid = this.getLogid();
  3992. shareParams.shareid = res.shareid.value;
  3993. shareParams.uk = res.share_uk.value;
  3994. shareParams.shareType === 'secret' && (shareParams.extra = this._getExtra());
  3995. shareParams.surl = this._getSurl();
  3996. },
  3997.  
  3998. detectPage() {
  3999. let path = location.pathname;
  4000. if (/^\/disk\/home/.test(path)) return 'home';
  4001. if (/^\/disk\/main/.test(path)) return 'main';
  4002. if (/^\/youth\/pan\/main/.test(path)) return 'youth';
  4003. if (/^\/(s|share)\//.test(path)) return 'share';
  4004. return '';
  4005. },
  4006.  
  4007. async initAuthorize() {
  4008. base.registerMenuCommand();
  4009. Swal.fire({
  4010. showConfirmButton: false,
  4011. allowOutsideClick: false,
  4012. allowEscapeKey: false,
  4013. allowEnterKey: false,
  4014. html: `请稍后`,
  4015. willOpen: function () {
  4016. Swal.showLoading();
  4017. },
  4018. ...swalDefault
  4019. });
  4020. if (config.base.num === base.getValue('setting_init_code') || config.base.license === base.getValue('setting_init_license')) {
  4021. let url = new URL(location.href);
  4022. let auth = new URL(config.$baidu.api.getAccessToken);
  4023. const allowedClientIds = [
  4024. auth.searchParams.get("client_id"),
  4025. 'L6g70tBRRIXLsY0Z3HwKqlRE', // pcstest_oauth
  4026. 'fSds3K4w43rw37tOqlQmTa2kDwaczK4U', // 小度智能词典笔专业版
  4027. 'TFwtw8uwHxpdkvVqVKdIlx1XqXUnr1zG', // 印象笔记
  4028. '9dgBV9yesuBVOXaxls7aVHbLBLqU8yyg', // WPS文档
  4029. 'l9DdBOG4RYroMscmzK5OChdaGelgd92M', // 小猴云印PC版
  4030. 'Kyr013gHQBf2immy3fQt1jZ3nZVpiGAm', // 简单打印
  4031. 'iYCeC9g08h5vuP9UqvPHKKSVrKFXGa1v', // Alist
  4032. 'IlLqBbU3GjQ0t46TRwFateTprHWl39zF', // 百度手机助手
  4033. ];
  4034. // https://openapi.baidu.com/oauth/2.0/authorize?response_type=code&client_id=&scope=basic,netdisk,mobile&display=page&redirect_uri=
  4035. if (
  4036. /openapi.baidu.com\/oauth\/2.0\/authorize/.test(location.href) &&
  4037. url.searchParams.get("response_type").includes("token") &&
  4038. url.searchParams.get("scope").includes("netdisk") &&
  4039. allowedClientIds.includes(url.searchParams.get("client_id"))
  4040. ) {
  4041. let dialog = await Swal.fire({
  4042. icon: 'info',
  4043. title: `提示`,
  4044. html: '你好呀,为了获取百度网盘文件的下载直链<br/>我们需要您的授权来使 “下载助手” 读取您的网盘文件信息<br/><br/>由于使用了别的应用ID,所以授权的应用名称会有不同<br/>获取到的数据仅用于生成直链,请放心授权ヾ(≧▽≦*)o',
  4045. showConfirmButton: true,
  4046. showDenyButton: true,
  4047. allowOutsideClick: false,
  4048. allowEscapeKey: false,
  4049. allowEnterKey: false,
  4050. confirmButtonText: '授权',
  4051. denyButtonText: '再想想',
  4052. position: 'center'
  4053. });
  4054. if (dialog.isConfirmed) {
  4055. base.waitForKeyElements("button#auth-allow", function (element) {
  4056. element[0].click();
  4057. }, true)
  4058. return;
  4059. }
  4060. if (dialog.isDenied) {
  4061. return await Swal.fire({
  4062. icon: 'question',
  4063. title: `好吧(* ̄3 ̄)╭`,
  4064. html: '那就再想一想<br/>想好了就按下 “授权” 按钮吧~',
  4065. timer: 180000,
  4066. toast: true,
  4067. timerProgressBar: true,
  4068. showConfirmButton: false,
  4069. showDenyButton: false,
  4070. position: 'bottom-end',
  4071. })
  4072. }
  4073. } else if (/openapi.baidu.com\/oauth\/2.0\/login_success/.test(location.href)) {
  4074. let int = setInterval(async function () {
  4075. if (location.href.includes('access_token') && (location.href.includes('basic+netdisk') || location.href.includes('basic,netdisk'))) {
  4076. clearInterval(int)
  4077. let token = location.href.match(/access_token=(.*?)&/)[1];
  4078. base.setValue('baidu_access_token', token);
  4079. let dialog = await Swal.fire({
  4080. icon: 'success',
  4081. title: `成功啦`,
  4082. html: '你已 成功授权/授权过 脚本读取您的网盘文件信息~<br/>等待 <span id="second">3</span> 秒之后将关闭此页面',
  4083. timer: 3000,
  4084. timerProgressBar: true,
  4085. showConfirmButton: true,
  4086. showDenyButton: false,
  4087. allowOutsideClick: false,
  4088. allowEscapeKey: false,
  4089. allowEnterKey: false,
  4090. confirmButtonText: '关闭页面',
  4091. position: 'center',
  4092. willOpen: function () {
  4093. let sec = 3.1;
  4094. setInterval(() => {
  4095. sec -= 0.1;
  4096. document.getElementById("second").innerText = sec.toFixed(1);
  4097. }, 100);
  4098. setTimeout(() => {
  4099. window.close()
  4100. }, 3100);
  4101. },
  4102. });
  4103. if (dialog.isConfirmed) {
  4104. window.close()
  4105. return;
  4106. }
  4107. } else {
  4108. clearInterval(int)
  4109. Swal.close()
  4110. }
  4111. }, 1)
  4112. } else {
  4113. Swal.close()
  4114. }
  4115. } else {
  4116. Swal.close()
  4117. }
  4118. },
  4119.  
  4120. async initPanLinker() {
  4121. base.createTip();
  4122. base.registerMenuCommand();
  4123. if (config.base.num === base.getValue('setting_init_code') || config.base.license === base.getValue('setting_init_license')) {
  4124. this.addButton();
  4125. this.addPageListener();
  4126. } else {
  4127. this.addInitButton();
  4128. }
  4129. },
  4130. };
  4131.  
  4132. /**
  4133. * 阿里云盘
  4134. * @author 油小猴
  4135. * @author hmjz100
  4136. */
  4137. let $aliyun = {
  4138. addPageListener() {
  4139. /*
  4140. 防止代码因其他原因被执行多次
  4141. 这段代码出自 Via轻插件,作者谷花泰
  4142. */
  4143. const key = encodeURIComponent('LinkSwift:阿里云盘');
  4144. if (window[key]) return;
  4145. window[key] = true;
  4146.  
  4147. function _factory(e) {
  4148. let target = $(e.target);
  4149. let item = target.parents('.pl-item');
  4150. let link = item.find('.pl-item-link.blob');
  4151. let directLink = item.find('.pl-item-link.browser');
  4152. let progress = item.find('.pl-item-progress');
  4153. let tip = item.find('.pl-item-tip');
  4154. let copy = item.find('.pl-item-copy');
  4155. let back = item.find('.pl-progress-back');
  4156. let stop = item.find('.pl-progress-stop');
  4157. return {
  4158. item, link, directLink, progress, tip, copy, back, stop, target,
  4159. };
  4160. }
  4161.  
  4162. doc.on('click', '.pl-button-save', async function (e) {
  4163. e.preventDefault();
  4164. let reactDomGrid = document.querySelector(config.$aliyun.mount.grid);
  4165. if (reactDomGrid) {
  4166. let dialog = await Swal.fire({
  4167. title: '提示',
  4168. html: '<div style="display: flex;align-items: center;justify-content: center;">请先切换到&nbsp;&nbsp;<svg class="icon" class="icon--D3kMk " viewBox="0 0 1024 1024" width="20" height="20" fill="currentColor"><use xlink:href="#PDSDrag"></use></svg>&nbsp;<b>列表视图</b>&nbsp;&nbsp;后再获取下载链接哦</div>',
  4169. icon: 'info',
  4170. showCloseButton: true,
  4171. showDenyButton: true,
  4172. confirmButtonText: '切换',
  4173. denyButtonText: '不要',
  4174. ...swalDefault
  4175. });
  4176. if (dialog.isConfirmed) {
  4177. document.querySelector(config.$aliyun.mount.switch).click();
  4178. return message.success('提示:<br/>切换为列表视图成功<br/>请再获取一次下载链接吧~');
  4179. }
  4180. return false;
  4181. }
  4182. selectList = $aliyun.getSelectedList();
  4183. if (selectList.length === 0) {
  4184. return message.error('提示:<br/>请勾选要保存到网盘的文件哦~');
  4185. }
  4186. message.info('提示:<br/>因网盘限制,请保存到自己网盘后再去下载哦~');
  4187. await base.sleep(500);
  4188. document.querySelector('[class*="btn-save--"]').click();
  4189. });
  4190.  
  4191. doc.on('click', '.pl-button-mode', async function (e) {
  4192. mode = e.target.dataset.mode;
  4193. if (!mode) return;
  4194. let reactDomGrid = document.querySelector(config.$aliyun.mount.grid);
  4195. if (reactDomGrid) {
  4196. let dialog = await Swal.fire({
  4197. title: '提示',
  4198. html: '<div style="display: flex;align-items: center;justify-content: center;">请先切换到&nbsp;&nbsp;<svg class="icon" class="icon--D3kMk " viewBox="0 0 1024 1024" width="20" height="20" fill="currentColor"><use xlink:href="#PDSDrag"></use></svg>&nbsp;<b>列表视图</b>&nbsp;&nbsp;后再获取下载链接哦</div>',
  4199. icon: 'info',
  4200. showCloseButton: true,
  4201. showDenyButton: true,
  4202. confirmButtonText: '切换',
  4203. denyButtonText: '不要',
  4204. ...swalDefault
  4205. });
  4206. if (dialog.isConfirmed) {
  4207. document.querySelector(config.$aliyun.mount.switch).click();
  4208. return message.success('提示:<br/>切换为列表视图成功<br/>请再获取一次下载链接吧~');
  4209. }
  4210. return false;
  4211. }
  4212. $aliyun.getLink();
  4213. });
  4214. doc.on('click', '.listener-link-api.browser', async function (e) {
  4215. e.preventDefault();
  4216. let dataset = e.currentTarget.dataset;
  4217. let href = dataset.link;
  4218. $('#downloadIframe').attr('src', href);
  4219. });
  4220. doc.on('click', '.listener-link-api.blob', async function (e) {
  4221. e.preventDefault();
  4222.  
  4223. const o = _factory(e);
  4224. const $width = o.item.find('.pl-progress-inner');
  4225. const $text = o.item.find('.pl-progress-inner-text');
  4226. const filename = o.link[0].dataset.filename;
  4227. const index = o.link[0].dataset.index;
  4228. const size = Number(o.link[0].dataset.size) || 0;
  4229.  
  4230. base._resetData(index);
  4231. base.get(e.currentTarget.dataset.link, { "Referer": `https://${location.host}/` }, 'blob', { filename, index });
  4232.  
  4233. let startTime = Date.now();
  4234. let prevLoaded = 0;
  4235. let prevTime = startTime;
  4236.  
  4237. ins[index] = setInterval(async function () {
  4238. const prog = +progress[index] || 0;
  4239. const currentTime = Date.now();
  4240. const elapsedTime = currentTime - startTime;
  4241. const loaded = prog * size / 100;
  4242. const timeDiff = Math.max(currentTime - prevTime, 1); // 避免除零
  4243. const speed = ((loaded - prevLoaded) / (timeDiff / 1000)) || 0;
  4244.  
  4245. // 计算剩余时间(保护除零)
  4246. const totalProgress = Math.max(prog / 100, 0.01);
  4247. const totalElapsedSeconds = elapsedTime / 1000;
  4248. const estTotalTime = totalElapsedSeconds / totalProgress;
  4249. const remainingTime = estTotalTime - totalElapsedSeconds;
  4250.  
  4251. // 更新界面状态
  4252. o.link.hide();
  4253. o.directLink.hide();
  4254. o.tip.hide();
  4255. o.stop.show();
  4256. o.copy.hide();
  4257. o.progress.show();
  4258.  
  4259. // 更新进度条
  4260. $width.css('width', `${prog}%`);
  4261. $text.text(`${prog.toFixed(1)}% | 速度:${base.sizeFormat(speed)} | 剩余:${base.rtimeFormat(remainingTime)}`);
  4262.  
  4263. // 更新历史值
  4264. prevLoaded = loaded;
  4265. prevTime = currentTime;
  4266.  
  4267. // 下载完成
  4268. if (prog >= 100) {
  4269. await base.sleep(1000);
  4270. clearInterval(ins[index]);
  4271. progress[index] = 0;
  4272. o.item.find('.pl-progress-stop').hide();
  4273. $text.text('下载完成~ 浏览器下载框应该弹出来了哦~');
  4274. o.back.show();
  4275. await base.sleep(3000);
  4276. o.link.text('增强下载(基于浏览器文件流)').animate({ opacity: '1' }, "slow");
  4277. }
  4278. }, 500);
  4279. });
  4280. doc.on('click', '.listener-retry', async function (e) {
  4281. let o = _factory(e);
  4282. o.tip.hide();
  4283. o.link.show();
  4284. o.directLink.show();
  4285. });
  4286. doc.on('click', '.listener-stop', async function (e) {
  4287. let o = _factory(e);
  4288. let index = o.link[0].dataset.index;
  4289. if (request[index]) {
  4290. request[index].abort();
  4291. clearInterval(ins[index]);
  4292. o.item.find('.pl-progress-inner-text').text('正在取消...');
  4293. o.item.find('.pl-progress-inner').css('width', 100 + '%');
  4294. setTimeout(function () {
  4295. o.tip.hide();
  4296. o.back.hide();
  4297. o.link.show();
  4298. o.directLink.show();
  4299. o.copy.show();
  4300. o.progress.hide();
  4301. o.stop.hide();
  4302. }, 1050)
  4303. }
  4304. });
  4305. doc.on('click', '.listener-back', async function (e) {
  4306. let o = _factory(e);
  4307. o.progress.hide();
  4308. o.tip.hide();
  4309. o.link.show();
  4310. o.directLink.show();
  4311. o.copy.show();
  4312. o.stop.hide();
  4313. o.back.hide();
  4314. });
  4315. doc.on('click', '.listener-copy-filename', async function (e) {
  4316. base.setClipboard(e.target.dataset.filename);
  4317. $(e.target).text('复制成功').animate({ opacity: '0.5' }, "slow");
  4318. setTimeout(function () {
  4319. $(e.target).text('重新复制').animate({ opacity: '1' }, "slow");
  4320. }, 2000)
  4321. });
  4322. doc.on('click', '.listener-link-aria, .listener-copy-all', function (e) {
  4323. e.preventDefault();
  4324. base.setClipboard(decodeURIComponent(e.target.dataset.link));
  4325. $(e.target).text('复制成功').animate({ opacity: '0.5' }, "slow");
  4326. setTimeout(function () {
  4327. $(e.target).text('重新复制').animate({ opacity: '1' }, "slow");
  4328. }, 2000)
  4329. });
  4330. doc.on('click', '.listener-link-rpc', async function (e) {
  4331. let target = $(e.currentTarget);
  4332.  
  4333. target.find('.icon-rpc-devices').remove();
  4334. target.find('.pl-loading').remove();
  4335. target.prepend(base.createLoading());
  4336.  
  4337. let res = await base.sendLinkToRPC(e.currentTarget.dataset.link, e.currentTarget.dataset.filename, [`Referer: https://${location.host}/`]);
  4338. if (res === 'success') {
  4339. $('.listener-rpc-task').show();
  4340. target.removeClass('pl-btn-danger').html('发送成功了!快去看看吧~').animate({ opacity: '0.5' }, "slow");
  4341. } else if (res === 'assistant') {
  4342. target.addClass('pl-btn-danger').html(`${config.base.assistant.message}👉<a href="${config.base.assistant.link}" target="_blank" class="pl-a">点击此处安装</a>👈`);
  4343. } else {
  4344. target.addClass('pl-btn-danger').text('发送失败,检查一下您的RPC配置信息哦!').animate({ opacity: '0.5' }, "slow");
  4345. }
  4346. });
  4347. doc.on('click', '.listener-send-rpc', function (e) {
  4348. $('.listener-link-rpc').click();
  4349. $(e.target).text('发送完成,发送结果见上方按钮哦~').animate({ opacity: '0.5' }, "slow");
  4350. });
  4351. doc.on('click', '.listener-download-all.blob', function (e) {
  4352. $('.pl-item-link.blob').each(function () {
  4353. if ($(this).css('display') !== 'none') {
  4354. $(this).click();
  4355. }
  4356. });
  4357. $(e.target).text('下载开始,下载进度见上方按钮哦~').animate({ opacity: '0.5' }, "slow");
  4358. setTimeout(function () {
  4359. $(e.target).text('全部增强下载').animate({ opacity: '1' }, "slow");
  4360. }, 2000)
  4361. });
  4362. doc.on('click', '.listener-open-setting', function () {
  4363. base.showSetting();
  4364. });
  4365. doc.on('click', '.listener-open-updatelog', function () {
  4366. base.showUpdate();
  4367. });
  4368. doc.on('click', '.listener-open-beautify', function () {
  4369. base.showBeautify();
  4370. });
  4371. doc.on('click', '.listener-rpc-task', function () {
  4372. let rpc = JSON.stringify({
  4373. domain: base.getValue('setting_rpc_domain'),
  4374. port: base.getValue('setting_rpc_port'),
  4375. }), url = `${config.base.service.rpc}/?rpc=${base.encodeBase(rpc)}#${base.getValue('setting_rpc_token')}`;
  4376. GM_openInTab(url, { active: true });
  4377. });
  4378. document.documentElement.addEventListener('mouseup', function (e) {
  4379. if (e.target.nodeName === 'A' && ~e.target.className.indexOf('pl-a')) {
  4380. e.stopPropagation();
  4381. }
  4382. }, true);
  4383. },
  4384.  
  4385. async getFileUrlByOnce(d, f) {
  4386. let authorization = `${base.getStorage('token').token_type} ${base.getStorage('token').access_token}`;
  4387. let res = await base.post(config.$aliyun.api.getLink, {
  4388. drive_id: d,
  4389. file_id: f
  4390. }, {
  4391. authorization,
  4392. "content-type": "application/json;charset=utf-8",
  4393. "referer": "https://www.aliyundrive.com/",
  4394. "x-canary": "client=windows,app=adrive,version=v6.0.0"
  4395. });
  4396. if (res.code === 'AccessTokenInvalid') {
  4397. return message.error('提示:<br/>访问令牌过期了,请刷新网页后再试');
  4398. }
  4399. if (res.url) {
  4400. return res.url;
  4401. }
  4402. return '';
  4403. },
  4404.  
  4405. greenerPage() {
  4406. base.waitForKeyElements('[class*="share-list-banner"]', function (tag) {
  4407. tag.fadeOut();
  4408. }, true);
  4409. base.waitForKeyElements('[class*="to-app"]', function (tag) {
  4410. tag.fadeOut();
  4411. }, true);
  4412. base.waitForKeyElements('[class*="btn-mobile-save"]', function (tag) {
  4413. tag.fadeOut();
  4414. }, true);
  4415. base.waitForKeyElements('div[class*="text"]', function (tag) {
  4416. if (tag[0].innerHTML.match("SVIP"))
  4417. tag.fadeOut();
  4418. }, true);
  4419. base.waitForKeyElements('[class*="SplashScreenImg--close"]', function (tag) {
  4420. tag[0].click();
  4421. }, true);
  4422. base.waitForKeyElements('[class*="container"]', function (tag) {
  4423. tag.find('[class^="icon-close"]').click();
  4424. }, true);
  4425. base.waitForKeyElements('[class*="popup_main_close"]', function (tag) {
  4426. tag[0].click();
  4427. }, true);
  4428. },
  4429. svg: `<svg class="ali-btn-icon" style="margin-right: 3px;" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path d="M853.333 938.667H170.667a85.333 85.333 0 0 1-85.334-85.334v-384A85.333 85.333 0 0 1 170.667 384H288a32 32 0 0 1 0 64H170.667a21.333 21.333 0 0 0-21.334 21.333v384a21.333 21.333 0 0 0 21.334 21.334h682.666a21.333 21.333 0 0 0 21.334-21.334v-384A21.333 21.333 0 0 0 853.333 448H736a32 32 0 0 1 0-64h117.333a85.333 85.333 0 0 1 85.334 85.333v384a85.333 85.333 0 0 1-85.334 85.334z" fill="#FFFFFF"></path><path d="M715.03 543.552a32.81 32.81 0 0 0-46.251 0L554.005 657.813v-540.48a32 32 0 0 0-64 0v539.734L375.893 543.488a32.79 32.79 0 0 0-46.229 0 32.427 32.427 0 0 0 0 46.037l169.557 168.811a32.81 32.81 0 0 0 46.251 0l169.557-168.81a32.47 32.47 0 0 0 0-45.974z" fill="#FFFFFF"></path></svg>`,
  4430. addButton() {
  4431. base.waitForKeyElements(config.$aliyun.mount.home, (element) => {
  4432. page = $aliyun.detectPage();
  4433. if ($(".pl-button").length > 0 || !page || page !== 'home') return;
  4434. let $button = $(`<div class="ali-button pl-button"><span data-role="icon" data-render-as="svg" class="icon">${$aliyun.svg}下载助手</span><ul class="pl-dropdown-menu" style="top: 30px; right: 0;"><li class="pl-dropdown-menu-item pl-button-mode" data-mode="api">API 下载</li><li class="pl-dropdown-menu-item pl-button-mode" data-mode="aria" >Aria 下载</li><li class="pl-dropdown-menu-item pl-button-mode" data-mode="rpc">RPC 下载</li><li class="pl-dropdown-menu-item pl-button-mode" data-mode="curl">cURL 下载</li><li class="pl-dropdown-menu-item pl-button-mode" data-mode="bc" >BC 下载</li><li class="pl-dropdown-menu-item pl-button-mode listener-open-setting">助手设置</li><li class="pl-dropdown-menu-item pl-button-mode listener-open-beautify">助手美化</li><li class="pl-dropdown-menu-item pl-button-mode listener-open-updatelog">更新日志</li></ul></div>`);
  4435. element.append($button);
  4436. })
  4437. base.waitForKeyElements(config.$aliyun.mount.share, (element) => {
  4438. page = $aliyun.detectPage();
  4439. if ($(".pl-button").length > 0 || !page || page !== 'share') return;
  4440. let $button = $(`<div class="ali-button pl-button"><span data-role="icon" data-render-as="svg" class="icon">${$aliyun.svg}下载助手</span><ul class="pl-dropdown-menu" style="top: 30px; right: 16px;"><li class="pl-dropdown-menu-item pl-button-mode pl-button-save">保存后下载</li><li class="pl-dropdown-menu-item pl-button-mode listener-open-setting">助手设置</li><li class="pl-dropdown-menu-item pl-button-mode listener-open-beautify">助手美化</li><li class="pl-dropdown-menu-item pl-button-mode listener-open-updatelog">更新日志</li></ul></div>`);
  4441. $button.css({ 'margin-right': '10px', "height": "36px", "width": "auto", "padding": "1px 30px" });
  4442. element.prepend($button);
  4443. })
  4444. },
  4445. addInitButton() {
  4446. let $button = $(`<div class="ali-button pl-button-init"><span data-role="icon" data-render-as="svg" class="icon">${$aliyun.svg}点我点亮</span></div>`);
  4447. $button.click(function () { base.initDialog() });
  4448. base.waitForKeyElements(config.$aliyun.mount.home, (element) => {
  4449. page = $aliyun.detectPage();
  4450. if ($(".pl-button-init").length > 0 || !page || page !== 'home') return;
  4451. $button.css({ "width": "auto" });
  4452. element.append($button);
  4453. })
  4454. base.waitForKeyElements(config.$aliyun.mount.share, (element) => {
  4455. page = $aliyun.detectPage();
  4456. if ($(".pl-button-init").length > 0 || !page || page !== 'share') return;
  4457. $button.css({ 'margin-right': '10px', "height": "36px", "padding": "1px 30px", "width": "auto" });
  4458. element.prepend($button);
  4459. })
  4460. },
  4461.  
  4462. async getLink() {
  4463. Swal.fire({
  4464. showConfirmButton: false,
  4465. allowOutsideClick: false,
  4466. allowEscapeKey: false,
  4467. allowEnterKey: false,
  4468. title: "获取中",
  4469. html: `...`,
  4470. footer: "如果选的文件较多,请耐心等待获取完成哦!",
  4471. customClass: {
  4472. popup: 'loading-popup',
  4473. header: 'loading-header',
  4474. title: 'loading-title',
  4475. content: 'loading-content',
  4476. input: 'loading-input',
  4477. footer: 'loading-footer'
  4478. },
  4479. willOpen: function () {
  4480. Swal.showLoading();
  4481. },
  4482. ...swalDefault
  4483. });
  4484. selectList = this.getSelectedList();
  4485. if (selectList.length === 0) {
  4486. return message.error('提示:<br/>请勾选要下载的文件哦~');
  4487. }
  4488. if (this.isOnlyFolder()) {
  4489. return message.error('提示:<br/>请打开文件夹后再勾选文件~');
  4490. }
  4491. if (page === 'home') {
  4492. selectList = selectList.filter(item => item.type === 'file')
  4493. let batchSize = 15;
  4494. let processed = 0;
  4495.  
  4496. doc.find('.loading-popup .loading-title').html(`链接获取中`);
  4497. doc.find('.loading-popup .swal2-html-container').html(`<div>正在获取文件对应的下载链接~</div>`);
  4498. for (let i = 0; i < selectList.length; i += batchSize) {
  4499. // 当前批次文件
  4500. let batch = selectList.slice(i, i + batchSize);
  4501.  
  4502. // 过滤掉已有 URL 的文件
  4503. let noUrlSelectList = batch.filter(v => !Boolean(v.url));
  4504. let hasUrlSelectList = batch.filter(v => Boolean(v.url));
  4505. let queue = [];
  4506.  
  4507. // 为没有 URL 的文件生成请求队列
  4508. noUrlSelectList.forEach((item) => {
  4509. queue.push(this.getFileUrlByOnce(item.driveId, item.fileId)
  4510. .then(val => {
  4511. processed++;
  4512. doc.find('.loading-popup .swal2-html-container').html(`<div>已获取 ${processed} / ${selectList.length} 个链接~</div>`);
  4513. return val;
  4514. }));
  4515. });
  4516.  
  4517. hasUrlSelectList.forEach((item) => {
  4518. processed++;
  4519. doc.find('.loading-popup .swal2-html-container').html(`<div>已获取 ${processed} / ${selectList.length} 个链接~</div>`);
  4520. });
  4521.  
  4522. // 等待本批次的请求结果
  4523. const res = await Promise.all(queue);
  4524. res.forEach((val, index) => {
  4525. noUrlSelectList[index].url = val;
  4526. });
  4527.  
  4528. // 每次处理完一个批次后,等待 1 秒
  4529. await base.sleep(1000);
  4530. }
  4531. } else {
  4532. return message.error('提示:<br/>页面错误~');
  4533. }
  4534.  
  4535. let html = this.generateDom(selectList);
  4536. base.showMainDialog(config.base.dom.button[mode].title, html, config.base.dom.button[mode].footer);
  4537. },
  4538.  
  4539. generateDom(list) {
  4540. if (!list) {
  4541. return message.error('提示:<br/>获取下载链接失败,刷新网页后再试试吧~');
  4542. }
  4543. let content = '<div class="pl-main">';
  4544. let alinkAllText = '';
  4545. list.forEach((v, i) => {
  4546. if (v.type === 'folder') return;
  4547. let filename = v.name;
  4548. let fid = v.fileId;
  4549. let did = v.driveId;
  4550. let size = base.sizeFormat(v.size);
  4551. let dlink = v.downloadUrl || v.url;
  4552. if (!dlink || !dlink.includes("http")) {
  4553. content += `<div class="pl-item">
  4554. <div class="pl-item-name listener-tip" data-size="${size}">${filename}</div>
  4555. <div class="pl-item-tip">获取下载链接失败,刷新网页后再试试吧~</div>
  4556. </div>`;
  4557. } else {
  4558. if (mode === 'api') {
  4559. alinkAllText += dlink + '\r\n';
  4560. content += `<div class="pl-item">
  4561. <div class="pl-item-name listener-tip" data-size="${size}" >${filename}</div>
  4562. <button class="pl-item-link listener-tip pl-btn-primary listener-link-api blob" data-title="不建议使用本功能,若文件过大下载完成后有可能不会弹出窗口,此时请换用“RPC 下载 + Mortix”的组合<br/>基于浏览器的 Blob 文件流下载文件,适用于较新的浏览器,可以在此窗口中显示下载剩余时间和下载速度,此方式下载不会被 IDM 捕获下载链接" data-did="${did}" data-fid="${fid}" data-filename="${filename}" data-link="${dlink}" data-size="${v.size}" data-index="${i}">增强下载(基于浏览器文件流)</button>
  4563. <button class="pl-item-link listener-tip pl-btn-primary pl-btn-info listener-link-api browser" data-title="基于浏览器直接打开链接来下载文件,适用于较为古老但支持 iframe 的浏览器,点击“直接下载”后需等待下载提示弹出才能点击下个“直接下载”,否则只会下载后者,此方式下载有可能会被 IDM 捕获下载链接" data-did="${did}" data-fid="${fid}" data-filename="${filename}" data-link="${dlink}" data-index="${i}">直接下载(基于浏览器链接)</button>
  4564. <button class="pl-item-copy listener-tip pl-btn-primary pl-btn-success listener-copy-filename" data-title="本网盘于下载高峰期时可能不会显示文件名称,这时需要手动复制文件名称到下载工具中" data-filename="${filename}">复制名称</button>
  4565. <button class="pl-item-copy listener-tip pl-btn-primary pl-btn-warning listener-copy-all" href="${dlink}" data-title="不建议使用本功能,在本网盘单独复制链接并粘贴下载可能会导致服务器回报 403 错误" data-filename="${filename}" data-link="${dlink}">复制链接</button>
  4566. <div class="pl-item-tip" style="display: none"><span><span class="pl-ext"></span></span> <span class="listener-back">返回</span></div>
  4567. <div class="pl-item-progress" style="display: none">
  4568. <div class="pl-progress">
  4569. <div class="pl-progress-outer"></div>
  4570. <div class="pl-progress-inner" style="width:5%">
  4571. <div class="pl-progress-inner-text">正在加载进度...0%</div>
  4572. </div>
  4573. </div>
  4574. <button class="pl-btn-primary pl-btn-danger pl-progress-stop listener-stop">取消下载</button>
  4575. <button class="pl-btn-primary pl-btn-info pl-progress-back listener-back" style="display: none">返回</button>
  4576. </div>
  4577. </div>`;
  4578. }
  4579. if (mode === 'aria') {
  4580. let alink = base.convertLinkToAria(dlink, filename, `--header "Referer: https://${location.host}/"`);
  4581. alinkAllText += alink + '\r\n';
  4582. content += `<div class="pl-item">
  4583. <div class="pl-item-name listener-tip" data-size="${size}">${filename}</div>
  4584. <a class="pl-item-link listener-link-aria" href="${alink}" title="点击复制 aria2c 命令行" data-filename="${filename}" data-link="${alink}">${decodeURIComponent(alink)}<br/>复制 ${filename} 下载命令行</a>
  4585. </div>`;
  4586. }
  4587. if (mode === 'rpc') {
  4588. content += `<div class="pl-item">
  4589. <div class="pl-item-name listener-tip" data-size="${size}">${filename}</div>
  4590. <button class="pl-item-link listener-link-rpc pl-btn-primary pl-btn-info" data-filename="${filename}" data-link="${dlink.replace(' ', '%20')}"><svg class="icon-rpc-devices" viewBox="-10 0 1034 1024"><g transform="matrix(1 0 0 -1 0 960)"><path fill="currentColor" d="M832 -64h-640q-53 0 -90.5 37.5t-37.5 90.5v768q0 53 37.5 90.5t90.5 37.5h640q53 0 90.5 -37.5t37.5 -90.5v-768q0 -53 -37.5 -90.5t-90.5 -37.5zM832 768q0 27 -18.5 45.5t-45.5 18.5h-512q-27 0 -45.5 -18.5t-18.5 -45.5v-320h640v320v0zM832 320h-640v-192q0 -27 18.5 -45.5t45.5 -18.5h512q27 0 45.5 18.5t18.5 45.5v192v0zM512 128q-27 0 -45.5 18.5t-18.5 45.5t18.5 45.5t45.5 18.5t45.5 -18.5t18.5 -45.5t-18.5 -45.5t-45.5 -18.5z" /></g></svg><span style="margin-left: 5px;">将 ${filename} 推送到 RPC 下载器</span></button>
  4591. </div>`;
  4592. }
  4593. if (mode === 'curl') {
  4594. let alink = base.convertLinkToCurl(dlink, filename, `&refer=${encodeURIComponent(`https://${location.host}/`)}`);
  4595. alinkAllText += alink + '\r\n';
  4596. content += `<div class="pl-item">
  4597. <div class="pl-item-name listener-tip" data-size="${size}">${filename}</div>
  4598. <a class="pl-item-link listener-link-aria" href="${alink}" title="点击复制 curl 命令行" data-filename="${filename}" data-link="${alink}">${decodeURIComponent(alink)}<br/>复制 ${filename} 下载命令行</a>
  4599. </div>`;
  4600. }
  4601. if (mode === 'bc') {
  4602. let alink = base.convertLinkToBC(dlink, filename, `-e "https://${location.host}/"`);
  4603. alinkAllText += alink + '\r\n';
  4604. content += `<div class="pl-item">
  4605. <div class="pl-item-name listener-tip" data-size="${size}">${filename}</div>
  4606. <a class="pl-item-link" href="${decodeURIComponent(alink)}" title="点击用比特彗星下载" data-filename="${filename}" data-link="${alink}">${decodeURIComponent(alink)}<br/>下载 ${filename}</a>
  4607. </div>`;
  4608. }
  4609. }
  4610. });
  4611. content += '</div>';
  4612.  
  4613. if (mode === 'rpc') {
  4614. content += `<div class="pl-extra">`
  4615. }
  4616. if (list.length >= 2) {
  4617. if (mode === 'api')
  4618. content += `<div class="pl-extra"><button class="pl-btn-primary listener-tip listener-download-all blob" data-title="不建议使用本功能,若文件过大下载完成后有可能不会弹出窗口,此时请换用“RPC 下载 + Mortix”的组合<br/>基于浏览器的 Blob 文件流下载文件,适用于较新的浏览器,可以在此窗口中显示下载剩余时间和下载速度,此方式下载不会被 IDM 捕获下载链接">全部增强下载</button><button class="pl-btn-primary listener-tip listener-copy-all" data-link="${alinkAllText}" data-title="不建议使用本功能,在本网盘单独复制链接并粘贴下载可能会导致服务器回报 403 错误">复制全部链接</button></div>`;
  4619. if (mode === 'aria')
  4620. content += `<div class="pl-extra"><button class="pl-btn-primary listener-copy-all" data-link="${alinkAllText}">复制全部命令行</button></div>`;
  4621. if (mode === 'rpc') {
  4622. content += `<button class="pl-btn-primary listener-send-rpc">发送全部链接</button>`;
  4623. }
  4624. if (mode === 'curl') {
  4625. content += `<div class="pl-extra"><button class="pl-btn-primary listener-copy-all" data-link="${alinkAllText}">复制全部命令行</button><button class="pl-btn-primary pl-btn-warning listener-open-setting" style="margin-left: 10px;">修改终端类型(${terminalType[base.getValue('setting_terminal_type')]})</button></div>`;
  4626. }
  4627. if (mode === 'bc') {
  4628. content += `<div class="pl-extra"><button class="pl-btn-primary listener-copy-all" data-link="${alinkAllText}">复制全部链接</button></div>`;
  4629. }
  4630. }
  4631. if (mode === 'rpc') {
  4632. let rpc = base.getValue('setting_rpc_domain') + ':' + base.getValue('setting_rpc_port') + base.getValue('setting_rpc_path');
  4633. content += `<button title="${rpc}" class="pl-btn-primary pl-btn-warning listener-open-setting" style="margin-left: 10px">修改 RPC 参数(${rpc})</button>
  4634. <button class="pl-btn-primary pl-btn-success pl-btn-opacity listener-rpc-task" style="margin-left: 10px;display: none">查看下载任务</button>
  4635. </div>`;
  4636. }
  4637.  
  4638. return content;
  4639. },
  4640.  
  4641. getSelectedList() {
  4642. try {
  4643. let selectedList = [];
  4644. let reactDom = document.querySelector(config.$aliyun.mount.list);
  4645. let reactObj = base.findReact(reactDom, 1);
  4646. let props = reactObj.pendingProps;
  4647. if (props) {
  4648. let fileList = props.dataSource || [];
  4649. let selectedKeys = props.selectedKeys.split(',');
  4650. fileList.forEach(function (val) {
  4651. if (selectedKeys.includes(val.fileId)) {
  4652. selectedList.push(val);
  4653. }
  4654. });
  4655. }
  4656. return selectedList;
  4657. } catch (e) {
  4658. return [];
  4659. }
  4660. },
  4661.  
  4662. detectPage() {
  4663. let path = location.pathname;
  4664. if (/^\/(drive)/.test(path)) return 'home';
  4665. if (/^\/(s|share)\//.test(path)) return 'share';
  4666. return '';
  4667. },
  4668.  
  4669. isOnlyFolder() {
  4670. for (let i = 0; i < selectList.length; i++) {
  4671. if (selectList[i].type === 'file') return false;
  4672. }
  4673. return true;
  4674. },
  4675.  
  4676. async initPanLinker() {
  4677. page = this.detectPage();
  4678. base.createTip();
  4679. base.registerMenuCommand();
  4680. if (config.base.num === base.getValue('setting_init_code') || config.base.license === base.getValue('setting_init_license')) {
  4681. this.addButton();
  4682. this.addPageListener();
  4683. } else {
  4684. this.addInitButton();
  4685. }
  4686. }
  4687. };
  4688.  
  4689. /**
  4690. * 中国移动云盘 / 和彩云
  4691. * @author 油小猴
  4692. * @author hmjz100
  4693. */
  4694. let $mcloud = {
  4695. addPageListener() {
  4696. /*
  4697. 防止代码因其他原因被执行多次
  4698. 这段代码出自 Via轻插件,作者谷花泰
  4699. */
  4700. const key = encodeURIComponent('LinkSwift:移动云盘');
  4701. if (window[key]) return;
  4702. window[key] = true;
  4703.  
  4704. function _factory(e) {
  4705. let target = $(e.target);
  4706. let item = target.parents('.pl-item');
  4707. let link = item.find('.pl-item-link.blob');
  4708. let directLink = item.find('.pl-item-link.browser');
  4709. let progress = item.find('.pl-item-progress');
  4710. let tip = item.find('.pl-item-tip');
  4711. let copy = item.find('.pl-item-copy');
  4712. let back = item.find('.pl-progress-back');
  4713. let stop = item.find('.pl-progress-stop');
  4714. return {
  4715. item, link, directLink, progress, tip, copy, back, stop, target,
  4716. };
  4717. }
  4718.  
  4719. doc.on('click', '.pl-button-mode', function (e) {
  4720. mode = e.target.dataset.mode;
  4721. if (!mode) return;
  4722. $mcloud.getLink();
  4723. });
  4724. doc.on('click', '.pl-button-save', async function (e) {
  4725. e.preventDefault();
  4726. selectList = $mcloud.getSelectedList();
  4727. if (selectList.length === 0) {
  4728. return message.error('提示:<br/>请勾选要直接下载的文件哦~');
  4729. }
  4730. if ($mcloud.isOnlyFolder()) {
  4731. return message.error('提示:<br/>请打开文件夹后再勾选文件~');
  4732. }
  4733. message.info('提示:<br/>因网盘限制,只能够通过页面直接下载哦~');
  4734. await base.sleep(500);
  4735. document.querySelector('.btn-top.btn-top_dl').click();
  4736. });
  4737. doc.on('click', '.listener-link-api.browser', async function (e) {
  4738. e.preventDefault();
  4739. let dataset = e.currentTarget.dataset;
  4740. let href = dataset.link;
  4741. $('#downloadIframe').attr('src', href);
  4742. });
  4743. doc.on('click', '.listener-link-api.blob', async function (e) {
  4744. e.preventDefault();
  4745.  
  4746. const o = _factory(e);
  4747. const $width = o.item.find('.pl-progress-inner');
  4748. const $text = o.item.find('.pl-progress-inner-text');
  4749. const filename = o.link[0].dataset.filename;
  4750. const index = o.link[0].dataset.index;
  4751. const size = Number(o.link[0].dataset.size) || 0;
  4752.  
  4753. base._resetData(index);
  4754. base.get(e.currentTarget.dataset.link, undefined, 'blob', { filename, index });
  4755.  
  4756. let startTime = Date.now();
  4757. let prevLoaded = 0;
  4758. let prevTime = startTime;
  4759.  
  4760. ins[index] = setInterval(async function () {
  4761. const prog = +progress[index] || 0;
  4762. const currentTime = Date.now();
  4763. const elapsedTime = currentTime - startTime;
  4764. const loaded = prog * size / 100;
  4765. const timeDiff = Math.max(currentTime - prevTime, 1); // 避免除零
  4766. const speed = ((loaded - prevLoaded) / (timeDiff / 1000)) || 0;
  4767.  
  4768. // 计算剩余时间(保护除零)
  4769. const totalProgress = Math.max(prog / 100, 0.01);
  4770. const totalElapsedSeconds = elapsedTime / 1000;
  4771. const estTotalTime = totalElapsedSeconds / totalProgress;
  4772. const remainingTime = estTotalTime - totalElapsedSeconds;
  4773.  
  4774. // 更新界面状态
  4775. o.link.hide();
  4776. o.directLink.hide();
  4777. o.tip.hide();
  4778. o.stop.show();
  4779. o.copy.hide();
  4780. o.progress.show();
  4781.  
  4782. // 更新进度条
  4783. $width.css('width', `${prog}%`);
  4784. $text.text(`${prog.toFixed(1)}% | 速度:${base.sizeFormat(speed)} | 剩余:${base.rtimeFormat(remainingTime)}`);
  4785.  
  4786. // 更新历史值
  4787. prevLoaded = loaded;
  4788. prevTime = currentTime;
  4789.  
  4790. // 下载完成
  4791. if (prog >= 100) {
  4792. await base.sleep(1000);
  4793. clearInterval(ins[index]);
  4794. progress[index] = 0;
  4795. o.item.find('.pl-progress-stop').hide();
  4796. $text.text('下载完成~ 浏览器下载框应该弹出来了哦~');
  4797. o.back.show();
  4798. await base.sleep(3000);
  4799. o.link.text('增强下载(基于浏览器文件流)').animate({ opacity: '1' }, "slow");
  4800. }
  4801. }, 500);
  4802. });
  4803. doc.on('click', '.listener-retry', async function (e) {
  4804. let o = _factory(e);
  4805. o.tip.hide();
  4806. o.link.show();
  4807. o.directLink.show();
  4808. });
  4809. doc.on('click', '.listener-stop', async function (e) {
  4810. let o = _factory(e);
  4811. let index = o.link[0].dataset.index;
  4812. if (request[index]) {
  4813. request[index].abort();
  4814. clearInterval(ins[index]);
  4815. o.item.find('.pl-progress-inner-text').text('正在取消...');
  4816. o.item.find('.pl-progress-inner').css('width', 100 + '%');
  4817. setTimeout(function () {
  4818. o.tip.hide();
  4819. o.back.hide();
  4820. o.link.show();
  4821. o.directLink.show();
  4822. o.copy.show();
  4823. o.progress.hide();
  4824. o.stop.hide();
  4825. }, 1050)
  4826. }
  4827. });
  4828. doc.on('click', '.listener-back', async function (e) {
  4829. let o = _factory(e);
  4830. o.progress.hide();
  4831. o.tip.hide();
  4832. o.link.show();
  4833. o.directLink.show();
  4834. o.copy.show();
  4835. o.stop.hide();
  4836. o.back.hide();
  4837. });
  4838. doc.on('click', '.listener-download-all', function (e) {
  4839. $('.pl-item-link.blob').each(function () {
  4840. if ($(this).css('display') !== 'none') {
  4841. $(this).click();
  4842. }
  4843. });
  4844. $(e.target).text('下载开始,下载进度见上方按钮哦~').animate({ opacity: '0.5' }, "slow");
  4845. setTimeout(function () {
  4846. $(e.target).text('全部增强下载').animate({ opacity: '1' }, "slow");
  4847. }, 2000)
  4848. });
  4849. doc.on('click', '.listener-link-aria, .listener-copy-all', function (e) {
  4850. e.preventDefault();
  4851. base.setClipboard(decodeURIComponent(e.target.dataset.link));
  4852. $(e.target).text('复制成功').animate({ opacity: '0.5' }, "slow");
  4853. setTimeout(function () {
  4854. $(e.target).text('重新复制').animate({ opacity: '1' }, "slow");
  4855. }, 2000)
  4856. });
  4857. doc.on('click', '.listener-link-rpc', async function (e) {
  4858. let target = $(e.currentTarget);
  4859.  
  4860. target.find('.icon-rpc-devices').remove();
  4861. target.find('.pl-loading').remove();
  4862. target.prepend(base.createLoading());
  4863.  
  4864. let res = await base.sendLinkToRPC(e.currentTarget.dataset.link, e.currentTarget.dataset.filename);
  4865. if (res === 'success') {
  4866. $('.listener-rpc-task').show();
  4867. target.removeClass('pl-btn-danger').html('发送成功了!快去看看吧~').animate({ opacity: '0.5' }, "slow");
  4868. } else if (res === 'assistant') {
  4869. target.addClass('pl-btn-danger').html(`${config.base.assistant.message}👉<a href="${config.base.assistant.link}" target="_blank" class="pl-a">点击此处安装</a>👈`);
  4870. } else {
  4871. target.addClass('pl-btn-danger').text('发送失败,检查一下您的RPC配置信息哦!').animate({ opacity: '0.5' }, "slow");
  4872. }
  4873. });
  4874. doc.on('click', '.listener-send-rpc', function (e) {
  4875. $('.listener-link-rpc').click();
  4876. $(e.target).text('发送完成,发送结果见上方按钮哦~').animate({ opacity: '0.5' }, "slow");
  4877. });
  4878. doc.on('click', '.listener-open-setting', function () {
  4879. base.showSetting();
  4880. });
  4881. doc.on('click', '.listener-open-updatelog', function () {
  4882. base.showUpdate();
  4883. });
  4884. doc.on('click', '.listener-open-beautify', function () {
  4885. base.showBeautify();
  4886. });
  4887. doc.on('click', '.listener-rpc-task', function () {
  4888. let rpc = JSON.stringify({
  4889. domain: base.getValue('setting_rpc_domain'),
  4890. port: base.getValue('setting_rpc_port'),
  4891. }), url = `${config.base.service.rpc}/?rpc=${base.encodeBase(rpc)}#${base.getValue('setting_rpc_token')}`;
  4892. GM_openInTab(url, { active: true });
  4893. });
  4894. },
  4895.  
  4896. greenerPage() {
  4897. base.waitForKeyElements(".adv_swiper_menu", function (tag) {
  4898. tag.fadeOut();
  4899. }, true);
  4900. base.waitForKeyElements(".client-bubble", function (tag) {
  4901. tag.fadeOut();
  4902. }, true);
  4903. base.waitForKeyElements(".avs-box", function (tag) {
  4904. tag.fadeOut();
  4905. }, true);
  4906. base.waitForKeyElements(".top-adv-swiper", function (tag) {
  4907. tag.fadeOut();
  4908. }, true);
  4909. base.waitForKeyElements(".client_download_icon", function (tag) {
  4910. tag.fadeOut();
  4911. }, true);
  4912. base.waitForKeyElements(".document_top_memberCenter", function (tag) {
  4913. $(tag[0]).click(function () {
  4914. Swal.fire({
  4915. html: `<iframe style="height: 700px; width: 420px; border: 0;" src="https://vip.yun.139.com/vip/"></iframe>`,
  4916. allowOutsideClick: false,
  4917. showCloseButton: true,
  4918. showConfirmButton: false,
  4919. ...swalDefault
  4920. });
  4921. });
  4922. }, true);
  4923. },
  4924.  
  4925. addButton() {
  4926. base.waitForKeyElements(config.$mcloud.mount.home, (element) => {
  4927. page = $mcloud.detectPage();
  4928. if ($(".pl-button").length > 0 || !page || page !== 'home') return;
  4929. let $button = $(`<div class="pl-button mcloud-button btn-top"><span class="mcloud-btn">下载助手</span><ul class="pl-dropdown-menu" style="top:36px;left:0;letter-spacing:normal;"><li class="pl-dropdown-menu-item pl-button-mode" data-mode="api">API 下载</li><li class="pl-dropdown-menu-item pl-button-mode" data-mode="aria" >Aria 下载</li><li class="pl-dropdown-menu-item pl-button-mode" data-mode="rpc">RPC 下载</li><li class="pl-dropdown-menu-item pl-button-mode" data-mode="curl">cURL 下载</li><li class="pl-dropdown-menu-item pl-button-mode" data-mode="bc" >BC 下载</li><li class="pl-dropdown-menu-item pl-button-mode listener-open-setting">助手设置</li><li class="pl-dropdown-menu-item pl-button-mode listener-open-beautify">助手美化</li><li class="pl-dropdown-menu-item pl-button-mode listener-open-updatelog">更新日志</li></ul></div>`);
  4930. element.prepend($button);
  4931. })
  4932. base.waitForKeyElements(config.$mcloud.mount.share, (element) => {
  4933. page = $mcloud.detectPage();
  4934. if ($(".pl-button").length > 0 || !page || page !== 'share') return;
  4935. let $button = $(`<div class="pl-button mcloud-share-button"><span class="mcloud-btn">下载助手</span><ul class="pl-dropdown-menu" style="top:36px;left:0;letter-spacing:normal;"><li class="pl-dropdown-menu-item pl-button-mode pl-button-save">直接下载</li><li class="pl-dropdown-menu-item pl-button-mode listener-open-setting">助手设置</li><li class="pl-dropdown-menu-item pl-button-mode listener-open-beautify">助手美化</li><li class="pl-dropdown-menu-item pl-button-mode listener-open-updatelog">更新日志</li></ul></div>`);
  4936. element.prepend($button);
  4937. })
  4938. },
  4939.  
  4940. addInitButton() {
  4941. let $button = $(`<div class="pl-button-init"><span class="mcloud-btn">点我点亮</span></div>`);
  4942. $button.click(function () { base.initDialog() });
  4943. base.waitForKeyElements(config.$mcloud.mount.home, (element) => {
  4944. page = $mcloud.detectPage();
  4945. if ($(".pl-button-init").length > 0 || !page || page !== 'home') return;
  4946. $button.addClass('mcloud-button');
  4947. element.prepend($button);
  4948. })
  4949. base.waitForKeyElements(config.$mcloud.mount.share, (element) => {
  4950. page = $mcloud.detectPage();
  4951. if ($(".pl-button-init").length > 0 || !page || page !== 'share') return;
  4952. $button.addClass('mcloud-share-button').css({ "cursor": "pointer" });
  4953. element.prepend($button);
  4954. })
  4955. },
  4956.  
  4957. getRandomString(len) {
  4958. len = len || 16;
  4959. let $chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678';
  4960. let maxPos = $chars.length;
  4961. let pwd = '';
  4962. for (let i = 0; i < len; i++) {
  4963. pwd += $chars.charAt(Math.floor(Math.random() * maxPos));
  4964. }
  4965. return pwd;
  4966. },
  4967.  
  4968. utob(str) {
  4969. const u = String.fromCharCode;
  4970. return str.replace(/[\uD800-\uDBFF][\uDC00-\uDFFFF]|[^\x00-\x7F]/g, function (t) {
  4971. if (t.length < 2) {
  4972. let e = t.charCodeAt(0);
  4973. return e < 128 ? t : e < 2048 ? u(192 | e >>> 6) + u(128 | 63 & e) : u(224 | e >>> 12 & 15) + u(128 | e >>> 6 & 63) + u(128 | 63 & e);
  4974. }
  4975. e = 65536 + 1024 * (t.charCodeAt(0) - 55296) + (t.charCodeAt(1) - 56320);
  4976. return u(240 | e >>> 18 & 7) + u(128 | e >>> 12 & 63) + u(128 | e >>> 6 & 63) + u(128 | 63 & e);
  4977. });
  4978. },
  4979.  
  4980. getSign(e, t, a, n) {
  4981. let r = "",
  4982. i = "";
  4983. if (t) {
  4984. let s = Object.assign({}, t);
  4985. i = JSON.stringify(s),
  4986. i = i.replace(/\s*/g, ""),
  4987. i = encodeURIComponent(i);
  4988. let c = i.split(""),
  4989. u = c.sort();
  4990. i = u.join("");
  4991. }
  4992. let A = md5(base.encodeBase(this.utob(i)));
  4993. let l = md5(a + ":" + n);
  4994. return md5(A + l).toUpperCase();
  4995. },
  4996.  
  4997. async getFileUrlByOnce(item, index) {
  4998. try {
  4999. if (item.downloadUrl) return {
  5000. index,
  5001. downloadUrl: item.downloadUrl
  5002. };
  5003.  
  5004. if (this.detectPage() === 'home') {
  5005. let body = {
  5006. fileId: item.contentID
  5007. }
  5008. let time = new Date(+new Date() + 8 * 3600 * 1000).toJSON().substr(0, 19).replace('T', ' ');
  5009. let key = this.getRandomString(16);
  5010. let sign = this.getSign(undefined, body, time, key);
  5011.  
  5012. let res = await base.post(config.$mcloud.api.getLink, body, {
  5013. 'Authorization': base.getCookie('authorization'),
  5014. 'Caller': 'web',
  5015. 'CMS-DEVICE': 'default',
  5016. 'Content-Type': "application/json;charset=UTF-8",
  5017. 'mcloud-channel': '1000101',
  5018. 'mcloud-client': '10701',
  5019. 'mcloud-sign': time + "," + key + "," + sign,
  5020. 'mcloud-version': '7.14.2',
  5021. 'Origin': 'https://yun.139.com',
  5022. 'Referer': 'https://yun.139.com/',
  5023. 'X-DeviceInfo': '||9|7.14.2|chrome|119.0.0.0|||windows 10||zh-CN|||',
  5024. 'X-Huawei-ChannelSrc': '10000034',
  5025. 'X-Inner-Ntwk': '2',
  5026. 'X-M4C-Caller': 'PC',
  5027. 'X-M4C-Src': '10002',
  5028. 'X-SvcType': '1',
  5029. 'X-Yun-Api-Version': 'v1',
  5030. 'X-Yun-App-Channel': '10000034',
  5031. 'X-Yun-Channel-Source': '10000034',
  5032. 'X-Yun-Client-Info': '||9|7.14.2|chrome|119.0.0.0|||windows 10||zh-CN|||||',
  5033. 'X-Yun-Module-Type': '100',
  5034. 'X-Yun-Svc-Type': '1'
  5035. });
  5036. if (res.success) {
  5037. return {
  5038. index,
  5039. downloadUrl: res.data.url
  5040. };
  5041. } else {
  5042. return {
  5043. index,
  5044. downloadUrl: '获取下载地址失败,刷新后再试试吧~'
  5045. };
  5046. }
  5047. }
  5048. if (this.detectPage() === 'share') {
  5049. let vueDom = document.querySelector(".main_file_list").__vue__;
  5050.  
  5051. let res = await base.post(config.$mcloud.api.getShareLink, `linkId=${vueDom.linkID}&contentIds=${item.path}&catalogIds=`, {
  5052. 'Content-Type': 'application/x-www-form-urlencoded',
  5053. });
  5054. if (res.code === 0) {
  5055. return {
  5056. index,
  5057. downloadUrl: res.data.redrUrl
  5058. };
  5059. } else {
  5060. return {
  5061. index,
  5062. downloadUrl: '获取下载地址失败,刷新后再试试吧~'
  5063. };
  5064. }
  5065. }
  5066. } catch (e) {
  5067. return {
  5068. index,
  5069. downloadUrl: '获取下载地址失败,刷新后再试试吧~'
  5070. };
  5071. }
  5072. },
  5073.  
  5074. async getLink() {
  5075. Swal.fire({
  5076. showConfirmButton: false,
  5077. allowOutsideClick: false,
  5078. allowEscapeKey: false,
  5079. allowEnterKey: false,
  5080. title: "获取中",
  5081. html: `...`,
  5082. footer: "如果选的文件较多,请耐心等待获取完成哦!",
  5083. customClass: {
  5084. popup: 'loading-popup',
  5085. header: 'loading-header',
  5086. title: 'loading-title',
  5087. content: 'loading-content',
  5088. input: 'loading-input',
  5089. footer: 'loading-footer'
  5090. },
  5091. willOpen: function () {
  5092. Swal.showLoading();
  5093. },
  5094. ...swalDefault
  5095. });
  5096. selectList = this.getSelectedList();
  5097. if (selectList.length === 0) {
  5098. return message.error('提示:<br/>请勾选要下载的文件哦~');
  5099. }
  5100. if (this.isOnlyFolder()) {
  5101. return message.error('提示:<br/>请打开文件夹后再勾选文件~');
  5102. }
  5103.  
  5104. if (page === 'home') {
  5105. selectList = selectList.filter(item => item.contentID && item.contentName && item.contentSuffix);
  5106. let batchSize = 15;
  5107. let processed = 0;
  5108. doc.find('.loading-popup .loading-title').html(`链接获取中`);
  5109. doc.find('.loading-popup .swal2-html-container').html(`<div>正在获取文件对应的下载链接~</div>`);
  5110. for (let i = 0; i < selectList.length; i += batchSize) {
  5111. let batch = selectList.slice(i, i + batchSize);
  5112. let queue = [];
  5113.  
  5114. batch.forEach((item, localIndex) => {
  5115. let globalIndex = i + localIndex;
  5116. queue.push(this.getFileUrlByOnce(item, globalIndex)
  5117. .then(val => {
  5118. processed++;
  5119. doc.find('.loading-popup .swal2-html-container').html(`<div>已获取 ${processed} / ${selectList.length} 个链接~</div>`);
  5120. return val;
  5121. }));
  5122. });
  5123.  
  5124. let res = await Promise.all(queue);
  5125. res.forEach(val => {
  5126. selectList[val.index].downloadUrl = val.downloadUrl;
  5127. });
  5128.  
  5129. await base.sleep(1000);
  5130. }
  5131. } else {
  5132. return message.error('提示:<br/>页面错误~');
  5133. }
  5134.  
  5135. let html = this.generateDom(selectList);
  5136. base.showMainDialog(config.base.dom.button[mode].title, html, config.base.dom.button[mode].footer);
  5137. },
  5138.  
  5139. generateDom(list) {
  5140. if (!list) {
  5141. return message.error('提示:<br/>获取下载链接失败,刷新网页后再试试吧~');
  5142. }
  5143. let content = '<div class="pl-main">';
  5144. let alinkAllText = '';
  5145. list.forEach((v, i) => {
  5146. if (v.dirEtag || v.caName) return;
  5147. let filename = v.contentName || v.coName;
  5148. let size = base.sizeFormat(v.contentSize || v.coSize);
  5149. let dlink = v.downloadUrl;
  5150. if (!dlink || !dlink.includes("http")) {
  5151. content += `<div class="pl-item">
  5152. <div class="pl-item-name listener-tip" data-size="${size}">${filename}</div>
  5153. <div class="pl-item-tip">获取下载链接失败,刷新网页后再试试吧~</div>
  5154. </div>`;
  5155. } else {
  5156. if (mode === 'api') {
  5157. alinkAllText += dlink + '\r\n';
  5158. content += `<div class="pl-item">
  5159. <div class="pl-item-name listener-tip" data-size="${size}">${filename}</div>
  5160. <button class="pl-item-link listener-tip pl-btn-primary pl-btn-default listener-link-api blob" data-title="不建议使用本功能,若文件过大下载完成后有可能不会弹出窗口,此时请换用“RPC 下载 + Mortix”的组合<br/>基于浏览器的 Blob 文件流下载文件,适用于较新的浏览器,可以在此窗口中显示下载剩余时间和下载速度,此方式下载不会被 IDM 捕获下载链接" data-filename="${filename}" data-size="${v.contentSize || v.coSize}" data-link="${dlink}" data-index="${i}">增强下载(基于浏览器文件流)</button>
  5161. <button class="pl-item-link listener-tip pl-btn-primary pl-btn-info listener-link-api browser" data-title="基于浏览器直接打开链接来下载文件,适用于较为古老但支持 iframe 的浏览器,点击“直接下载”后需等待下载提示弹出才能点击下个“直接下载”,否则只会下载后者,此方式下载有可能会被 IDM 捕获下载链接" data-filename="${filename}" data-link="${dlink}">直接下载(基于浏览器链接)</button>
  5162. <button class="pl-item-copy pl-btn-primary pl-btn-warning listener-copy-all" href="${dlink}" data-filename="${filename}" data-link="${dlink}">复制链接</button>
  5163. <div class="pl-item-progress" style="display: none">
  5164. <div class="pl-progress">
  5165. <div class="pl-progress-outer"></div>
  5166. <div class="pl-progress-inner" style="width:5%">
  5167. <div class="pl-progress-inner-text">正在加载进度...0%</div>
  5168. </div>
  5169. </div>
  5170. <button class="pl-btn-primary pl-btn-danger pl-progress-stop listener-stop">取消下载</button>
  5171. <button class="pl-btn-primary pl-btn-info pl-progress-back listener-back" style="display: none">返回</button>
  5172. </div>
  5173. </div>`;
  5174. }
  5175. if (mode === 'aria') {
  5176. let alink = base.convertLinkToAria(dlink, filename);
  5177. alinkAllText += alink + '\r\n';
  5178. content += `<div class="pl-item">
  5179. <div class="pl-item-name listener-tip" data-size="${size}">${filename}</div>
  5180. <a class="pl-item-link listener-link-aria" href="${alink}" title="点击复制 aria2c 命令行" data-filename="${filename}" data-link="${alink}">${decodeURIComponent(alink)}<br/>复制 ${filename} 下载命令行</a>
  5181. </div>`;
  5182. }
  5183. if (mode === 'rpc') {
  5184. content += `<div class="pl-item">
  5185. <div class="pl-item-name listener-tip" data-size="${size}">${filename}</div>
  5186. <button class="pl-item-link listener-link-rpc pl-btn-primary pl-btn-info" data-filename="${filename}" data-link="${dlink}"><svg class="icon-rpc-devices" viewBox="-10 0 1034 1024"><g transform="matrix(1 0 0 -1 0 960)"><path fill="currentColor" d="M832 -64h-640q-53 0 -90.5 37.5t-37.5 90.5v768q0 53 37.5 90.5t90.5 37.5h640q53 0 90.5 -37.5t37.5 -90.5v-768q0 -53 -37.5 -90.5t-90.5 -37.5zM832 768q0 27 -18.5 45.5t-45.5 18.5h-512q-27 0 -45.5 -18.5t-18.5 -45.5v-320h640v320v0zM832 320h-640v-192q0 -27 18.5 -45.5t45.5 -18.5h512q27 0 45.5 18.5t18.5 45.5v192v0zM512 128q-27 0 -45.5 18.5t-18.5 45.5t18.5 45.5t45.5 18.5t45.5 -18.5t18.5 -45.5t-18.5 -45.5t-45.5 -18.5z" /></g></svg><span style="margin-left: 5px;">将 ${filename} 推送到 RPC 下载器</span></button>
  5187. </div>`;
  5188. }
  5189. if (mode === 'curl') {
  5190. let alink = base.convertLinkToCurl(dlink, filename);
  5191. alinkAllText += alink + '\r\n';
  5192. content += `<div class="pl-item">
  5193. <div class="pl-item-name listener-tip" data-size="${size}">${filename}</div>
  5194. <a class="pl-item-link listener-link-aria" href="${alink}" title="点击复制 curl 命令行" data-filename="${filename}" data-link="${alink}">${decodeURIComponent(alink)}<br/>复制 ${filename} 下载命令行</a>
  5195. </div>`;
  5196. }
  5197. if (mode === 'bc') {
  5198. let alink = base.convertLinkToBC(dlink, filename);
  5199. alinkAllText += alink + '\r\n';
  5200. content += `<div class="pl-item">
  5201. <div class="pl-item-name listener-tip" data-size="${size}">${filename}</div>
  5202. <a class="pl-item-link" href="${decodeURIComponent(alink)}" title="点击用比特彗星下载" data-filename="${filename}" data-link="${alink}">${decodeURIComponent(alink)}<br/>下载 ${filename}</a>
  5203. </div>`;
  5204. }
  5205. }
  5206. });
  5207. content += '</div>';
  5208.  
  5209. if (mode === 'rpc') {
  5210. content += `<div class="pl-extra">`
  5211. }
  5212. if (list.length >= 2) {
  5213. if (mode === 'api')
  5214. content += `<div class="pl-extra"><button class="pl-btn-primary listener-tip listener-download-all blob" data-title="不建议使用本功能,若文件过大下载完成后有可能不会弹出窗口,此时请换用“RPC 下载 + Mortix”的组合<br/>基于浏览器的 Blob 文件流下载文件,适用于较新的浏览器,可以在此窗口中显示下载剩余时间和下载速度,此方式下载不会被 IDM 捕获下载链接">全部增强下载</button><button class="pl-btn-primary listener-tip listener-copy-all" data-link="${alinkAllText}" data-title="不建议使用本功能,在本网盘单独复制链接并粘贴下载可能会导致服务器回报 403 错误">复制全部链接</button></div>`;
  5215. if (mode === 'aria')
  5216. content += `<div class="pl-extra"><button class="pl-btn-primary listener-copy-all" data-link="${alinkAllText}">复制全部命令行</button></div>`;
  5217. if (mode === 'rpc') {
  5218. content += `<button class="pl-btn-primary listener-send-rpc">发送全部链接</button>`;
  5219. }
  5220. if (mode === 'curl') {
  5221. content += `<div class="pl-extra"><button class="pl-btn-primary listener-copy-all" data-link="${alinkAllText}">复制全部命令行</button><button class="pl-btn-primary pl-btn-warning listener-open-setting" style="margin-left: 10px;">修改终端类型(${terminalType[base.getValue('setting_terminal_type')]})</button></div>`;
  5222. }
  5223. if (mode === 'bc') {
  5224. content += `<div class="pl-extra"><button class="pl-btn-primary listener-copy-all" data-link="${alinkAllText}">复制全部链接</button></div>`;
  5225. }
  5226. }
  5227. if (mode === 'rpc') {
  5228. let rpc = base.getValue('setting_rpc_domain') + ':' + base.getValue('setting_rpc_port') + base.getValue('setting_rpc_path');
  5229. content += `<button title="${rpc}" class="pl-btn-primary pl-btn-warning listener-open-setting" style="margin-left: 10px">修改 RPC 参数(${rpc})</button>
  5230. <button class="pl-btn-primary pl-btn-success pl-btn-opacity listener-rpc-task" style="margin-left: 10px;display: none">查看下载任务</button>
  5231. </div>`;
  5232. }
  5233.  
  5234. return content;
  5235. },
  5236.  
  5237. getSelectedList() {
  5238. try {
  5239. return document.querySelector(".main_file_list").__vue__.selectList.map(val => val.item);
  5240. } catch (e) {
  5241. let vueDom = document.querySelector(".home-page").__vue__;
  5242. let fileList = vueDom._computedWatchers.fileList.value;
  5243. let dirList = vueDom._computedWatchers.dirList.value;
  5244. let selectedFileIndex = vueDom.selectedFile;
  5245. let selectedDirIndex = vueDom.selectedDir;
  5246. let selectFileList = fileList.filter((v, i) => {
  5247. return selectedFileIndex.includes(i);
  5248. });
  5249. let selectDirList = dirList.filter((v, i) => {
  5250. return selectedDirIndex.includes(i);
  5251. });
  5252. return [...selectFileList, ...selectDirList];
  5253. }
  5254. },
  5255.  
  5256. detectPage() {
  5257. let path = location.pathname;
  5258. if (/^\/w/.test(path)) return 'home';
  5259. if (/^\/link|shareweb/.test(path)) return 'share';
  5260. return '';
  5261. },
  5262.  
  5263. isOnlyFolder() {
  5264. for (let i = 0; i < selectList.length; i++) {
  5265. if (selectList[i].contentID || selectList[i].contentName) return false;
  5266. }
  5267. return true;
  5268. },
  5269.  
  5270. async initPanLinker() {
  5271. page = this.detectPage();
  5272. base.createTip();
  5273. base.registerMenuCommand();
  5274. if (config.base.num === base.getValue('setting_init_code') || config.base.license === base.getValue('setting_init_license')) {
  5275. this.addButton();
  5276. this.addPageListener();
  5277. } else {
  5278. this.addInitButton();
  5279. }
  5280. }
  5281. };
  5282.  
  5283. /**
  5284. * 天翼云盘
  5285. * @author 油小猴
  5286. * @author hmjz100
  5287. */
  5288. let $tcloud = {
  5289. addPageListener() {
  5290. /*
  5291. 防止代码因其他原因被执行多次
  5292. 这段代码出自 Via轻插件,作者谷花泰
  5293. */
  5294. const key = encodeURIComponent('LinkSwift:天翼云盘');
  5295. if (window[key]) return;
  5296. window[key] = true;
  5297.  
  5298. function _factory(e) {
  5299. let target = $(e.target);
  5300. let item = target.parents('.pl-item');
  5301. let link = item.find('.pl-item-link.blob');
  5302. let directLink = item.find('.pl-item-link.browser');
  5303. let progress = item.find('.pl-item-progress');
  5304. let tip = item.find('.pl-item-tip');
  5305. let copy = item.find('.pl-item-copy');
  5306. let back = item.find('.pl-progress-back');
  5307. let stop = item.find('.pl-progress-stop');
  5308. return {
  5309. item, link, directLink, progress, tip, copy, back, stop, target,
  5310. };
  5311. }
  5312.  
  5313. doc.on('click', '.pl-button-mode', function (e) {
  5314. mode = e.target.dataset.mode;
  5315. if (!mode) return;
  5316. $tcloud.getLink();
  5317. });
  5318. doc.on('click', '.listener-link-api.browser', async function (e) {
  5319. e.preventDefault();
  5320. let dataset = e.currentTarget.dataset;
  5321. let href = dataset.link;
  5322. $('#downloadIframe').attr('src', href);
  5323. });
  5324. doc.on('click', '.listener-link-api.blob', async function (e) {
  5325. e.preventDefault();
  5326.  
  5327. const o = _factory(e);
  5328. const $width = o.item.find('.pl-progress-inner');
  5329. const $text = o.item.find('.pl-progress-inner-text');
  5330. const filename = o.link[0].dataset.filename;
  5331. const index = o.link[0].dataset.index;
  5332. const size = Number(o.link[0].dataset.size) || 0;
  5333.  
  5334. base._resetData(index);
  5335. base.get(e.currentTarget.dataset.link, undefined, 'blob', { filename, index });
  5336.  
  5337. let startTime = Date.now();
  5338. let prevLoaded = 0;
  5339. let prevTime = startTime;
  5340.  
  5341. ins[index] = setInterval(async function () {
  5342. const prog = +progress[index] || 0;
  5343. const currentTime = Date.now();
  5344. const elapsedTime = currentTime - startTime;
  5345. const loaded = prog * size / 100;
  5346. const timeDiff = Math.max(currentTime - prevTime, 1); // 避免除零
  5347. const speed = ((loaded - prevLoaded) / (timeDiff / 1000)) || 0;
  5348.  
  5349. // 计算剩余时间(保护除零)
  5350. const totalProgress = Math.max(prog / 100, 0.01);
  5351. const totalElapsedSeconds = elapsedTime / 1000;
  5352. const estTotalTime = totalElapsedSeconds / totalProgress;
  5353. const remainingTime = estTotalTime - totalElapsedSeconds;
  5354.  
  5355. // 更新界面状态
  5356. o.link.hide();
  5357. o.directLink.hide();
  5358. o.tip.hide();
  5359. o.stop.show();
  5360. o.copy.hide();
  5361. o.progress.show();
  5362.  
  5363. // 更新进度条
  5364. $width.css('width', `${prog}%`);
  5365. $text.text(`${prog.toFixed(1)}% | 速度:${base.sizeFormat(speed)} | 剩余:${base.rtimeFormat(remainingTime)}`);
  5366.  
  5367. // 更新历史值
  5368. prevLoaded = loaded;
  5369. prevTime = currentTime;
  5370.  
  5371. // 下载完成
  5372. if (prog >= 100) {
  5373. await base.sleep(1000);
  5374. clearInterval(ins[index]);
  5375. progress[index] = 0;
  5376. o.item.find('.pl-progress-stop').hide();
  5377. $text.text('下载完成~ 浏览器下载框应该弹出来了哦~');
  5378. o.back.show();
  5379. await base.sleep(3000);
  5380. o.link.text('增强下载(基于浏览器文件流)').animate({ opacity: '1' }, "slow");
  5381. }
  5382. }, 500);
  5383. });
  5384. doc.on('click', '.listener-retry', async function (e) {
  5385. let o = _factory(e);
  5386. o.tip.hide();
  5387. o.link.show();
  5388. o.directLink.show();
  5389. });
  5390. doc.on('click', '.listener-stop', async function (e) {
  5391. let o = _factory(e);
  5392. let index = o.link[0].dataset.index;
  5393. if (request[index]) {
  5394. request[index].abort();
  5395. clearInterval(ins[index]);
  5396. o.item.find('.pl-progress-inner-text').text('正在取消...');
  5397. o.item.find('.pl-progress-inner').css('width', 100 + '%');
  5398. setTimeout(function () {
  5399. o.tip.hide();
  5400. o.back.hide();
  5401. o.link.show();
  5402. o.directLink.show();
  5403. o.copy.show();
  5404. o.progress.hide();
  5405. o.stop.hide();
  5406. }, 1050)
  5407. }
  5408. });
  5409. doc.on('click', '.listener-back', async function (e) {
  5410. let o = _factory(e);
  5411. o.progress.hide();
  5412. o.tip.hide();
  5413. o.link.show();
  5414. o.directLink.show();
  5415. o.copy.show();
  5416. o.stop.hide();
  5417. o.back.hide();
  5418. });
  5419. doc.on('click', '.listener-download-all', function (e) {
  5420. $('.pl-item-link.blob').each(function () {
  5421. if ($(this).css('display') !== 'none') {
  5422. $(this).click();
  5423. }
  5424. });
  5425. $(e.target).text('下载开始,下载进度见上方按钮哦~').animate({ opacity: '0.5' }, "slow");
  5426. setTimeout(function () {
  5427. $(e.target).text('全部增强下载').animate({ opacity: '1' }, "slow");
  5428. }, 2000)
  5429. });
  5430. doc.on('click', '.listener-link-aria, .listener-copy-all', function (e) {
  5431. e.preventDefault();
  5432. base.setClipboard(decodeURIComponent(e.target.dataset.link));
  5433. $(e.target).text('复制成功').animate({ opacity: '0.5' }, "slow");
  5434. setTimeout(function () {
  5435. $(e.target).text('重新复制').animate({ opacity: '1' }, "slow");
  5436. }, 2000)
  5437. });
  5438. doc.on('click', '.listener-link-rpc', async function (e) {
  5439. let target = $(e.currentTarget);
  5440.  
  5441. target.find('.icon-rpc-devices').remove();
  5442. target.find('.pl-loading').remove();
  5443. target.prepend(base.createLoading());
  5444.  
  5445. let res = await base.sendLinkToRPC(e.currentTarget.dataset.link, e.currentTarget.dataset.filename);
  5446. if (res === 'success') {
  5447. $('.listener-rpc-task').show();
  5448. target.removeClass('pl-btn-danger').html('发送成功了!快去看看吧~').animate({ opacity: '0.5' }, "slow");
  5449. } else if (res === 'assistant') {
  5450. target.addClass('pl-btn-danger').html(`${config.base.assistant.message}👉<a href="${config.base.assistant.link}" target="_blank" class="pl-a">点击此处安装</a>👈`);
  5451. } else {
  5452. target.addClass('pl-btn-danger').text('发送失败,检查一下您的RPC配置信息哦!').animate({ opacity: '0.5' }, "slow");
  5453. }
  5454. });
  5455. doc.on('click', '.listener-send-rpc', function (e) {
  5456. $('.listener-link-rpc').click();
  5457. $(e.target).text('发送完成,发送结果见上方按钮哦~').animate({ opacity: '0.5' }, "slow");
  5458. });
  5459. doc.on('click', '.listener-open-setting', function () {
  5460. base.showSetting();
  5461. });
  5462. doc.on('click', '.listener-open-updatelog', function () {
  5463. base.showUpdate();
  5464. });
  5465. doc.on('click', '.listener-open-beautify', function () {
  5466. base.showBeautify();
  5467. });
  5468. doc.on('click', '.listener-rpc-task', function () {
  5469. let rpc = JSON.stringify({
  5470. domain: base.getValue('setting_rpc_domain'),
  5471. port: base.getValue('setting_rpc_port'),
  5472. }), url = `${config.base.service.rpc}/?rpc=${base.encodeBase(rpc)}#${base.getValue('setting_rpc_token')}`;
  5473. GM_openInTab(url, { active: true });
  5474. });
  5475. },
  5476.  
  5477. greenerPage() {
  5478. base.waitForKeyElements(".advertising-mask", function (tag) {
  5479. tag.fadeOut();
  5480. }, true);
  5481. base.waitForKeyElements("a.client-download.nav-block", function (tag) {
  5482. tag.fadeOut();
  5483. }, true);
  5484. },
  5485.  
  5486. addButton() {
  5487. let $button = $(`<div class="pl-button tcloud-button">下载助手&nbsp;<i aria-label="icon: caret-down" class="anticon anticon-caret-down"><svg viewBox="0 0 1024 1024" data-icon="caret-down" width="1em" height="1em" fill="currentColor" aria-hidden="true" focusable="false" class=""><path d="M840.4 300H183.6c-19.7 0-30.7 20.8-18.5 35l328.4 380.8c9.4 10.9 27.5 10.9 37 0L858.9 335c12.2-14.2 1.2-35-18.5-35z"></path></svg></i><ul class="pl-dropdown-menu"><li class="pl-dropdown-menu-item pl-button-mode" data-mode="api">API 下载</li><li class="pl-dropdown-menu-item pl-button-mode" data-mode="aria" >Aria 下载</li><li class="pl-dropdown-menu-item pl-button-mode" data-mode="rpc">RPC 下载</li><li class="pl-dropdown-menu-item pl-button-mode" data-mode="curl">cURL 下载</li><li class="pl-dropdown-menu-item pl-button-mode" data-mode="bc" >BC 下载</li><li class="pl-dropdown-menu-item pl-button-mode listener-open-setting">助手设置</li><li class="pl-dropdown-menu-item pl-button-mode listener-open-beautify">助手美化</li><li class="pl-dropdown-menu-item pl-button-mode listener-open-updatelog">更新日志</li></ul></div>`);
  5488. $button.find(".pl-dropdown-menu").css({ 'position': 'absolute', 'left': '-1px' })
  5489. base.waitForKeyElements(config.$tcloud.mount.home, (element) => {
  5490. page = $tcloud.detectPage();
  5491. if ($(".pl-button").length > 0 || !page || page !== 'home') return;
  5492. $button.find(".pl-dropdown-menu").css({ 'top': '28px' })
  5493. element.prepend($button);
  5494. })
  5495. base.waitForKeyElements(config.$tcloud.mount.share, (element) => {
  5496. page = $tcloud.detectPage();
  5497. if ($(".pl-button").length > 0 || !page || page !== 'share') return;
  5498. $button.css({ 'height': '28px', 'border-radius': '15px' })
  5499. $button.find(".pl-dropdown-menu").css({ 'top': '25px' })
  5500. element.prepend($button);
  5501. })
  5502. },
  5503.  
  5504. addInitButton() {
  5505. let $button = $(`<div class="tcloud-button pl-button-init">点我点亮</div>`);
  5506. $button.click(function () { base.initDialog() });
  5507. base.waitForKeyElements(config.$tcloud.mount.home, (element) => {
  5508. page = $tcloud.detectPage();
  5509. if ($(".pl-button-init").length > 0 || !page || page !== 'home') return;
  5510. element.prepend($button);
  5511. })
  5512. base.waitForKeyElements(config.$tcloud.mount.share, (element) => {
  5513. page = $tcloud.detectPage();
  5514. if ($(".pl-button-init").length > 0 || !page || page !== 'share') return;
  5515. $button.css({ 'height': '28px', 'border-radius': '15px' })
  5516. element.prepend($button);
  5517. })
  5518. },
  5519.  
  5520. async getToken() {
  5521. doc.find('.loading-popup .loading-title').html(`令牌获取中`);
  5522. doc.find('.loading-popup .swal2-html-container').html(`<div>正在获取状态~</div>`);
  5523. let res = await base.getFinalUrl(config.$tcloud.api.getAccessToken, {});
  5524. let accessToken = res.match(/accessToken=(\w+)/)?.[1];
  5525. accessToken && base.setStorage('accessToken', accessToken);
  5526. doc.find('.loading-popup .loading-title').html(`令牌获取中`);
  5527. doc.find('.loading-popup .swal2-html-container').html(`<div>获取成功,令牌已缓存~</div>`);
  5528. return accessToken;
  5529. },
  5530.  
  5531. async getFileUrlByOnce(item, index, token) {
  5532. try {
  5533. if (item.downloadUrl) return {
  5534. index,
  5535. downloadUrl: item.downloadUrl
  5536. };
  5537. let time = Date.now(),
  5538. fileId = item.fileId,
  5539. o = "AccessToken=" + token + "&Timestamp=" + time + "&fileId=" + fileId,
  5540. url = config.$tcloud.api.getLink + '?fileId=' + fileId;
  5541. if (item.shareId) {
  5542. o = "AccessToken=" + token + "&Timestamp=" + time + "&dt=1&fileId=" + fileId + "&shareId=" + item.shareId;
  5543. url += '&dt=1&shareId=' + item.shareId;
  5544. }
  5545. let sign = md5(o).toString();
  5546. let res = await base.get(url, {
  5547. "accept": "application/json;charset=UTF-8",
  5548. "sign-type": 1,
  5549. "accesstoken": token,
  5550. "timestamp": time,
  5551. "signature": sign
  5552. });
  5553. if (res.res_code === 0) {
  5554. return {
  5555. index,
  5556. downloadUrl: res.fileDownloadUrl
  5557. };
  5558. } else if (res.errorCode === 'InvalidSessionKey') {
  5559. return {
  5560. index,
  5561. downloadUrl: '提示:<br/>请先登录网盘~'
  5562. };
  5563. } else if (res.res_code === 'ShareNotFoundFlatDir') {
  5564. return {
  5565. index,
  5566. downloadUrl: '提示:<br/>请[转存]文件,之后再👉前往[我的网盘]中下载哦~'
  5567. };
  5568. } else {
  5569. return {
  5570. index,
  5571. downloadUrl: '获取下载地址失败,刷新后再试试吧~' + (res.res_code ? res.res_code : "")
  5572. };
  5573. }
  5574. } catch (e) {
  5575. return {
  5576. index,
  5577. downloadUrl: '获取下载地址失败,刷新后再试试吧~'
  5578. };
  5579. }
  5580. },
  5581.  
  5582. async getLink() {
  5583. Swal.fire({
  5584. showConfirmButton: false,
  5585. allowOutsideClick: false,
  5586. allowEscapeKey: false,
  5587. allowEnterKey: false,
  5588. title: "获取中",
  5589. html: `...`,
  5590. footer: "如果选的文件较多,请耐心等待获取完成哦!",
  5591. customClass: {
  5592. popup: 'loading-popup',
  5593. header: 'loading-header',
  5594. title: 'loading-title',
  5595. content: 'loading-content',
  5596. input: 'loading-input',
  5597. footer: 'loading-footer'
  5598. },
  5599. willOpen: function () {
  5600. Swal.showLoading();
  5601. },
  5602. ...swalDefault
  5603. });
  5604. selectList = this.getSelectedList();
  5605. if (selectList.length === 0) {
  5606. return message.error('提示:<br/>请勾选要下载的文件哦~');
  5607. }
  5608. if (this.isOnlyFolder()) {
  5609. return message.error('提示:<br/>请打开文件夹后再勾选文件~');
  5610. }
  5611. selectList = selectList.filter(item => !item.isFolder)
  5612. doc.find('.loading-popup .loading-title').html(`令牌获取中`);
  5613. doc.find('.loading-popup .swal2-html-container').html(`<div>正在获取状态~</div>`);
  5614. let token = base.getStorage('accessToken') || await this.getToken();
  5615. if (!token) {
  5616. return message.error('提示:<br/>请先登录网盘~');
  5617. }
  5618. doc.find('.loading-popup .loading-title').html(`令牌获取中`);
  5619. doc.find('.loading-popup .swal2-html-container').html(`<div>获取缓存成功~</div>`);
  5620.  
  5621. let batchSize = 15;
  5622. let processed = 0;
  5623. doc.find('.loading-popup .loading-title').html(`链接获取中`);
  5624. doc.find('.loading-popup .swal2-html-container').html(`<div>正在获取文件对应的下载链接~</div>`);
  5625. for (let i = 0; i < selectList.length; i += batchSize) {
  5626. let batch = selectList.slice(i, i + batchSize);
  5627. let queue = [];
  5628.  
  5629. batch.forEach((item, localIndex) => {
  5630. let globalIndex = i + localIndex;
  5631. queue.push(this.getFileUrlByOnce(item, globalIndex, token)
  5632. .then(val => {
  5633. processed++;
  5634. doc.find('.loading-popup .swal2-html-container').html(`<div>已获取 ${processed} / ${selectList.length} 个链接~</div>`);
  5635. return val;
  5636. }));
  5637. });
  5638.  
  5639. let res = await Promise.all(queue);
  5640. res.forEach(val => {
  5641. selectList[val.index].downloadUrl = val.downloadUrl;
  5642. });
  5643.  
  5644. await base.sleep(1000);
  5645. }
  5646.  
  5647. let html = this.generateDom(selectList);
  5648. base.showMainDialog(config.base.dom.button[mode].title, html, config.base.dom.button[mode].footer);
  5649. },
  5650.  
  5651. generateDom(list) {
  5652. console.log(list)
  5653. if (!list) {
  5654. return message.error('提示:<br/>获取下载链接失败,刷新网页后再试试吧~');
  5655. }
  5656. let content = '<div class="pl-main">';
  5657. let alinkAllText = '';
  5658. list.forEach((v, i) => {
  5659. if (v.isFolder) return;
  5660. let filename = v.fileName;
  5661. let size = base.sizeFormat(v.size);
  5662. let dlink = v.downloadUrl;
  5663. if (!dlink || !dlink.includes("http")) {
  5664. content += `<div class="pl-item">
  5665. <div class="pl-item-name listener-tip" data-size="${size}">${filename}</div>
  5666. <div class="pl-item-tip">获取下载链接失败,刷新网页后再试试吧~</div>
  5667. </div>`;
  5668. } else {
  5669. if (mode === 'api') {
  5670. alinkAllText += dlink + '\r\n';
  5671. content += `<div class="pl-item">
  5672. <div class="pl-item-name listener-tip" data-size="${size}">${filename}</div>
  5673. <button class="pl-item-link listener-tip pl-btn-primary listener-link-api blob" data-title="不建议使用本功能,若文件过大下载完成后有可能不会弹出窗口,此时请换用“RPC 下载 + Mortix”的组合<br/>基于浏览器的 Blob 文件流下载文件,适用于较新的浏览器,可以在此窗口中显示下载剩余时间和下载速度,此方式下载不会被 IDM 捕获下载链接" data-filename="${filename}" data-size="${v.size}" data-link="${dlink}" data-index="${i}">增强下载(基于浏览器文件流)</button>
  5674. <button class="pl-item-link listener-tip pl-btn-primary pl-btn-info listener-link-api browser" data-title="基于浏览器直接打开链接来下载文件,适用于较为古老但支持 iframe 的浏览器,点击“直接下载”后需等待下载提示弹出才能点击下个“直接下载”,否则只会下载后者,此方式下载有可能会被 IDM 捕获下载链接" data-filename="${filename}" data-link="${dlink}">直接下载(基于浏览器链接)</button>
  5675. <button class="pl-item-copy pl-btn-primary pl-btn-warning listener-copy-all" href="${dlink}" data-filename="${filename}" data-link="${dlink}">复制链接</button>
  5676. <div class="pl-item-progress" style="display: none">
  5677. <div class="pl-progress">
  5678. <div class="pl-progress-outer"></div>
  5679. <div class="pl-progress-inner" style="width:5%">
  5680. <div class="pl-progress-inner-text">正在加载进度...0%</div>
  5681. </div>
  5682. </div>
  5683. <button class="pl-btn-primary pl-btn-danger pl-progress-stop listener-stop">取消下载</button>
  5684. <button class="pl-btn-primary pl-btn-info pl-progress-back listener-back" style="display: none">返回</button>
  5685. </div>
  5686. </div>`;
  5687. }
  5688. if (mode === 'aria') {
  5689. let alink = base.convertLinkToAria(dlink, filename);
  5690. alinkAllText += alink + '\r\n';
  5691. content += `<div class="pl-item">
  5692. <div class="pl-item-name listener-tip" data-size="${size}">${filename}</div>
  5693. <a class="pl-item-link listener-link-aria" href="${alink}" title="点击复制 aria2c 命令行" data-filename="${filename}" data-link="${alink}">${decodeURIComponent(alink)}<br/>复制 ${filename} 下载命令行</a>
  5694. </div>`;
  5695. }
  5696. if (mode === 'rpc') {
  5697. content += `<div class="pl-item">
  5698. <div class="pl-item-name listener-tip" data-size="${size}">${filename}</div>
  5699. <button class="pl-item-link listener-link-rpc pl-btn-primary pl-btn-info" data-filename="${filename}" data-link="${dlink}"><svg class="icon-rpc-devices" viewBox="-10 0 1034 1024"><g transform="matrix(1 0 0 -1 0 960)"><path fill="currentColor" d="M832 -64h-640q-53 0 -90.5 37.5t-37.5 90.5v768q0 53 37.5 90.5t90.5 37.5h640q53 0 90.5 -37.5t37.5 -90.5v-768q0 -53 -37.5 -90.5t-90.5 -37.5zM832 768q0 27 -18.5 45.5t-45.5 18.5h-512q-27 0 -45.5 -18.5t-18.5 -45.5v-320h640v320v0zM832 320h-640v-192q0 -27 18.5 -45.5t45.5 -18.5h512q27 0 45.5 18.5t18.5 45.5v192v0zM512 128q-27 0 -45.5 18.5t-18.5 45.5t18.5 45.5t45.5 18.5t45.5 -18.5t18.5 -45.5t-18.5 -45.5t-45.5 -18.5z" /></g></svg><span style="margin-left: 5px;">将 ${filename} 推送到 RPC 下载器</span></button>
  5700. </div>`;
  5701. }
  5702. if (mode === 'curl') {
  5703. let alink = base.convertLinkToCurl(dlink, filename);
  5704. alinkAllText += alink + '\r\n';
  5705. content += `<div class="pl-item">
  5706. <div class="pl-item-name listener-tip" data-size="${size}">${filename}</div>
  5707. <a class="pl-item-link listener-link-aria" href="${alink}" title="点击复制 curl 命令行" data-filename="${filename}" data-link="${alink}">${decodeURIComponent(alink)}<br/>复制 ${filename} 下载命令行</a>
  5708. </div>`;
  5709. }
  5710. if (mode === 'bc') {
  5711. let alink = base.convertLinkToBC(dlink, filename);
  5712. alinkAllText += alink + '\r\n';
  5713. content += `<div class="pl-item">
  5714. <div class="pl-item-name listener-tip" data-size="${size}">${filename}</div>
  5715. <a class="pl-item-link" href="${decodeURIComponent(alink)}" title="点击用比特彗星下载" data-filename="${filename}" data-link="${alink}">${decodeURIComponent(alink)}<br/>下载 ${filename}</a>
  5716. </div>`;
  5717. }
  5718. }
  5719. });
  5720. content += '</div>';
  5721.  
  5722. if (mode === 'rpc') {
  5723. content += `<div class="pl-extra">`
  5724. }
  5725. if (list.length >= 2) {
  5726. if (mode === 'api')
  5727. content += `<div class="pl-extra"><button class="pl-btn-primary listener-tip listener-download-all blob" data-title="不建议使用本功能,若文件过大下载完成后有可能不会弹出窗口,此时请换用“RPC 下载 + Mortix”的组合<br/>基于浏览器的 Blob 文件流下载文件,适用于较新的浏览器,可以在此窗口中显示下载剩余时间和下载速度,此方式下载不会被 IDM 捕获下载链接">全部增强下载</button><button class="pl-btn-primary listener-tip listener-copy-all" data-link="${alinkAllText}" data-title="不建议使用本功能,在本网盘单独复制链接并粘贴下载可能会导致服务器回报 403 错误">复制全部链接</button></div>`;
  5728. if (mode === 'aria')
  5729. content += `<div class="pl-extra"><button class="pl-btn-primary listener-copy-all" data-link="${alinkAllText}">复制全部命令行</button></div>`;
  5730. if (mode === 'rpc') {
  5731. content += `<button class="pl-btn-primary listener-send-rpc">发送全部链接</button>`;
  5732. }
  5733. if (mode === 'curl') {
  5734. content += `<div class="pl-extra"><button class="pl-btn-primary listener-copy-all" data-link="${alinkAllText}">复制全部命令行</button><button class="pl-btn-primary pl-btn-warning listener-open-setting" style="margin-left: 10px;">修改终端类型(${terminalType[base.getValue('setting_terminal_type')]})</button></div>`;
  5735. }
  5736. if (mode === 'bc') {
  5737. content += `<div class="pl-extra"><button class="pl-btn-primary listener-copy-all" data-link="${alinkAllText}">复制全部链接</button></div>`;
  5738. }
  5739. }
  5740. if (mode === 'rpc') {
  5741. let rpc = base.getValue('setting_rpc_domain') + ':' + base.getValue('setting_rpc_port') + base.getValue('setting_rpc_path');
  5742. content += `<button title="${rpc}" class="pl-btn-primary pl-btn-warning listener-open-setting" style="margin-left: 10px">修改 RPC 参数(${rpc})</button>
  5743. <button class="pl-btn-primary pl-btn-success pl-btn-opacity listener-rpc-task" style="margin-left: 10px;display: none">查看下载任务</button>
  5744. </div>`;
  5745. }
  5746.  
  5747. return content;
  5748. },
  5749.  
  5750. getSelectedList() {
  5751. try {
  5752. return document.querySelector(".c-file-list").__vue__.selectedList;
  5753. } catch (e) {
  5754. return [document.querySelector(".info-detail").__vue__.fileDetail];
  5755. }
  5756. },
  5757.  
  5758. detectPage() {
  5759. let path = location.pathname;
  5760. if (/^\/web\/main/.test(path)) return 'home';
  5761. if (/^\/web\/share/.test(path)) return 'share';
  5762. return '';
  5763. },
  5764.  
  5765. isOnlyFolder() {
  5766. for (let i = 0; i < selectList.length; i++) {
  5767. if (!selectList[i].isFolder) return false;
  5768. }
  5769. return true;
  5770. },
  5771.  
  5772. async initPanLinker() {
  5773. page = this.detectPage();
  5774. base.createTip();
  5775. base.registerMenuCommand();
  5776. if (config.base.num === base.getValue('setting_init_code') || config.base.license === base.getValue('setting_init_license')) {
  5777. this.addButton();
  5778. this.addPageListener();
  5779. } else {
  5780. this.addInitButton();
  5781. }
  5782. this.getToken();
  5783. }
  5784. };
  5785.  
  5786. /**
  5787. * 迅雷云盘
  5788. * @author 油小猴
  5789. * @author hmjz100
  5790. */
  5791. let $xunlei = {
  5792. addPageListener() {
  5793. /*
  5794. 防止代码因其他原因被执行多次
  5795. 这段代码出自 Via轻插件,作者谷花泰
  5796. */
  5797. const key = encodeURIComponent('LinkSwift:迅雷云盘');
  5798. if (window[key]) return;
  5799. window[key] = true;
  5800.  
  5801. function _factory(e) {
  5802. let target = $(e.target);
  5803. let item = target.parents('.pl-item');
  5804. let link = item.find('.pl-item-link.blob');
  5805. let directLink = item.find('.pl-item-link.browser');
  5806. let progress = item.find('.pl-item-progress');
  5807. let tip = item.find('.pl-item-tip');
  5808. let copy = item.find('.pl-item-copy');
  5809. let back = item.find('.pl-progress-back');
  5810. let stop = item.find('.pl-progress-stop');
  5811. return {
  5812. item, link, directLink, progress, tip, copy, back, stop, target,
  5813. };
  5814. }
  5815.  
  5816. doc.on('click', '.pl-button-mode', function (e) {
  5817. mode = e.target.dataset.mode;
  5818. if (!mode) return;
  5819. $xunlei.getLink();
  5820. });
  5821. doc.on('click', '.pl-button-save', async function (e) {
  5822. e.preventDefault();
  5823. selectList = $xunlei.getSelectedList();
  5824. if (selectList.length === 0) {
  5825. return message.error('提示:<br/>请勾选要保存到网盘的文件哦~');
  5826. }
  5827. message.info('提示:<br/>因网盘限制,请保存到自己网盘后再去下载哦~');
  5828. await base.sleep(500);
  5829. document.querySelector('.saveToCloud').click();
  5830. });
  5831. doc.on('click', '.listener-link-api.browser', async function (e) {
  5832. e.preventDefault();
  5833. let dataset = e.currentTarget.dataset;
  5834. let href = dataset.link;
  5835. $('#downloadIframe').attr('src', href);
  5836. });
  5837. doc.on('click', '.listener-link-api.blob', async function (e) {
  5838. e.preventDefault();
  5839.  
  5840. const o = _factory(e);
  5841. const $width = o.item.find('.pl-progress-inner');
  5842. const $text = o.item.find('.pl-progress-inner-text');
  5843. const filename = o.link[0].dataset.filename;
  5844. const index = o.link[0].dataset.index;
  5845. const size = Number(o.link[0].dataset.size) || 0;
  5846.  
  5847. base._resetData(index);
  5848. base.get(e.currentTarget.dataset.link, undefined, 'blob', { filename, index });
  5849.  
  5850. let startTime = Date.now();
  5851. let prevLoaded = 0;
  5852. let prevTime = startTime;
  5853.  
  5854. ins[index] = setInterval(async function () {
  5855. const prog = +progress[index] || 0;
  5856. const currentTime = Date.now();
  5857. const elapsedTime = currentTime - startTime;
  5858. const loaded = prog * size / 100;
  5859. const timeDiff = Math.max(currentTime - prevTime, 1); // 避免除零
  5860. const speed = ((loaded - prevLoaded) / (timeDiff / 1000)) || 0;
  5861.  
  5862. // 计算剩余时间(保护除零)
  5863. const totalProgress = Math.max(prog / 100, 0.01);
  5864. const totalElapsedSeconds = elapsedTime / 1000;
  5865. const estTotalTime = totalElapsedSeconds / totalProgress;
  5866. const remainingTime = estTotalTime - totalElapsedSeconds;
  5867.  
  5868. // 更新界面状态
  5869. o.link.hide();
  5870. o.directLink.hide();
  5871. o.tip.hide();
  5872. o.stop.show();
  5873. o.copy.hide();
  5874. o.progress.show();
  5875.  
  5876. // 更新进度条
  5877. $width.css('width', `${prog}%`);
  5878. $text.text(`${prog.toFixed(1)}% | 速度:${base.sizeFormat(speed)} | 剩余:${base.rtimeFormat(remainingTime)}`);
  5879.  
  5880. // 更新历史值
  5881. prevLoaded = loaded;
  5882. prevTime = currentTime;
  5883.  
  5884. // 下载完成
  5885. if (prog >= 100) {
  5886. await base.sleep(1000);
  5887. clearInterval(ins[index]);
  5888. progress[index] = 0;
  5889. o.item.find('.pl-progress-stop').hide();
  5890. $text.text('下载完成~ 浏览器下载框应该弹出来了哦~');
  5891. o.back.show();
  5892. await base.sleep(3000);
  5893. o.link.text('增强下载(基于浏览器文件流)').animate({ opacity: '1' }, "slow");
  5894. }
  5895. }, 500);
  5896. });
  5897. doc.on('click', '.listener-retry', async function (e) {
  5898. let o = _factory(e);
  5899. o.tip.hide();
  5900. o.link.show();
  5901. o.directLink.show();
  5902. });
  5903. doc.on('click', '.listener-stop', async function (e) {
  5904. let o = _factory(e);
  5905. let index = o.link[0].dataset.index;
  5906. if (request[index]) {
  5907. request[index].abort();
  5908. clearInterval(ins[index]);
  5909. o.item.find('.pl-progress-inner-text').text('正在取消...');
  5910. o.item.find('.pl-progress-inner').css('width', 100 + '%');
  5911. setTimeout(function () {
  5912. o.tip.hide();
  5913. o.back.hide();
  5914. o.link.show();
  5915. o.directLink.show();
  5916. o.copy.show();
  5917. o.progress.hide();
  5918. o.stop.hide();
  5919. }, 1050)
  5920. }
  5921. });
  5922. doc.on('click', '.listener-back', async function (e) {
  5923. let o = _factory(e);
  5924. o.progress.hide();
  5925. o.tip.hide();
  5926. o.link.show();
  5927. o.directLink.show();
  5928. o.copy.show();
  5929. o.stop.hide();
  5930. o.back.hide();
  5931. });
  5932. doc.on('click', '.listener-download-all', function (e) {
  5933. $('.pl-item-link.blob').each(function () {
  5934. if ($(this).css('display') !== 'none') {
  5935. $(this).click();
  5936. }
  5937. });
  5938. $(e.target).text('下载开始,下载进度见上方按钮哦~').animate({ opacity: '0.5' }, "slow");
  5939. setTimeout(function () {
  5940. $(e.target).text('全部增强下载').animate({ opacity: '1' }, "slow");
  5941. }, 2000)
  5942. });
  5943.  
  5944. doc.on('click', '.listener-copy-filename', async function (e) {
  5945. base.setClipboard(e.target.dataset.filename);
  5946. $(e.target).text('复制成功').animate({ opacity: '0.5' }, "slow");
  5947. setTimeout(function () {
  5948. $(e.target).text('重新复制').animate({ opacity: '1' }, "slow");
  5949. }, 2000)
  5950. });
  5951. doc.on('click', '.listener-link-bc-btn', async function (e) {
  5952. let mirror = base.getMirrorList(e.target.dataset.dlink, config.$xunlei.api.mirror);
  5953. base.setClipboard(mirror);
  5954. $(e.target).text('复制成功').animate({ opacity: '0.5' }, "slow");
  5955. setTimeout(function () {
  5956. $(e.target).text('重新复制').animate({ opacity: '1' }, "slow");
  5957. }, 2000)
  5958. });
  5959. doc.on('click', '.listener-link-aria, .listener-copy-all', function (e) {
  5960. e.preventDefault();
  5961. base.setClipboard(decodeURIComponent(e.target.dataset.link));
  5962. $(e.target).text('复制成功').animate({ opacity: '0.5' }, "slow");
  5963. setTimeout(function () {
  5964. $(e.target).text('重新复制').animate({ opacity: '1' }, "slow");
  5965. }, 2000)
  5966. });
  5967. doc.on('click', '.listener-link-rpc', async function (e) {
  5968. let target = $(e.currentTarget);
  5969.  
  5970. target.find('.icon-rpc-devices').remove();
  5971. target.find('.pl-loading').remove();
  5972. target.prepend(base.createLoading());
  5973.  
  5974. let res = await base.sendLinkToRPC(e.currentTarget.dataset.link, e.currentTarget.dataset.filename);
  5975. if (res === 'success') {
  5976. $('.listener-rpc-task').show();
  5977. target.removeClass('pl-btn-danger').html('发送成功了!快去看看吧~').animate({ opacity: '0.5' }, "slow");
  5978. } else if (res === 'assistant') {
  5979. target.addClass('pl-btn-danger').html(`${config.base.assistant.message}👉<a href="${config.base.assistant.link}" target="_blank" class="pl-a">点击此处安装</a>👈`);
  5980. } else {
  5981. target.addClass('pl-btn-danger').text('发送失败,检查一下您的RPC配置信息哦!').animate({ opacity: '0.5' }, "slow");
  5982. }
  5983. });
  5984. doc.on('click', '.listener-send-rpc', function (e) {
  5985. $('.listener-link-rpc').click();
  5986. $(e.target).text('发送完成,发送结果见上方按钮哦~').animate({ opacity: '0.5' }, "slow");
  5987. });
  5988. doc.on('click', '.listener-open-setting', function () {
  5989. base.showSetting();
  5990. });
  5991. doc.on('click', '.listener-open-updatelog', function () {
  5992. base.showUpdate();
  5993. });
  5994. doc.on('click', '.listener-open-beautify', function () {
  5995. base.showBeautify();
  5996. });
  5997. doc.on('click', '.listener-rpc-task', function () {
  5998. let rpc = JSON.stringify({
  5999. domain: base.getValue('setting_rpc_domain'),
  6000. port: base.getValue('setting_rpc_port'),
  6001. }), url = `${config.base.service.rpc}/?rpc=${base.encodeBase(rpc)}#${base.getValue('setting_rpc_token')}`;
  6002. GM_openInTab(url, { active: true });
  6003. });
  6004. },
  6005.  
  6006. addButton() {
  6007. base.waitForKeyElements(config.$xunlei.mount.home, (element) => {
  6008. page = $xunlei.detectPage();
  6009. if ($(".pl-button").length > 0 || !page || page !== 'home') return;
  6010. let $button = $(`<div class="xunlei-button pl-button"><i class="xlpfont xlp-download"></i><span style="font-size: 13px;margin-left: 6px;">下载助手</span>
  6011. <ul class="pl-dropdown-menu" style="top: 34px;">
  6012. <li class="pl-dropdown-menu-item pl-button-mode" data-mode="api">API 下载</li>
  6013. <li class="pl-dropdown-menu-item pl-button-mode" data-mode="aria">Aria 下载</li>
  6014. <li class="pl-dropdown-menu-item pl-button-mode" data-mode="rpc">RPC 下载</li>
  6015. <li class="pl-dropdown-menu-item pl-button-mode" data-mode="curl">cURL 下载</li>
  6016. <li class="pl-dropdown-menu-item pl-button-mode" data-mode="bc">BC 下载</li>
  6017. <li class="pl-dropdown-menu-item pl-button-mode listener-open-setting">助手设置</li>
  6018. <li class="pl-dropdown-menu-item pl-button-mode listener-open-beautify">助手美化</li>
  6019. <li class="pl-dropdown-menu-item pl-button-mode listener-open-updatelog">更新日志</li>
  6020. </ul>
  6021. </div>`);
  6022. element.prepend($button);
  6023. })
  6024. base.waitForKeyElements(config.$xunlei.mount.share, (element) => {
  6025. page = $xunlei.detectPage();
  6026. if ($(".pl-button").length > 0 || !page || page !== 'share') return;
  6027. let $button = $(`<div class="xunlei-button pl-button">
  6028. <i class="xlpfont xlp-download"></i><span style="font-size: 13px;margin-left: 6px;">下载助手</span>
  6029. <ul class="pl-dropdown-menu" style="top: 34px;">
  6030. <li class="pl-dropdown-menu-item pl-button-mode pl-button-save"><i class="xlpfont xlp-file-upload"></i><span style="margin-left: 3px;">转存后下载</span></li>
  6031. <li class="pl-dropdown-menu-item pl-button-mode listener-open-setting">助手设置</li>
  6032. <li class="pl-dropdown-menu-item pl-button-mode listener-open-beautify">助手美化</li>
  6033. <li class="pl-dropdown-menu-item pl-button-mode listener-open-updatelog">更新日志</li>
  6034. </ul>
  6035. </div>`);
  6036. $button.css({ 'margin-right': '10px' });
  6037. element.prepend($button);
  6038. })
  6039. },
  6040.  
  6041. addInitButton() {
  6042. let $button = $(`<div class="xunlei-button pl-button-init"><i class="xlpfont xlp-download"></i><span style="font-size: 13px;margin-left: 6px;">点我点亮</span></div>`);
  6043. $button.click(function () { base.initDialog() });
  6044. base.waitForKeyElements(config.$xunlei.mount.home, (element) => {
  6045. page = $xunlei.detectPage();
  6046. if ($(".pl-button-init").length > 0 || !page || page !== 'home') return;
  6047. element.prepend($button);
  6048. })
  6049. base.waitForKeyElements(config.$xunlei.mount.share, (element) => {
  6050. page = $xunlei.detectPage();
  6051. if ($(".pl-button-init").length > 0 || !page || page !== 'share') return;
  6052. $button.css({ 'margin-right': '10px' });
  6053. element.prepend($button);
  6054. })
  6055. },
  6056.  
  6057. getToken() {
  6058. doc.find('.loading-popup .loading-title').html(`令牌获取中`);
  6059. doc.find('.loading-popup .swal2-html-container').html(`<div>正在获取状态~</div>`);
  6060. let credentials = {}, captcha = {};
  6061. for (let i = 0; i < localStorage.length; i++) {
  6062. if (/^credentials_/.test(localStorage.key(i))) {
  6063. credentials = base.getStorage(localStorage.key(i));
  6064. base.setStorage('');
  6065. }
  6066. if (/^captcha_[\w]{16}/.test(localStorage.key(i))) {
  6067. captcha = base.getStorage(localStorage.key(i));
  6068. }
  6069. }
  6070. let deviceid = /(\w{32})/.exec(base.getStorage('deviceid').split(','))[0];
  6071. let token = {
  6072. credentials,
  6073. captcha,
  6074. deviceid
  6075. };
  6076. return token;
  6077. },
  6078.  
  6079. async getFileUrlByOnce(item, index, token) {
  6080. try {
  6081. if (item.downloadUrl) return {
  6082. index,
  6083. downloadUrl: item.downloadUrl
  6084. };
  6085. let res = await base.get(config.$xunlei.api.getLink + item.id, {
  6086. 'Authorization': `${token.credentials.token_type} ${token.credentials.access_token}`,
  6087. 'content-type': "application/json",
  6088. 'x-captcha-token': token.captcha.token,
  6089. 'x-device-id': token.deviceid,
  6090. });
  6091. if (res.web_content_link) {
  6092. return {
  6093. index,
  6094. downloadUrl: res.web_content_link
  6095. };
  6096. } else {
  6097. return {
  6098. index,
  6099. downloadUrl: '获取下载地址失败,刷新后再试试吧~'
  6100. };
  6101. }
  6102. } catch (e) {
  6103. return message.error('提示:<br/>请先登录网盘后再刷新页面呢~');
  6104. }
  6105. },
  6106.  
  6107. async getLink() {
  6108. Swal.fire({
  6109. showConfirmButton: false,
  6110. allowOutsideClick: false,
  6111. allowEscapeKey: false,
  6112. allowEnterKey: false,
  6113. title: "获取中",
  6114. html: `...`,
  6115. footer: "如果选的文件较多,请耐心等待获取完成哦!",
  6116. customClass: {
  6117. popup: 'loading-popup',
  6118. header: 'loading-header',
  6119. title: 'loading-title',
  6120. content: 'loading-content',
  6121. input: 'loading-input',
  6122. footer: 'loading-footer'
  6123. },
  6124. willOpen: function () {
  6125. Swal.showLoading();
  6126. },
  6127. ...swalDefault
  6128. });
  6129. selectList = this.getSelectedList();
  6130. if (selectList.length === 0) {
  6131. return message.error('提示:<br/>请勾选要下载的文件哦~');
  6132. }
  6133. if (this.isOnlyFolder()) {
  6134. return message.error('提示:<br/>请打开文件夹后再勾选文件~');
  6135. }
  6136. if (page === 'home') {
  6137. let token = this.getToken();
  6138. let batchSize = 15;
  6139. let processed = 0;
  6140. doc.find('.loading-popup .loading-title').html(`链接获取中`);
  6141. doc.find('.loading-popup .swal2-html-container').html(`<div>正在获取文件对应的下载链接~</div>`);
  6142. for (let i = 0; i < selectList.length; i += batchSize) {
  6143. let batch = selectList.slice(i, i + batchSize);
  6144. let queue = [];
  6145.  
  6146. batch.forEach((item, localIndex) => {
  6147. let globalIndex = i + localIndex;
  6148. queue.push(this.getFileUrlByOnce(item, globalIndex, token)
  6149. .then(val => {
  6150. processed++;
  6151. doc.find('.loading-popup .swal2-html-container').html(`<div>已获取 ${processed} / ${selectList.length} 个链接~</div>`);
  6152. return val;
  6153. }));
  6154. });
  6155.  
  6156. let res = await Promise.all(queue);
  6157. res.forEach(val => {
  6158. selectList[val.index].downloadUrl = val.downloadUrl;
  6159. });
  6160.  
  6161. await base.sleep(1000);
  6162. }
  6163. } else {
  6164. return message.error('提示:<br/>页面错误~');
  6165. }
  6166. let html = this.generateDom(selectList);
  6167. base.showMainDialog(config.base.dom.button[mode].title, html, config.base.dom.button[mode].footer);
  6168.  
  6169. },
  6170.  
  6171. generateDom(list) {
  6172. if (!list) {
  6173. return message.error('提示:<br/>获取下载链接失败,刷新网页后再试试吧~');
  6174. }
  6175. let content = '<div class="pl-main">';
  6176. let alinkAllText = '';
  6177. list.forEach((v, i) => {
  6178. if (v.kind === 'drive#folder') return;
  6179. let filename = v.name;
  6180. let size = base.sizeFormat(+v.size);
  6181. let dlink = v.downloadUrl;
  6182. if (!dlink || !dlink.includes("http")) {
  6183. content += `<div class="pl-item">
  6184. <div class="pl-item-name listener-tip" data-size="${size}">${filename}</div>
  6185. <div class="pl-item-tip">获取下载链接失败,刷新网页后再试试吧~</div>
  6186. </div>`;
  6187. } else {
  6188. if (mode === 'api') {
  6189. alinkAllText += dlink + '\r\n';
  6190. content += `<div class="pl-item">
  6191. <div class="pl-item-name listener-tip" data-size="${size}">${filename}</div>
  6192. <button class="pl-item-link listener-tip pl-btn-primary listener-link-api blob" data-title="不建议使用本功能,若文件过大下载完成后有可能不会弹出窗口,此时请换用“RPC 下载 + Mortix”的组合<br/>基于浏览器的 Blob 文件流下载文件,下载完成可自动命名,适用于较新的浏览器,可以在此窗口中显示下载剩余时间和下载速度,此方式下载不会被 IDM 捕获下载链接" data-filename="${filename}" data-size="${v.size}" data-link="${dlink}" data-index="${i}">增强下载(基于浏览器文件流)</button>
  6193. <a class="pl-item-link listener-tip pl-btn-primary pl-btn-info listener-link-api browser" data-title="基于浏览器直接打开链接来下载文件,适用于较为古老但支持 iframe 的浏览器,点击“直接下载”后需等待下载提示弹出才能点击下个“直接下载”,否则只会下载后者,若服务器未回报文件名,此方式下载不会被 IDM 捕获下载链接,此时建议右键此按钮,选择 “使用 IDM 下载”" data-filename="${filename}" data-link="${dlink}" href="${dlink}">直接下载(基于浏览器链接)</a>
  6194. <button class="pl-item-copy listener-tip pl-btn-primary pl-btn-success listener-copy-filename" data-title="本网盘下载时可能不会显示文件名称,这时需要手动复制文件名称到下载工具中" data-filename="${filename}">复制名称</button>
  6195. <button class="pl-item-copy pl-btn-primary pl-btn-warning listener-copy-all" href="${dlink}" data-filename="${filename}" data-link="${dlink}">复制链接</button>
  6196. <div class="pl-item-progress" style="display: none">
  6197. <div class="pl-progress">
  6198. <div class="pl-progress-outer"></div>
  6199. <div class="pl-progress-inner" style="width:5%">
  6200. <div class="pl-progress-inner-text">正在加载进度...0%</div>
  6201. </div>
  6202. </div>
  6203. <button class="pl-btn-primary pl-btn-danger pl-progress-stop listener-stop">取消下载</button>
  6204. <button class="pl-btn-primary pl-btn-info pl-progress-back listener-back" style="display: none">返回</button>
  6205. </div>
  6206. </div>`;
  6207. }
  6208. if (mode === 'aria') {
  6209. let alink = base.convertLinkToAria(dlink, filename);
  6210. alinkAllText += alink + '\r\n';
  6211. content += `<div class="pl-item">
  6212. <div class="pl-item-name listener-tip" data-size="${size}">${filename}</div>
  6213. <a class="pl-item-link listener-link-aria" href="${alink}" title="点击复制 aria2c 命令行" data-filename="${filename}" data-link="${alink}">${decodeURIComponent(alink)}<br/>复制 ${filename} 下载命令行</a>
  6214. </div>`;
  6215. }
  6216. if (mode === 'rpc') {
  6217. content += `<div class="pl-item">
  6218. <div class="pl-item-name listener-tip" data-size="${size}">${filename}</div>
  6219. <button class="pl-item-link listener-link-rpc pl-btn-primary pl-btn-info" data-filename="${filename}" data-link="${dlink}"><svg class="icon-rpc-devices" viewBox="-10 0 1034 1024"><g transform="matrix(1 0 0 -1 0 960)"><path fill="currentColor" d="M832 -64h-640q-53 0 -90.5 37.5t-37.5 90.5v768q0 53 37.5 90.5t90.5 37.5h640q53 0 90.5 -37.5t37.5 -90.5v-768q0 -53 -37.5 -90.5t-90.5 -37.5zM832 768q0 27 -18.5 45.5t-45.5 18.5h-512q-27 0 -45.5 -18.5t-18.5 -45.5v-320h640v320v0zM832 320h-640v-192q0 -27 18.5 -45.5t45.5 -18.5h512q27 0 45.5 18.5t18.5 45.5v192v0zM512 128q-27 0 -45.5 18.5t-18.5 45.5t18.5 45.5t45.5 18.5t45.5 -18.5t18.5 -45.5t-18.5 -45.5t-45.5 -18.5z" /></g></svg><span style="margin-left: 5px;">将 ${filename} 推送到 RPC 下载器</span></button>
  6220. </div>`;
  6221. }
  6222. if (mode === 'curl') {
  6223. let alink = base.convertLinkToCurl(dlink, filename);
  6224. alinkAllText += alink + '\r\n';
  6225. content += `<div class="pl-item">
  6226. <div class="pl-item-name listener-tip" data-size="${size}">${filename}</div>
  6227. <a class="pl-item-link listener-link-aria" href="${alink}" title="点击复制 curl 命令行" data-filename="${filename}" data-link="${alink}">${decodeURIComponent(alink)}<br/>复制 ${filename} 下载命令行</a>
  6228. </div>`;
  6229. }
  6230. if (mode === 'bc') {
  6231. let alink = base.convertLinkToBC(dlink, filename);
  6232. alinkAllText += alink + '\r\n';
  6233. content += `<div class="pl-item">
  6234. <div class="pl-item-name listener-tip" data-size="${size}">${filename}</div>
  6235. <a class="pl-item-link" href="${decodeURIComponent(alink)}" title="点击用比特彗星下载" data-filename="${filename}" data-link="${alink}">${decodeURIComponent(alink)}<br/>下载 ${filename}</a>
  6236. <button class="pl-btn-primary listener-link-bc-btn" data-dlink="${dlink}">复制镜像地址</div>
  6237. </div>`;
  6238. }
  6239. }
  6240. });
  6241. content += '</div>';
  6242.  
  6243. if (mode === 'rpc') {
  6244. content += `<div class="pl-extra">`
  6245. }
  6246. if (list.length >= 2) {
  6247. if (mode === 'api')
  6248. content += `<div class="pl-extra"><button class="pl-btn-primary listener-tip listener-download-all blob" data-title="不建议使用本功能,若文件过大下载完成后有可能不会弹出窗口,此时请换用“RPC 下载 + Mortix”的组合<br/>基于浏览器的 Blob 文件流下载文件,适用于较新的浏览器,可以在此窗口中显示下载剩余时间和下载速度,此方式下载不会被 IDM 捕获下载链接">全部增强下载</button><button class="pl-btn-primary listener-tip listener-copy-all" data-link="${alinkAllText}" data-title="不建议使用本功能,在本网盘单独复制链接并粘贴下载可能会导致服务器回报 403 错误">复制全部链接</button></div>`;
  6249. if (mode === 'aria')
  6250. content += `<div class="pl-extra"><button class="pl-btn-primary listener-copy-all" data-link="${alinkAllText}">复制全部命令行</button></div>`;
  6251. if (mode === 'rpc') {
  6252. content += `<button class="pl-btn-primary listener-send-rpc">发送全部链接</button>`;
  6253. }
  6254. if (mode === 'curl') {
  6255. content += `<div class="pl-extra"><button class="pl-btn-primary listener-copy-all" data-link="${alinkAllText}">复制全部命令行</button><button class="pl-btn-primary pl-btn-warning listener-open-setting" style="margin-left: 10px;">修改终端类型(${terminalType[base.getValue('setting_terminal_type')]})</button></div>`;
  6256. }
  6257. if (mode === 'bc') {
  6258. content += `<div class="pl-extra"><button class="pl-btn-primary listener-copy-all" data-link="${alinkAllText}">复制全部链接</button></div>`;
  6259. }
  6260. }
  6261. if (mode === 'rpc') {
  6262. let rpc = base.getValue('setting_rpc_domain') + ':' + base.getValue('setting_rpc_port') + base.getValue('setting_rpc_path');
  6263. content += `<button title="${rpc}" class="pl-btn-primary pl-btn-warning listener-open-setting" style="margin-left: 10px">修改 RPC 参数(${rpc})</button>
  6264. <button class="pl-btn-primary pl-btn-success pl-btn-opacity listener-rpc-task" style="margin-left: 10px;display: none">查看下载任务</button>
  6265. </div>`;
  6266. }
  6267.  
  6268. return content;
  6269. },
  6270.  
  6271. getSelectedList() {
  6272. try {
  6273. let doms = document.querySelectorAll('.SourceListItem__item--XxpOC');
  6274. let selectedList = [];
  6275. for (let dom of doms) {
  6276. let domVue = dom.__vue__;
  6277. if (domVue.selected.includes(domVue.info.id)) {
  6278. selectedList.push(domVue.info);
  6279. }
  6280. }
  6281. return selectedList;
  6282. } catch (e) {
  6283. return [];
  6284. }
  6285. },
  6286.  
  6287. detectPage() {
  6288. let path = location.pathname;
  6289. if (/^\/$/.test(path)) return 'home';
  6290. if (/^\/(s|share)\//.test(path)) return 'share';
  6291. return '';
  6292. },
  6293.  
  6294. isOnlyFolder() {
  6295. for (let i = 0; i < selectList.length; i++) {
  6296. if (selectList[i].kind === 'drive#file') return false;
  6297. }
  6298. return true;
  6299. },
  6300.  
  6301. async initPanLinker() {
  6302. page = this.detectPage();
  6303. base.createTip();
  6304. base.registerMenuCommand();
  6305. if (config.base.num === base.getValue('setting_init_code') || config.base.license === base.getValue('setting_init_license')) {
  6306. this.addButton();
  6307. this.addPageListener();
  6308. } else {
  6309. this.addInitButton();
  6310. }
  6311. }
  6312. };
  6313.  
  6314. /**
  6315. * 夸克网盘
  6316. * @author 油小猴
  6317. * @author hmjz100
  6318. */
  6319. let $quark = {
  6320. addPageListener() {
  6321. /*
  6322. 防止代码因其他原因被执行多次
  6323. 这段代码出自 Via轻插件,作者谷花泰
  6324. */
  6325. const key = encodeURIComponent('LinkSwift:夸克网盘');
  6326. if (window[key]) return;
  6327. window[key] = true;
  6328.  
  6329. function _factory(e) {
  6330. let target = $(e.target);
  6331. let item = target.parents('.pl-item');
  6332. let link = item.find('.pl-item-link.blob');
  6333. let directLink = item.find('.pl-item-link.browser');
  6334. let progress = item.find('.pl-item-progress');
  6335. let tip = item.find('.pl-item-tip');
  6336. let copy = item.find('.pl-item-copy');
  6337. let back = item.find('.pl-progress-back');
  6338. let stop = item.find('.pl-progress-stop');
  6339. return {
  6340. item, link, directLink, progress, tip, copy, back, stop, target,
  6341. };
  6342. }
  6343.  
  6344. window.addEventListener('hashchange', async function (e) {
  6345. let home = 'https://pan.quark.cn/list#/', all = 'https://pan.quark.cn/list#/list/all';
  6346. if (e.oldURL === home && e.newURL === all) return;
  6347. await base.sleep(150);
  6348. if ($('.quark-button').length > 0) return;
  6349. if (config.base.num === base.getValue('setting_init_code') || config.base.license === base.getValue('setting_init_license')) {
  6350. this.addButton();
  6351. this.addPageListener();
  6352. } else {
  6353. this.addInitButton();
  6354. }
  6355. });
  6356. doc.on('click', '.pl-button-mode', function (e) {
  6357. mode = e.target.dataset.mode;
  6358. if (!mode) return;
  6359. $quark.getLink();
  6360. });
  6361. doc.on('click', '.pl-button-save', async function (e) {
  6362. e.preventDefault();
  6363. selectList = $quark.getSelectedList();
  6364. if (selectList.length === 0) {
  6365. return message.error('提示:<br/>请勾选要保存到网盘的文件哦~');
  6366. }
  6367. message.info('提示:<br/>因网盘限制,请保存到自己网盘后再去下载哦~');
  6368. await base.sleep(500);
  6369. document.querySelector('.share-path').click();
  6370. base.waitForKeyElements(".btn-file.btn-file-primary.confirm-btn", (element) => {
  6371. element.one("click", async () => {
  6372. await base.sleep(1000);
  6373. document.querySelector('.share-save').click();
  6374. })
  6375. return true;
  6376. }, true)
  6377. });
  6378. doc.on('click', '.listener-link-api.browser', async function (e) {
  6379. e.preventDefault();
  6380. let dataset = e.currentTarget.dataset;
  6381. let href = dataset.link;
  6382. $('#downloadIframe').attr('src', href);
  6383. });
  6384. doc.on('click', '.listener-link-api.blob', async function (e) {
  6385. e.preventDefault();
  6386.  
  6387. const o = _factory(e);
  6388. const $width = o.item.find('.pl-progress-inner');
  6389. const $text = o.item.find('.pl-progress-inner-text');
  6390. const filename = o.link[0].dataset.filename;
  6391. const index = o.link[0].dataset.index;
  6392. const size = Number(o.link[0].dataset.size) || 0;
  6393.  
  6394. base._resetData(index);
  6395. base.get(e.currentTarget.dataset.link, undefined, 'blob', { filename, index });
  6396.  
  6397. let startTime = Date.now();
  6398. let prevLoaded = 0;
  6399. let prevTime = startTime;
  6400.  
  6401. ins[index] = setInterval(async function () {
  6402. const prog = +progress[index] || 0;
  6403. const currentTime = Date.now();
  6404. const elapsedTime = currentTime - startTime;
  6405. const loaded = prog * size / 100;
  6406. const timeDiff = Math.max(currentTime - prevTime, 1); // 避免除零
  6407. const speed = ((loaded - prevLoaded) / (timeDiff / 1000)) || 0;
  6408.  
  6409. // 计算剩余时间(保护除零)
  6410. const totalProgress = Math.max(prog / 100, 0.01);
  6411. const totalElapsedSeconds = elapsedTime / 1000;
  6412. const estTotalTime = totalElapsedSeconds / totalProgress;
  6413. const remainingTime = estTotalTime - totalElapsedSeconds;
  6414.  
  6415. // 更新界面状态
  6416. o.link.hide();
  6417. o.directLink.hide();
  6418. o.tip.hide();
  6419. o.stop.show();
  6420. o.copy.hide();
  6421. o.progress.show();
  6422.  
  6423. // 更新进度条
  6424. $width.css('width', `${prog}%`);
  6425. $text.text(`${prog.toFixed(1)}% | 速度:${base.sizeFormat(speed)} | 剩余:${base.rtimeFormat(remainingTime)}`);
  6426.  
  6427. // 更新历史值
  6428. prevLoaded = loaded;
  6429. prevTime = currentTime;
  6430.  
  6431. // 下载完成
  6432. if (prog >= 100) {
  6433. await base.sleep(1000);
  6434. clearInterval(ins[index]);
  6435. progress[index] = 0;
  6436. o.item.find('.pl-progress-stop').hide();
  6437. $text.text('下载完成~ 浏览器下载框应该弹出来了哦~');
  6438. o.back.show();
  6439. await base.sleep(3000);
  6440. o.link.text('增强下载(基于浏览器文件流)').animate({ opacity: '1' }, "slow");
  6441. }
  6442. }, 500);
  6443. });
  6444. doc.on('click', '.listener-retry', async function (e) {
  6445. let o = _factory(e);
  6446. o.tip.hide();
  6447. o.link.show();
  6448. o.directLink.show();
  6449. });
  6450. doc.on('click', '.listener-stop', async function (e) {
  6451. let o = _factory(e);
  6452. let index = o.link[0].dataset.index;
  6453. if (request[index]) {
  6454. request[index].abort();
  6455. clearInterval(ins[index]);
  6456. o.item.find('.pl-progress-inner-text').text('正在取消...');
  6457. o.item.find('.pl-progress-inner').css('width', 100 + '%');
  6458. setTimeout(function () {
  6459. o.tip.hide();
  6460. o.back.hide();
  6461. o.link.show();
  6462. o.directLink.show();
  6463. o.copy.show();
  6464. o.progress.hide();
  6465. o.stop.hide();
  6466. }, 1050)
  6467. }
  6468. });
  6469. doc.on('click', '.listener-back', async function (e) {
  6470. let o = _factory(e);
  6471. o.progress.hide();
  6472. o.tip.hide();
  6473. o.link.show();
  6474. o.directLink.show();
  6475. o.copy.show();
  6476. o.stop.hide();
  6477. o.back.hide();
  6478. });
  6479. doc.on('click', '.listener-download-all', function (e) {
  6480. $('.pl-item-link.blob').each(function () {
  6481. if ($(this).css('display') !== 'none') {
  6482. $(this).click();
  6483. }
  6484. });
  6485. $(e.target).text('下载开始,下载进度见上方按钮哦~').animate({ opacity: '0.5' }, "slow");
  6486. setTimeout(function () {
  6487. $(e.target).text('全部增强下载').animate({ opacity: '1' }, "slow");
  6488. }, 2000)
  6489. });
  6490. doc.on('click', '.listener-link-aria, .listener-copy-all', function (e) {
  6491. e.preventDefault();
  6492. base.setClipboard(decodeURIComponent(e.target.dataset.link));
  6493. $(e.target).text('复制成功').animate({ opacity: '0.5' }, "slow");
  6494. setTimeout(function () {
  6495. $(e.target).text('重新复制').animate({ opacity: '1' }, "slow");
  6496. }, 2000)
  6497. });
  6498. doc.on('click', '.listener-link-rpc', async function (e) {
  6499. let target = $(e.currentTarget);
  6500.  
  6501. target.find('.icon-rpc-devices').remove();
  6502. target.find('.pl-loading').remove();
  6503. target.prepend(base.createLoading());
  6504.  
  6505. let res = await base.sendLinkToRPC(e.currentTarget.dataset.link, e.currentTarget.dataset.filename, [`Cookie: ${document.cookie}`]);
  6506. if (res === 'success') {
  6507. $('.listener-rpc-task').show();
  6508. target.removeClass('pl-btn-danger').html('发送成功了!快去看看吧~').animate({ opacity: '0.5' }, "slow");
  6509. } else if (res === 'assistant') {
  6510. target.addClass('pl-btn-danger').html(`${config.base.assistant.message}👉<a href="${config.base.assistant.link}" target="_blank" class="pl-a">点击此处安装</a>👈`);
  6511. } else {
  6512. target.addClass('pl-btn-danger').text('发送失败,检查一下您的RPC配置信息哦!').animate({ opacity: '0.5' }, "slow");
  6513. }
  6514. });
  6515. doc.on('click', '.listener-send-rpc', function (e) {
  6516. $('.listener-link-rpc').click();
  6517. $(e.target).text('发送完成,发送结果见上方按钮哦~').animate({ opacity: '0.5' }, "slow");
  6518. });
  6519. doc.on('click', '.listener-open-setting', function () {
  6520. base.showSetting();
  6521. });
  6522. doc.on('click', '.listener-open-updatelog', function () {
  6523. base.showUpdate();
  6524. });
  6525. doc.on('click', '.listener-open-beautify', function () {
  6526. base.showBeautify();
  6527. });
  6528. doc.on('click', '.listener-rpc-task', function () {
  6529. let rpc = JSON.stringify({
  6530. domain: base.getValue('setting_rpc_domain'),
  6531. port: base.getValue('setting_rpc_port'),
  6532. }), url = `${config.base.service.rpc}/?rpc=${base.encodeBase(rpc)}#${base.getValue('setting_rpc_token')}`;
  6533. GM_openInTab(url, { active: true });
  6534. });
  6535. },
  6536.  
  6537. greenerPage() {
  6538. base.waitForKeyElements('[class*="Activity--video-toolbar-activity"]', function (tag) {
  6539. tag.fadeOut();
  6540. }, true);
  6541. base.waitForKeyElements('span[class*="SectionHeaderController--icon-download"]', function (tag) {
  6542. tag.fadeOut();
  6543. }, true);
  6544. base.waitForKeyElements('div[class*="SectionHeaderController--download-popover"]', function (tag) {
  6545. tag.find(".ant-popover-arrow").css({ "left": "75%" });
  6546. }, true);
  6547. base.waitForKeyElements('div[class*="DetailLayout--client-download"]', function (tag) {
  6548. tag.fadeOut();
  6549. }, true);
  6550. base.waitForKeyElements(".next-box.share-right-side-content", function (tag) {
  6551. tag.fadeOut();
  6552. }, true);
  6553. base.waitForKeyElements('[class*="DetailLayout--container"] .feature-screen', function (tag) {
  6554. tag.fadeOut();
  6555. }, true);
  6556. base.waitForKeyElements('.ant-modal-content .ant-modal-body .right-wrap', function (tag) {
  6557. if (tag.find(".hint").text().includes("客户端")) tag.fadeOut();
  6558. }, true);
  6559. base.waitForKeyElements(".pc-member-entrance span.button-text", function (tag) {
  6560. tag.text("会员中心");
  6561. let observer = new MutationObserver(function (mutations) {
  6562. mutations.forEach(function (mutation) {
  6563. if (tag.text() === "会员中心") return
  6564. tag.text("会员中心");
  6565. });
  6566. });
  6567. let config = { subtree: true, characterData: true, childList: true };
  6568. observer.observe(tag[0], config);
  6569. }, true);
  6570. base.waitForKeyElements(".pc-member-entrance .tips", function (tag) {
  6571. tag.fadeOut();
  6572. }, true);
  6573. base.waitForKeyElements(".modal .modal-content .halo-animated-background .halo-content .pay-modal .close", function (tag) {
  6574. tag[0].click();
  6575. }, true);
  6576. base.waitForKeyElements(".modal .modal-content .halo-animated-background .halo-content .red-envelope .close", function (tag) {
  6577. tag[0].click();
  6578. }, true);
  6579. },
  6580. svg: 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjIiIGhlaWdodD0iMjIiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGcgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIiBzdHJva2U9IiNGRkZGRkYiIHN0cm9rZS13aWR0aD0iMiI+PHBhdGggc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBkPSJNOSAxMmwyIDIgMi0yeiIvPjxwYXRoIGQ9Ik0xNCA4aDEuNTUzYy44NSAwIDEuMTYuMDkzIDEuNDcuMjY3LjMxMS4xNzQuNTU2LjQzLjcyMi43NTYuMTY2LjMyNi4yNTUuNjUuMjU1IDEuNTR2NC44NzNjMCAuODkyLS4wODkgMS4yMTUtLjI1NSAxLjU0LS4xNjYuMzI3LS40MS41ODMtLjcyMi43NTctLjMxLjE3NC0uNjIuMjY3LTEuNDcuMjY3SDYuNDQ3Yy0uODUgMC0xLjE2LS4wOTMtMS40Ny0uMjY3YTEuNzc4IDEuNzc4IDAgMDEtLjcyMi0uNzU2Yy0uMTY2LS4zMjYtLjI1NS0uNjUtLjI1NS0xLjU0di00Ljg3M2MwLS44OTIuMDg5LTEuMjE1LjI1NS0xLjU0LjE2Ni0uMzI3LjQxLS41ODMuNzIyLS43NTcuMzEtLjE3NC42Mi0uMjY3IDEuNDctLjI2N0gxMSIvPjxwYXRoIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgZD0iTTExIDN2MTAiLz48L2c+PC9zdmc+',
  6581. addButton() {
  6582. base.waitForKeyElements(config.$quark.mount.home, (element) => {
  6583. page = $quark.detectPage();
  6584. if ($(".pl-button").length > 0 || !page || page !== 'home') return;
  6585. let $button = $(`<div class="ant-dropdown-trigger pl-button">
  6586. <button type="button" class="quark-button ant-btn btn-file ant-btn-primary">
  6587. <img class="quark-btn-icon" src="${$quark.svg}"><span>下载助手</span>
  6588. </button>
  6589. <ul class="pl-dropdown-menu">
  6590. <li class="pl-dropdown-menu-item pl-button-mode" data-mode="api">API 下载</li>
  6591. <li class="pl-dropdown-menu-item pl-button-mode" data-mode="aria">Aria 下载</li>
  6592. <li class="pl-dropdown-menu-item pl-button-mode" data-mode="rpc">RPC 下载</li>
  6593. <li class="pl-dropdown-menu-item pl-button-mode" data-mode="curl">cURL 下载</li>
  6594. <li class="pl-dropdown-menu-item pl-button-mode" data-mode="bc">BC 下载</li>
  6595. <li class="pl-dropdown-menu-item pl-button-mode listener-open-setting">助手设置</li>
  6596. <li class="pl-dropdown-menu-item pl-button-mode listener-open-beautify">助手美化</li>
  6597. <li class="pl-dropdown-menu-item pl-button-mode listener-open-updatelog">更新日志</li>
  6598. </ul>
  6599. </div>`);
  6600. $button.css({ "margin-right": "10px", "display": "inline-block" });
  6601. element.prepend($button);
  6602. })
  6603. base.waitForKeyElements(config.$quark.mount.share, (element) => {
  6604. page = $quark.detectPage();
  6605. if ($(".pl-button").length > 0 || !page || page !== 'share') return;
  6606. let $button = $(`<button type="button" class="ant-btn btn-file ant-btn-primary pl-button quark-button"><img class="quark-btn-icon" src="${$quark.svg}"><span>下载助手</span><ul class="pl-dropdown-menu" style="bottom:20px;left:0"><li class="pl-dropdown-menu-item pl-button-mode pl-button-save"><span class="share-save-ico"></span>保存后下载</li><li class="pl-dropdown-menu-item pl-button-mode listener-open-setting">助手设置</li><li class="pl-dropdown-menu-item pl-button-mode listener-open-beautify">助手美化</li><li class="pl-dropdown-menu-item pl-button-mode listener-open-updatelog">更新日志</li></ul></button>`);
  6607. $button.css({ "height": "36px", "margin-left": "16px", "border-radius": "6px", "display": "inline-block" });
  6608. element.append($button);
  6609. })
  6610. },
  6611.  
  6612. addInitButton() {
  6613. base.waitForKeyElements(config.$quark.mount.home, (element) => {
  6614. page = $quark.detectPage();
  6615. if ($(".pl-button-init").length > 0 || !page || page !== 'home') return;
  6616. let $button = $(`<div class="ant-dropdown-trigger pl-button-init"><button type="button" class="quark-button ant-btn btn-file ant-btn-primary"><img class="quark-btn-icon" src="${$quark.svg}"><span>点我点亮</span></button></div>`);
  6617. $button.css({ "margin-right": "10px", "display": "inline-block" });
  6618. $button.click(function () { base.initDialog() });
  6619. element.prepend($button);
  6620. })
  6621. base.waitForKeyElements(config.$quark.mount.share, (element) => {
  6622. page = $quark.detectPage();
  6623. if ($(".pl-button-init").length > 0 || !page || page !== 'share') return;
  6624. let $button = $(`<button type="button" class="ant-btn btn-file ant-btn-primary pl-button-init quark-button"><img class="quark-btn-icon" src="${$quark.svg}"><span>点我点亮</span></button>`);
  6625. $button.css({ "height": "36px", "margin-left": "16px", "border-radius": "6px", "display": "inline-block" });
  6626. $button.click(function () { base.initDialog() });
  6627. element.append($button);
  6628. })
  6629. },
  6630.  
  6631. async getLink() {
  6632. Swal.fire({
  6633. showConfirmButton: false,
  6634. allowOutsideClick: false,
  6635. allowEscapeKey: false,
  6636. allowEnterKey: false,
  6637. title: "获取中",
  6638. html: `...`,
  6639. footer: "如果选的文件较多,请耐心等待获取完成哦!",
  6640. customClass: {
  6641. popup: 'loading-popup',
  6642. header: 'loading-header',
  6643. title: 'loading-title',
  6644. content: 'loading-content',
  6645. input: 'loading-input',
  6646. footer: 'loading-footer'
  6647. },
  6648. willOpen: function () {
  6649. Swal.showLoading();
  6650. },
  6651. ...swalDefault
  6652. });
  6653. selectList = this.getSelectedList();
  6654. if (selectList.length === 0) {
  6655. return message.error('提示:<br/>请勾选要下载的文件哦~');
  6656. }
  6657. if (this.isOnlyFolder()) {
  6658. return message.error('提示:<br/>请打开文件夹后再勾选文件~');
  6659. }
  6660. if (page === 'home') {
  6661. let data = [];
  6662. let batchSize = 15;
  6663. let processed = 0;
  6664. selectList = selectList.filter(item => item.file === true)
  6665.  
  6666. for (let i = 0; i < selectList.length; i += batchSize) {
  6667. // 获取当前批次文件
  6668. let batch = selectList.slice(i, i + batchSize);
  6669. console.log()
  6670. let fids = batch.map(item => item.fid);
  6671.  
  6672. // 发起请求获取链接
  6673. let res = await base.post(config.$quark.api.getLink, { "fids": fids }, { "content-type": "application/json;charset=utf-8", "user-agent": config.$quark.api.ua.downloadLink });
  6674.  
  6675. if (res?.code === 31001) {
  6676. return message.error('提示:<br/>请先登录网盘~<br/>代码:' + res.code);
  6677. }
  6678. if (res?.code !== 0) {
  6679. return message.error('提示:<br/>获取链接失败了~<br/>代码:' + res.code);
  6680. }
  6681.  
  6682. // 合并响应数据
  6683. if (res?.data) {
  6684. data.push(...res.data);
  6685. }
  6686.  
  6687. // 更新处理进度
  6688. processed += batch.length;
  6689.  
  6690. // 更新UI显示
  6691. doc.find('.loading-popup .loading-title').html(`链接获取中`);
  6692. doc.find('.loading-popup .swal2-html-container').html(`<div>已获取 ${processed} / ${selectList.length} 个链接~</div>`);
  6693.  
  6694. // 请求间隔节流
  6695. await base.sleep(1000);
  6696. }
  6697. let html = this.generateDom(data);
  6698. base.showMainDialog(config.base.dom.button[mode].title, html, config.base.dom.button[mode].footer);
  6699. } else {
  6700. return message.error('提示:<br/>页面错误~');
  6701. }
  6702. },
  6703.  
  6704. generateDom(list) {
  6705. if (!list) {
  6706. return message.error('提示:<br/>获取下载链接失败,刷新网页后再试试吧~');
  6707. }
  6708. let content = '<div class="pl-main">';
  6709. let alinkAllText = '';
  6710. list.forEach((v, i) => {
  6711. if (v.file === false) return;
  6712. let filename = v.file_name;
  6713. let fid = v.fid;
  6714. let size = base.sizeFormat(v.size);
  6715. let dlink = v.download_url;
  6716. if (!dlink || !dlink.includes("http")) {
  6717. content += `<div class="pl-item">
  6718. <div class="pl-item-name listener-tip" data-size="${size}">${filename}</div>
  6719. <div class="pl-item-tip">获取下载链接失败,刷新网页后再试试吧~</div>
  6720. </div>`;
  6721. } else {
  6722. if (mode === 'api') {
  6723. alinkAllText += dlink + '\r\n';
  6724. content += `<div class="pl-item">
  6725. <div class="pl-item-name listener-tip" data-size="${size}">${filename}</div>
  6726. <button class="pl-item-link listener-tip pl-btn-primary pl-btn-default listener-link-api blob" data-title="不建议使用本功能,若文件过大下载完成后有可能不会弹出窗口,此时请换用“RPC 下载 + Mortix”的组合<br/>基于浏览器的 Blob 文件流下载文件,适用于较新的浏览器,可以在此窗口中显示下载剩余时间和下载速度,此方式下载不会被 IDM 捕获下载链接" data-filename="${filename}" data-size="${v.size}" data-link="${dlink}" data-fid="${fid}" data-index="${i}">增强下载(基于浏览器文件流)</button>
  6727. <button class="pl-item-link listener-tip pl-btn-primary pl-btn-info listener-link-api browser" data-title="基于浏览器直接打开链接来下载文件,适用于较为古老但支持 iframe 的浏览器,点击“直接下载”后需等待下载提示弹出才能点击下个“直接下载”,否则只会下载后者,此方式下载有可能会被 IDM 捕获下载链接" data-filename="${filename}" data-link="${dlink}" data-fid="${fid}">直接下载(基于浏览器链接)</button>
  6728. <button class="pl-item-copy pl-btn-primary pl-btn-warning listener-copy-all" href="${dlink}" data-filename="${filename}" data-link="${dlink}">复制链接</button>
  6729. <div class="pl-item-progress" style="display: none">
  6730. <div class="pl-progress">
  6731. <div class="pl-progress-outer"></div>
  6732. <div class="pl-progress-inner" style="width:5%">
  6733. <div class="pl-progress-inner-text">正在加载进度...0%</div>
  6734. </div>
  6735. </div>
  6736. <button class="pl-btn-primary pl-btn-danger pl-progress-stop listener-stop">取消下载</button>
  6737. <button class="pl-btn-primary pl-btn-info pl-progress-back listener-back" style="display: none">返回</button>
  6738. </div>
  6739. </div>`;
  6740. }
  6741. if (mode === 'aria') {
  6742. let alink = base.convertLinkToAria(dlink, filename, `--header "Cookie: ${document.cookie}"`);
  6743. alinkAllText += alink + '\r\n';
  6744. content += `<div class="pl-item">
  6745. <div class="pl-item-name listener-tip" data-size="${size}">${filename}</div>
  6746. <a class="pl-item-link listener-link-aria" href="${alink}" title="点击复制 aria2c 命令行" data-filename="${filename}" data-link="${alink}">${decodeURIComponent(alink)}<br/>复制 ${filename} 下载命令行</a>
  6747. </div>`;
  6748. }
  6749. if (mode === 'rpc') {
  6750. content += `<div class="pl-item">
  6751. <div class="pl-item-name listener-tip" data-size="${size}">${filename}</div>
  6752. <button class="pl-item-link listener-link-rpc pl-btn-primary pl-btn-info" data-filename="${filename}" data-link="${dlink}"><svg class="icon-rpc-devices" viewBox="-10 0 1034 1024"><g transform="matrix(1 0 0 -1 0 960)"><path fill="currentColor" d="M832 -64h-640q-53 0 -90.5 37.5t-37.5 90.5v768q0 53 37.5 90.5t90.5 37.5h640q53 0 90.5 -37.5t37.5 -90.5v-768q0 -53 -37.5 -90.5t-90.5 -37.5zM832 768q0 27 -18.5 45.5t-45.5 18.5h-512q-27 0 -45.5 -18.5t-18.5 -45.5v-320h640v320v0zM832 320h-640v-192q0 -27 18.5 -45.5t45.5 -18.5h512q27 0 45.5 18.5t18.5 45.5v192v0zM512 128q-27 0 -45.5 18.5t-18.5 45.5t18.5 45.5t45.5 18.5t45.5 -18.5t18.5 -45.5t-18.5 -45.5t-45.5 -18.5z" /></g></svg><span style="margin-left: 5px;">将 ${filename} 推送到 RPC 下载器</span></button>
  6753. </div>`;
  6754. }
  6755. if (mode === 'curl') {
  6756. let alink = base.convertLinkToCurl(dlink, filename, `-b "${document.cookie}"`);
  6757. alinkAllText += alink + '\r\n';
  6758. content += `<div class="pl-item">
  6759. <div class="pl-item-name listener-tip" data-size="${size}">${filename}</div>
  6760. <a class="pl-item-link listener-link-aria" href="${alink}" title="点击复制 curl 命令行" data-filename="${filename}" data-link="${alink}">${decodeURIComponent(alink)}<br/>复制 ${filename} 下载命令行</a>
  6761. </div>`;
  6762. }
  6763. if (mode === 'bc') {
  6764. let alink = base.convertLinkToBC(dlink, filename, `cookie=${encodeURIComponent(document.cookie)}`);
  6765. alinkAllText += alink + '\r\n';
  6766. content += `<div class="pl-item">
  6767. <div class="pl-item-name listener-tip" data-size="${size}">${filename}</div>
  6768. <a class="pl-item-link" href="${decodeURIComponent(alink)}" title="点击用比特彗星下载" data-filename="${filename}" data-link="${alink}">${decodeURIComponent(alink)}<br/>下载 ${filename}</a>
  6769. </div>`;
  6770. }
  6771. }
  6772. });
  6773. content += '</div>';
  6774.  
  6775. if (mode === 'rpc') {
  6776. content += `<div class="pl-extra">`
  6777. }
  6778. if (list.length >= 2) {
  6779. if (mode === 'api')
  6780. content += `<div class="pl-extra"><button class="pl-btn-primary listener-tip listener-download-all blob" data-title="不建议使用本功能,若文件过大下载完成后有可能不会弹出窗口,此时请换用“RPC 下载 + Mortix”的组合<br/>基于浏览器的 Blob 文件流下载文件,适用于较新的浏览器,可以在此窗口中显示下载剩余时间和下载速度,此方式下载不会被 IDM 捕获下载链接">全部增强下载</button><button class="pl-btn-primary listener-tip listener-copy-all" data-link="${alinkAllText}" data-title="不建议使用本功能,在本网盘单独复制链接并粘贴下载可能会导致服务器回报 403 错误">复制全部链接</button></div>`;
  6781. if (mode === 'aria')
  6782. content += `<div class="pl-extra"><button class="pl-btn-primary listener-copy-all" data-link="${alinkAllText}">复制全部命令行</button></div>`;
  6783. if (mode === 'rpc') {
  6784. content += `<button class="pl-btn-primary listener-send-rpc">发送全部链接</button>`;
  6785. }
  6786. if (mode === 'curl') {
  6787. content += `<div class="pl-extra"><button class="pl-btn-primary listener-copy-all" data-link="${alinkAllText}">复制全部命令行</button><button class="pl-btn-primary pl-btn-warning listener-open-setting" style="margin-left: 10px;">修改终端类型(${terminalType[base.getValue('setting_terminal_type')]})</button></div>`;
  6788. }
  6789. if (mode === 'bc') {
  6790. content += `<div class="pl-extra"><button class="pl-btn-primary listener-copy-all" data-link="${alinkAllText}">复制全部链接</button></div>`;
  6791. }
  6792. }
  6793. if (mode === 'rpc') {
  6794. let rpc = base.getValue('setting_rpc_domain') + ':' + base.getValue('setting_rpc_port') + base.getValue('setting_rpc_path');
  6795. content += `<button title="${rpc}" class="pl-btn-primary pl-btn-warning listener-open-setting" style="margin-left: 10px">修改 RPC 参数(${rpc})</button>
  6796. <button class="pl-btn-primary pl-btn-success pl-btn-opacity listener-rpc-task" style="margin-left: 10px;display: none">查看下载任务</button>
  6797. </div>`;
  6798. }
  6799.  
  6800. return content;
  6801. },
  6802.  
  6803. getSelectedList() {
  6804. try {
  6805. let selectedList = [];
  6806. let reactDom = document.getElementsByClassName('file-list')[0];
  6807. let reactObj = base.findReact(reactDom);
  6808. let props = reactObj.props;
  6809. if (props) {
  6810. let fileList = props.list || [];
  6811. let selectedKeys = props.selectedRowKeys || [];
  6812. fileList.forEach(function (val) {
  6813. if (selectedKeys.includes(val.fid)) {
  6814. selectedList.push(val);
  6815. }
  6816. });
  6817. }
  6818. return selectedList;
  6819. } catch (e) {
  6820. return [];
  6821. }
  6822. },
  6823.  
  6824. detectPage() {
  6825. let path = location.pathname;
  6826. if (/^\/(list)/.test(path)) return 'home';
  6827. if (/^\/(s|share)\//.test(path)) return 'share';
  6828. return '';
  6829. },
  6830.  
  6831. isOnlyFolder() {
  6832. for (let i = 0; i < selectList.length; i++) {
  6833. if (selectList[i].file) return false;
  6834. }
  6835. return true;
  6836. },
  6837.  
  6838. async initPanLinker() {
  6839. page = this.detectPage();
  6840. base.createTip();
  6841. base.registerMenuCommand();
  6842. if (config.base.num === base.getValue('setting_init_code') || config.base.license === base.getValue('setting_init_license')) {
  6843. this.addButton();
  6844. this.addPageListener();
  6845. } else {
  6846. this.addInitButton();
  6847. }
  6848. }
  6849. };
  6850.  
  6851. /**
  6852. * UC网盘
  6853. * @author 油小猴
  6854. * @author hmjz100
  6855. */
  6856. let $uc = {
  6857. addPageListener() {
  6858. /*
  6859. 防止代码因其他原因被执行多次
  6860. 这段代码出自 Via轻插件,作者谷花泰
  6861. */
  6862. const key = encodeURIComponent('LinkSwift:UC网盘');
  6863. if (window[key]) return;
  6864. window[key] = true;
  6865.  
  6866. function _factory(e) {
  6867. let target = $(e.target);
  6868. let item = target.parents('.pl-item');
  6869. let link = item.find('.pl-item-link.blob');
  6870. let directLink = item.find('.pl-item-link.browser');
  6871. let progress = item.find('.pl-item-progress');
  6872. let tip = item.find('.pl-item-tip');
  6873. let copy = item.find('.pl-item-copy');
  6874. let back = item.find('.pl-progress-back');
  6875. let stop = item.find('.pl-progress-stop');
  6876. return {
  6877. item, link, directLink, progress, tip, copy, back, stop, target,
  6878. };
  6879. }
  6880.  
  6881. window.addEventListener('hashchange', async function (e) {
  6882. let home = 'https://drive.uc.cn/list#/', all = 'https://drive.uc.cn/list#/list/all';
  6883. if (e.oldURL === home && e.newURL === all) return;
  6884. await base.sleep(150);
  6885. if ($('.uc-button').length > 0) return;
  6886. if (config.base.num === base.getValue('setting_init_code') || config.base.license === base.getValue('setting_init_license')) {
  6887. this.addButton();
  6888. this.addPageListener();
  6889. } else {
  6890. this.addInitButton();
  6891. }
  6892. });
  6893. doc.on('click', '.pl-button-mode', function (e) {
  6894. mode = e.target.dataset.mode;
  6895. if (!mode) return;
  6896. $uc.getLink();
  6897. });
  6898. doc.on('click', '.pl-button-save', async function (e) {
  6899. e.preventDefault();
  6900. selectList = $uc.getSelectedList();
  6901. if (selectList.length === 0) {
  6902. return message.error('提示:<br/>请勾选要保存到网盘的文件哦~');
  6903. }
  6904. message.info('提示:<br/>因网盘限制,请保存到自己网盘后再去下载哦~');
  6905. await base.sleep(500);
  6906. document.querySelector('.file-info_r').click();
  6907. });
  6908. doc.on('click', '.listener-link-api.browser', async function (e) {
  6909. e.preventDefault();
  6910. let dataset = e.currentTarget.dataset;
  6911. let href = dataset.link;
  6912. $('#downloadIframe').attr('src', href);
  6913. });
  6914. doc.on('click', '.listener-link-api.blob', async function (e) {
  6915. e.preventDefault();
  6916.  
  6917. const o = _factory(e);
  6918. const $width = o.item.find('.pl-progress-inner');
  6919. const $text = o.item.find('.pl-progress-inner-text');
  6920. const filename = o.link[0].dataset.filename;
  6921. const index = o.link[0].dataset.index;
  6922. const size = Number(o.link[0].dataset.size) || 0;
  6923.  
  6924. base._resetData(index);
  6925. base.get(e.currentTarget.dataset.link, undefined, 'blob', { filename, index });
  6926.  
  6927. let startTime = Date.now();
  6928. let prevLoaded = 0;
  6929. let prevTime = startTime;
  6930.  
  6931. ins[index] = setInterval(async function () {
  6932. const prog = +progress[index] || 0;
  6933. const currentTime = Date.now();
  6934. const elapsedTime = currentTime - startTime;
  6935. const loaded = prog * size / 100;
  6936. const timeDiff = Math.max(currentTime - prevTime, 1); // 避免除零
  6937. const speed = ((loaded - prevLoaded) / (timeDiff / 1000)) || 0;
  6938.  
  6939. // 计算剩余时间(保护除零)
  6940. const totalProgress = Math.max(prog / 100, 0.01);
  6941. const totalElapsedSeconds = elapsedTime / 1000;
  6942. const estTotalTime = totalElapsedSeconds / totalProgress;
  6943. const remainingTime = estTotalTime - totalElapsedSeconds;
  6944.  
  6945. // 更新界面状态
  6946. o.link.hide();
  6947. o.directLink.hide();
  6948. o.tip.hide();
  6949. o.stop.show();
  6950. o.copy.hide();
  6951. o.progress.show();
  6952.  
  6953. // 更新进度条
  6954. $width.css('width', `${prog}%`);
  6955. $text.text(`${prog.toFixed(1)}% | 速度:${base.sizeFormat(speed)} | 剩余:${base.rtimeFormat(remainingTime)}`);
  6956.  
  6957. // 更新历史值
  6958. prevLoaded = loaded;
  6959. prevTime = currentTime;
  6960.  
  6961. // 下载完成
  6962. if (prog >= 100) {
  6963. await base.sleep(1000);
  6964. clearInterval(ins[index]);
  6965. progress[index] = 0;
  6966. o.item.find('.pl-progress-stop').hide();
  6967. $text.text('下载完成~ 浏览器下载框应该弹出来了哦~');
  6968. o.back.show();
  6969. await base.sleep(3000);
  6970. o.link.text('增强下载(基于浏览器文件流)').animate({ opacity: '1' }, "slow");
  6971. }
  6972. }, 500);
  6973. });
  6974. doc.on('click', '.listener-retry', async function (e) {
  6975. let o = _factory(e);
  6976. o.tip.hide();
  6977. o.link.show();
  6978. o.directLink.show();
  6979. });
  6980. doc.on('click', '.listener-stop', async function (e) {
  6981. let o = _factory(e);
  6982. let index = o.link[0].dataset.index;
  6983. if (request[index]) {
  6984. request[index].abort();
  6985. clearInterval(ins[index]);
  6986. o.item.find('.pl-progress-inner-text').text('正在取消...');
  6987. o.item.find('.pl-progress-inner').css('width', 100 + '%');
  6988. setTimeout(function () {
  6989. o.tip.hide();
  6990. o.back.hide();
  6991. o.link.show();
  6992. o.directLink.show();
  6993. o.copy.show();
  6994. o.progress.hide();
  6995. o.stop.hide();
  6996. }, 1050)
  6997. }
  6998. });
  6999. doc.on('click', '.listener-back', async function (e) {
  7000. let o = _factory(e);
  7001. o.progress.hide();
  7002. o.tip.hide();
  7003. o.link.show();
  7004. o.directLink.show();
  7005. o.copy.show();
  7006. o.stop.hide();
  7007. o.back.hide();
  7008. });
  7009. doc.on('click', '.listener-download-all', function (e) {
  7010. $('.pl-item-link.blob').each(function () {
  7011. if ($(this).css('display') !== 'none') {
  7012. $(this).click();
  7013. }
  7014. });
  7015. $(e.target).text('下载开始,下载进度见上方按钮哦~').animate({ opacity: '0.5' }, "slow");
  7016. setTimeout(function () {
  7017. $(e.target).text('全部增强下载').animate({ opacity: '1' }, "slow");
  7018. }, 2000)
  7019. });
  7020. doc.on('click', '.listener-link-aria, .listener-copy-all', function (e) {
  7021. e.preventDefault();
  7022. base.setClipboard(decodeURIComponent(e.target.dataset.link));
  7023. $(e.target).text('复制成功').animate({ opacity: '0.5' }, "slow");
  7024. setTimeout(function () {
  7025. $(e.target).text('重新复制').animate({ opacity: '1' }, "slow");
  7026. }, 2000)
  7027. });
  7028. doc.on('click', '.listener-link-rpc', async function (e) {
  7029. let target = $(e.currentTarget);
  7030.  
  7031. target.find('.icon-rpc-devices').remove();
  7032. target.find('.pl-loading').remove();
  7033. target.prepend(base.createLoading());
  7034.  
  7035. let res = await base.sendLinkToRPC(e.currentTarget.dataset.link, e.currentTarget.dataset.filename, [`Cookie: ${document.cookie}`]);
  7036. if (res === 'success') {
  7037. $('.listener-rpc-task').show();
  7038. target.removeClass('pl-btn-danger').html('发送成功了!快去看看吧~').animate({ opacity: '0.5' }, "slow");
  7039. } else if (res === 'assistant') {
  7040. target.addClass('pl-btn-danger').html(`${config.base.assistant.message}👉<a href="${config.base.assistant.link}" target="_blank" class="pl-a">点击此处安装</a>👈`);
  7041. } else {
  7042. target.addClass('pl-btn-danger').text('发送失败,检查一下您的RPC配置信息哦!').animate({ opacity: '0.5' }, "slow");
  7043. }
  7044. });
  7045. doc.on('click', '.listener-send-rpc', function (e) {
  7046. $('.listener-link-rpc').click();
  7047. $(e.target).text('发送完成,发送结果见上方按钮哦~').animate({ opacity: '0.5' }, "slow");
  7048. });
  7049. doc.on('click', '.listener-open-setting', function () {
  7050. base.showSetting();
  7051. });
  7052. doc.on('click', '.listener-open-updatelog', function () {
  7053. base.showUpdate();
  7054. });
  7055. doc.on('click', '.listener-open-beautify', function () {
  7056. base.showBeautify();
  7057. });
  7058. doc.on('click', '.listener-rpc-task', function () {
  7059. let rpc = JSON.stringify({
  7060. domain: base.getValue('setting_rpc_domain'),
  7061. port: base.getValue('setting_rpc_port'),
  7062. }), url = `${config.base.service.rpc}/?rpc=${base.encodeBase(rpc)}#${base.getValue('setting_rpc_token')}`;
  7063. GM_openInTab(url, { active: true });
  7064. });
  7065. },
  7066.  
  7067. greenerPage() {
  7068. base.waitForKeyElements('[class*="VideoDetail--content-footer"]', function (tag) {
  7069. tag.children().each(function () {
  7070. const $child = $(this);
  7071. if ($child.text().includes('手机客户端')) {
  7072. $child.hide();
  7073. }
  7074. });
  7075. }, true);
  7076. base.waitForKeyElements('[class*="PCLandingBanner--ad-block"]', function (tag) {
  7077. tag.hide();
  7078. }, true);
  7079. },
  7080.  
  7081. svg: 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjIiIGhlaWdodD0iMjIiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGcgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIiBzdHJva2U9IiNGRkZGRkYiIHN0cm9rZS13aWR0aD0iMiI+PHBhdGggc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBkPSJNOSAxMmwyIDIgMi0yeiIvPjxwYXRoIGQ9Ik0xNCA4aDEuNTUzYy44NSAwIDEuMTYuMDkzIDEuNDcuMjY3LjMxMS4xNzQuNTU2LjQzLjcyMi43NTYuMTY2LjMyNi4yNTUuNjUuMjU1IDEuNTR2NC44NzNjMCAuODkyLS4wODkgMS4yMTUtLjI1NSAxLjU0LS4xNjYuMzI3LS40MS41ODMtLjcyMi43NTctLjMxLjE3NC0uNjIuMjY3LTEuNDcuMjY3SDYuNDQ3Yy0uODUgMC0xLjE2LS4wOTMtMS40Ny0uMjY3YTEuNzc4IDEuNzc4IDAgMDEtLjcyMi0uNzU2Yy0uMTY2LS4zMjYtLjI1NS0uNjUtLjI1NS0xLjU0di00Ljg3M2MwLS44OTIuMDg5LTEuMjE1LjI1NS0xLjU0LjE2Ni0uMzI3LjQxLS41ODMuNzIyLS43NTcuMzEtLjE3NC42Mi0uMjY3IDEuNDctLjI2N0gxMSIvPjxwYXRoIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgZD0iTTExIDN2MTAiLz48L2c+PC9zdmc+',
  7082. addButton() {
  7083. base.waitForKeyElements(config.$uc.mount.home, (element) => {
  7084. page = $uc.detectPage();
  7085. if ($(".pl-button").length > 0 || !page || page !== 'home') return;
  7086. let $button = $(`<div class="ant-dropdown-trigger pl-button">
  7087. <button type="button" class="uc-button ant-btn btn-file ant-btn-primary">
  7088. <img class="uc-btn-icon" src="${$uc.svg}"><span>下载助手</span>
  7089. </button>
  7090. <ul class="pl-dropdown-menu" style="top: 39px;">
  7091. <li class="pl-dropdown-menu-item pl-button-mode" data-mode="api">API 下载</li>
  7092. <li class="pl-dropdown-menu-item pl-button-mode" data-mode="aria">Aria 下载</li>
  7093. <li class="pl-dropdown-menu-item pl-button-mode" data-mode="rpc">RPC 下载</li>
  7094. <li class="pl-dropdown-menu-item pl-button-mode" data-mode="curl">cURL 下载</li>
  7095. <li class="pl-dropdown-menu-item pl-button-mode" data-mode="bc">BC 下载</li>
  7096. <li class="pl-dropdown-menu-item pl-button-mode listener-open-setting">助手设置</li>
  7097. <li class="pl-dropdown-menu-item pl-button-mode listener-open-beautify">助手美化</li>
  7098. <li class="pl-dropdown-menu-item pl-button-mode listener-open-updatelog">更新日志</li>
  7099. </ul>
  7100. </div>`);
  7101. $button.css({ "margin-right": "10px", "display": "inline-block" });
  7102. element.prepend($button);
  7103. })
  7104. base.waitForKeyElements(config.$uc.mount.share, (element) => {
  7105. page = $uc.detectPage();
  7106. if ($(".pl-button").length > 0 || !page || page !== 'share') return;
  7107. let $button = $(`<div class="ant-dropdown-trigger pl-button"><button type="button" class="uc-button ant-btn btn-file ant-btn-primary" style="height: 40px;"><img class="uc-btn-icon" src="${$uc.svg}"><span>下载助手</span></button><ul class="pl-dropdown-menu"><li class="pl-dropdown-menu-item pl-button-mode pl-button-save"><span class="save-btn-icon"></span>保存后下载</li><li class="pl-dropdown-menu-item pl-button-mode listener-open-setting">助手设置</li><li class="pl-dropdown-menu-item pl-button-mode listener-open-beautify">助手美化</li><li class="pl-dropdown-menu-item pl-button-mode listener-open-updatelog">更新日志</li></ul></div>`);
  7108. $button.css({ "margin-left": "10px", "display": "inline-block" });
  7109. element.append($button);
  7110. })
  7111. },
  7112.  
  7113. addInitButton() {
  7114. let $button = $(`<div class="ant-dropdown-trigger pl-button-init"><button type="button" class="uc-button ant-btn btn-file ant-btn-primary" style="height: 40px;"><img class="uc-btn-icon" src="${$uc.svg}"><span>点我点亮</span></button></div>`);
  7115. $button.click(function () { base.initDialog() });
  7116. base.waitForKeyElements(config.$uc.mount.home, (element) => {
  7117. page = $uc.detectPage();
  7118. if ($(".pl-button-init").length > 0 || !page || page !== 'home') return;
  7119. $button.css({ "margin-right": "10px", "display": "inline-block" });
  7120. element.prepend($button);
  7121. })
  7122. base.waitForKeyElements(config.$uc.mount.share, (element) => {
  7123. page = $uc.detectPage();
  7124. if ($(".pl-button-init").length > 0 || !page || page !== 'share') return;
  7125. $button.css({ "margin-left": "10px", "display": "inline-block" });
  7126. element.append($button);
  7127. })
  7128. },
  7129.  
  7130. async getLink() {
  7131. Swal.fire({
  7132. showConfirmButton: false,
  7133. allowOutsideClick: false,
  7134. allowEscapeKey: false,
  7135. allowEnterKey: false,
  7136. title: "获取中",
  7137. html: `...`,
  7138. footer: "如果选的文件较多,请耐心等待获取完成哦!",
  7139. customClass: {
  7140. popup: 'loading-popup',
  7141. header: 'loading-header',
  7142. title: 'loading-title',
  7143. content: 'loading-content',
  7144. input: 'loading-input',
  7145. footer: 'loading-footer'
  7146. },
  7147. willOpen: function () {
  7148. Swal.showLoading();
  7149. },
  7150. ...swalDefault
  7151. });
  7152. selectList = this.getSelectedList();
  7153. if (selectList.length === 0) {
  7154. return message.error('提示:<br/>请勾选要下载的文件哦~');
  7155. }
  7156. if (this.isOnlyFolder()) {
  7157. return message.error('提示:<br/>请打开文件夹后再勾选文件~');
  7158. }
  7159. if (page === 'home') {
  7160. let data = [];
  7161. let batchSize = 15;
  7162. let processed = 0;
  7163. selectList = selectList.filter(item => item.file === true)
  7164.  
  7165. for (let i = 0; i < selectList.length; i += batchSize) {
  7166. // 获取当前批次文件
  7167. let batch = selectList.slice(i, i + batchSize);
  7168. console.log()
  7169. let fids = batch.map(item => item.fid);
  7170.  
  7171. // 发起请求获取链接
  7172. let res = await base.post(config.$uc.api.getLink, { "fids": fids }, { "content-type": "application/json;charset=utf-8", "user-agent": config.$quark.api.ua.downloadLink });
  7173.  
  7174. if (res?.code === 31001) {
  7175. return message.error('提示:<br/>请先登录网盘~<br/>代码:' + res.code);
  7176. }
  7177. if (res?.code !== 0) {
  7178. return message.error('提示:<br/>获取链接失败了~<br/>代码:' + res.code);
  7179. }
  7180.  
  7181. // 合并响应数据
  7182. if (res?.data) {
  7183. data.push(...res.data);
  7184. }
  7185.  
  7186. // 更新处理进度
  7187. processed += batch.length;
  7188.  
  7189. // 更新UI显示
  7190. doc.find('.loading-popup .loading-title').html(`链接获取中`);
  7191. doc.find('.loading-popup .swal2-html-container').html(`<div>已获取 ${processed} / ${selectList.length} 个链接~</div>`);
  7192.  
  7193. // 请求间隔节流
  7194. await base.sleep(1000);
  7195. }
  7196. let html = this.generateDom(data);
  7197. base.showMainDialog(config.base.dom.button[mode].title, html, config.base.dom.button[mode].footer);
  7198. } else {
  7199. return message.error('提示:<br/>页面错误~');
  7200. }
  7201. },
  7202.  
  7203. generateDom(list) {
  7204. if (!list) {
  7205. return message.error('提示:<br/>获取下载链接失败,刷新网页后再试试吧~');
  7206. }
  7207. let content = '<div class="pl-main">';
  7208. let alinkAllText = '';
  7209. list.forEach((v, i) => {
  7210. if (v.file === false) return;
  7211. let filename = v.file_name;
  7212. let fid = v.fid;
  7213. let size = base.sizeFormat(v.size);
  7214. let dlink = v.download_url;
  7215. if (!dlink || !dlink.includes("http")) {
  7216. content += `<div class="pl-item">
  7217. <div class="pl-item-name listener-tip" data-size="${size}">${filename}</div>
  7218. <div class="pl-item-tip">获取下载链接失败,刷新网页后再试试吧~</div>
  7219. </div>`;
  7220. } else {
  7221. if (mode === 'api') {
  7222. alinkAllText += dlink + '\r\n';
  7223. content += `<div class="pl-item">
  7224. <div class="pl-item-name listener-tip" data-size="${size}">${filename}</div>
  7225. <button class="pl-item-link listener-tip pl-btn-primary pl-btn-default listener-link-api blob" data-title="不建议使用本功能,若文件过大下载完成后有可能不会弹出窗口,此时请换用“RPC 下载 + Mortix”的组合<br/>基于浏览器的 Blob 文件流下载文件,适用于较新的浏览器,可以在此窗口中显示下载剩余时间和下载速度,此方式下载不会被 IDM 捕获下载链接" data-filename="${filename}" data-size="${v.size}" data-link="${dlink}" data-fid="${fid}" data-index="${i}">增强下载(基于浏览器文件流)</button>
  7226. <button class="pl-item-link listener-tip pl-btn-primary pl-btn-info listener-link-api browser" data-title="基于浏览器直接打开链接来下载文件,适用于较为古老但支持 iframe 的浏览器,点击“直接下载”后需等待下载提示弹出才能点击下个“直接下载”,否则只会下载后者,此方式下载有可能会被 IDM 捕获下载链接" data-filename="${filename}" data-link="${dlink}" data-fid="${fid}">直接下载(基于浏览器链接)</button>
  7227. <button class="pl-item-copy pl-btn-primary pl-btn-warning listener-copy-all" href="${dlink}" data-filename="${filename}" data-link="${dlink}">复制链接</button>
  7228. <div class="pl-item-progress" style="display: none">
  7229. <div class="pl-progress">
  7230. <div class="pl-progress-outer"></div>
  7231. <div class="pl-progress-inner" style="width:5%">
  7232. <div class="pl-progress-inner-text">正在加载进度...0%</div>
  7233. </div>
  7234. </div>
  7235. <button class="pl-btn-primary pl-btn-danger pl-progress-stop listener-stop">取消下载</button>
  7236. <button class="pl-btn-primary pl-btn-info pl-progress-back listener-back" style="display: none">返回</button>
  7237. </div>
  7238. </div>`;
  7239. }
  7240. if (mode === 'aria') {
  7241. let alink = base.convertLinkToAria(dlink, filename, `--header "Cookie: ${document.cookie}"`);
  7242. alinkAllText += alink + '\r\n';
  7243. content += `<div class="pl-item">
  7244. <div class="pl-item-name listener-tip" data-size="${size}">${filename}</div>
  7245. <a class="pl-item-link listener-link-aria" href="${alink}" title="点击复制 aria2c 命令行" data-filename="${filename}" data-link="${alink}">${decodeURIComponent(alink)}<br/>复制 ${filename} 下载命令行</a>
  7246. </div>`;
  7247. }
  7248. if (mode === 'rpc') {
  7249. content += `<div class="pl-item">
  7250. <div class="pl-item-name listener-tip" data-size="${size}">${filename}</div>
  7251. <button class="pl-item-link listener-link-rpc pl-btn-primary pl-btn-info" data-filename="${filename}" data-link="${dlink}"><svg class="icon-rpc-devices" viewBox="-10 0 1034 1024"><g transform="matrix(1 0 0 -1 0 960)"><path fill="currentColor" d="M832 -64h-640q-53 0 -90.5 37.5t-37.5 90.5v768q0 53 37.5 90.5t90.5 37.5h640q53 0 90.5 -37.5t37.5 -90.5v-768q0 -53 -37.5 -90.5t-90.5 -37.5zM832 768q0 27 -18.5 45.5t-45.5 18.5h-512q-27 0 -45.5 -18.5t-18.5 -45.5v-320h640v320v0zM832 320h-640v-192q0 -27 18.5 -45.5t45.5 -18.5h512q27 0 45.5 18.5t18.5 45.5v192v0zM512 128q-27 0 -45.5 18.5t-18.5 45.5t18.5 45.5t45.5 18.5t45.5 -18.5t18.5 -45.5t-18.5 -45.5t-45.5 -18.5z" /></g></svg><span style="margin-left: 5px;">将 ${filename} 推送到 RPC 下载器</span></button>
  7252. </div>`;
  7253. }
  7254. if (mode === 'curl') {
  7255. let alink = base.convertLinkToCurl(dlink, filename, `-b "${document.cookie}"`);
  7256. alinkAllText += alink + '\r\n';
  7257. content += `<div class="pl-item">
  7258. <div class="pl-item-name listener-tip" data-size="${size}">${filename}</div>
  7259. <a class="pl-item-link listener-link-aria" href="${alink}" title="点击复制 curl 命令行" data-filename="${filename}" data-link="${alink}">${decodeURIComponent(alink)}<br/>复制 ${filename} 下载命令行</a>
  7260. </div>`;
  7261. }
  7262. if (mode === 'bc') {
  7263. let alink = base.convertLinkToBC(dlink, filename, `cookie=${encodeURIComponent(document.cookie)}`);
  7264. alinkAllText += alink + '\r\n';
  7265. content += `<div class="pl-item">
  7266. <div class="pl-item-name listener-tip" data-size="${size}">${filename}</div>
  7267. <a class="pl-item-link" href="${decodeURIComponent(alink)}" title="点击用比特彗星下载" data-filename="${filename}" data-link="${alink}">${decodeURIComponent(alink)}<br/>下载 ${filename}</a>
  7268. </div>`;
  7269. }
  7270. }
  7271. });
  7272. content += '</div>';
  7273.  
  7274. if (mode === 'rpc') {
  7275. content += `<div class="pl-extra">`
  7276. }
  7277. if (list.length >= 2) {
  7278. if (mode === 'api')
  7279. content += `<div class="pl-extra"><button class="pl-btn-primary listener-tip listener-download-all blob" data-title="不建议使用本功能,若文件过大下载完成后有可能不会弹出窗口,此时请换用“RPC 下载 + Mortix”的组合<br/>基于浏览器的 Blob 文件流下载文件,适用于较新的浏览器,可以在此窗口中显示下载剩余时间和下载速度,此方式下载不会被 IDM 捕获下载链接">全部增强下载</button><button class="pl-btn-primary listener-tip listener-copy-all" data-link="${alinkAllText}" data-title="不建议使用本功能,在本网盘单独复制链接并粘贴下载可能会导致服务器回报 403 错误">复制全部链接</button></div>`;
  7280. if (mode === 'aria')
  7281. content += `<div class="pl-extra"><button class="pl-btn-primary listener-copy-all" data-link="${alinkAllText}">复制全部命令行</button></div>`;
  7282. if (mode === 'rpc') {
  7283. content += `<button class="pl-btn-primary listener-send-rpc">发送全部链接</button>`;
  7284. }
  7285. if (mode === 'curl') {
  7286. content += `<div class="pl-extra"><button class="pl-btn-primary listener-copy-all" data-link="${alinkAllText}">复制全部命令行</button><button class="pl-btn-primary pl-btn-warning listener-open-setting" style="margin-left: 10px;">修改终端类型(${terminalType[base.getValue('setting_terminal_type')]})</button></div>`;
  7287. }
  7288. if (mode === 'bc') {
  7289. content += `<div class="pl-extra"><button class="pl-btn-primary listener-copy-all" data-link="${alinkAllText}">复制全部链接</button></div>`;
  7290. }
  7291. }
  7292. if (mode === 'rpc') {
  7293. let rpc = base.getValue('setting_rpc_domain') + ':' + base.getValue('setting_rpc_port') + base.getValue('setting_rpc_path');
  7294. content += `<button title="${rpc}" class="pl-btn-primary pl-btn-warning listener-open-setting" style="margin-left: 10px">修改 RPC 参数(${rpc})</button>
  7295. <button class="pl-btn-primary pl-btn-success pl-btn-opacity listener-rpc-task" style="margin-left: 10px;display: none">查看下载任务</button>
  7296. </div>`;
  7297. }
  7298.  
  7299. return content;
  7300. },
  7301.  
  7302. getSelectedList() {
  7303. try {
  7304. let selectedList = [];
  7305. let reactDom = document.getElementsByClassName('file-list')[0];
  7306. let reactObj = base.findReact(reactDom);
  7307. let props = reactObj.props;
  7308. if (props) {
  7309. let fileList = props.list || [];
  7310. let selectedKeys = props.selectedRowKeys || [];
  7311. fileList.forEach(function (val) {
  7312. if (selectedKeys.includes(val.fid)) {
  7313. selectedList.push(val);
  7314. }
  7315. });
  7316. }
  7317. return selectedList;
  7318. } catch (e) {
  7319. return [];
  7320. }
  7321. },
  7322.  
  7323. detectPage() {
  7324. let path = location.pathname;
  7325. if (/^\/(list)/.test(path)) return 'home';
  7326. if (/^\/(s|share)\//.test(path)) return 'share';
  7327. return '';
  7328. },
  7329.  
  7330. isOnlyFolder() {
  7331. for (let i = 0; i < selectList.length; i++) {
  7332. if (selectList[i].file) return false;
  7333. }
  7334. return true;
  7335. },
  7336.  
  7337. async initPanLinker() {
  7338. page = this.detectPage();
  7339. base.createTip();
  7340. base.registerMenuCommand();
  7341. if (config.base.num === base.getValue('setting_init_code') || config.base.license === base.getValue('setting_init_license')) {
  7342. this.addButton();
  7343. this.addPageListener();
  7344. } else {
  7345. this.addInitButton();
  7346. }
  7347. }
  7348. };
  7349.  
  7350. /**
  7351. * 123云盘
  7352. * @author 油小猴
  7353. * @author hmjz100
  7354. */
  7355. let $123pan = {
  7356. addPageListener() {
  7357. /*
  7358. 防止代码因其他原因被执行多次
  7359. 这段代码出自 Via轻插件,作者谷花泰
  7360. */
  7361. const key = encodeURIComponent('LinkSwift:123云盘');
  7362. if (window[key]) return;
  7363. window[key] = true;
  7364.  
  7365. function _factory(e) {
  7366. let target = $(e.target);
  7367. let item = target.parents('.pl-item');
  7368. let link = item.find('.pl-item-link.blob');
  7369. let directLink = item.find('.pl-item-link.browser');
  7370. let progress = item.find('.pl-item-progress');
  7371. let tip = item.find('.pl-item-tip');
  7372. let copy = item.find('.pl-item-copy');
  7373. let back = item.find('.pl-progress-back');
  7374. let stop = item.find('.pl-progress-stop');
  7375. return {
  7376. item, link, directLink, progress, tip, copy, back, stop, target,
  7377. };
  7378. }
  7379.  
  7380. doc.on('click', '.pl-button-mode', function (e) {
  7381. mode = e.target.dataset.mode;
  7382. if (!mode) return;
  7383. $123pan.getLink();
  7384. });
  7385. doc.on('click', '.pl-button-save', async function (e) {
  7386. e.preventDefault();
  7387. selectList = $123pan.getSelectedList();
  7388. if (selectList.length === 0) {
  7389. return message.error('提示:<br/>请勾选要保存到网盘的文件哦~');
  7390. }
  7391. message.info('提示:<br/>因网盘限制,请保存到自己网盘后再去下载哦~');
  7392. await base.sleep(500);
  7393. document.querySelector('.file-info_r').click();
  7394. });
  7395. doc.on('click', '.listener-link-api.browser', async function (e) {
  7396. e.preventDefault();
  7397. let dataset = e.currentTarget.dataset;
  7398. let href = dataset.link;
  7399. $('#downloadIframe').attr('src', href);
  7400. });
  7401. doc.on('click', '.listener-link-api.blob', async function (e) {
  7402. e.preventDefault();
  7403.  
  7404. const o = _factory(e);
  7405. const $width = o.item.find('.pl-progress-inner');
  7406. const $text = o.item.find('.pl-progress-inner-text');
  7407. const filename = o.link[0].dataset.filename;
  7408. const index = o.link[0].dataset.index;
  7409. const size = Number(o.link[0].dataset.size) || 0;
  7410.  
  7411. base._resetData(index);
  7412. base.get(e.currentTarget.dataset.link, undefined, 'blob', { filename, index });
  7413.  
  7414. let startTime = Date.now();
  7415. let prevLoaded = 0;
  7416. let prevTime = startTime;
  7417.  
  7418. ins[index] = setInterval(async function () {
  7419. const prog = +progress[index] || 0;
  7420. const currentTime = Date.now();
  7421. const elapsedTime = currentTime - startTime;
  7422. const loaded = prog * size / 100;
  7423. const timeDiff = Math.max(currentTime - prevTime, 1); // 避免除零
  7424. const speed = ((loaded - prevLoaded) / (timeDiff / 1000)) || 0;
  7425.  
  7426. // 计算剩余时间(保护除零)
  7427. const totalProgress = Math.max(prog / 100, 0.01);
  7428. const totalElapsedSeconds = elapsedTime / 1000;
  7429. const estTotalTime = totalElapsedSeconds / totalProgress;
  7430. const remainingTime = estTotalTime - totalElapsedSeconds;
  7431.  
  7432. // 更新界面状态
  7433. o.link.hide();
  7434. o.directLink.hide();
  7435. o.tip.hide();
  7436. o.stop.show();
  7437. o.copy.hide();
  7438. o.progress.show();
  7439.  
  7440. // 更新进度条
  7441. $width.css('width', `${prog}%`);
  7442. $text.text(`${prog.toFixed(1)}% | 速度:${base.sizeFormat(speed)} | 剩余:${base.rtimeFormat(remainingTime)}`);
  7443.  
  7444. // 更新历史值
  7445. prevLoaded = loaded;
  7446. prevTime = currentTime;
  7447.  
  7448. // 下载完成
  7449. if (prog >= 100) {
  7450. await base.sleep(1000);
  7451. clearInterval(ins[index]);
  7452. progress[index] = 0;
  7453. o.item.find('.pl-progress-stop').hide();
  7454. $text.text('下载完成~ 浏览器下载框应该弹出来了哦~');
  7455. o.back.show();
  7456. await base.sleep(3000);
  7457. o.link.text('增强下载(基于浏览器文件流)').animate({ opacity: '1' }, "slow");
  7458. }
  7459. }, 500);
  7460. });
  7461. doc.on('click', '.listener-retry', async function (e) {
  7462. let o = _factory(e);
  7463. o.tip.hide();
  7464. o.link.show();
  7465. o.directLink.show();
  7466. });
  7467. doc.on('click', '.listener-stop', async function (e) {
  7468. let o = _factory(e);
  7469. let index = o.link[0].dataset.index;
  7470. if (request[index]) {
  7471. request[index].abort();
  7472. clearInterval(ins[index]);
  7473. o.item.find('.pl-progress-inner-text').text('正在取消...');
  7474. o.item.find('.pl-progress-inner').css('width', 100 + '%');
  7475. setTimeout(function () {
  7476. o.tip.hide();
  7477. o.back.hide();
  7478. o.link.show();
  7479. o.directLink.show();
  7480. o.copy.show();
  7481. o.progress.hide();
  7482. o.stop.hide();
  7483. }, 1050)
  7484. }
  7485. });
  7486. doc.on('click', '.listener-back', async function (e) {
  7487. let o = _factory(e);
  7488. o.progress.hide();
  7489. o.tip.hide();
  7490. o.link.show();
  7491. o.directLink.show();
  7492. o.copy.show();
  7493. o.stop.hide();
  7494. o.back.hide();
  7495. });
  7496. doc.on('click', '.listener-download-all', function (e) {
  7497. $('.pl-item-link.blob').each(function () {
  7498. if ($(this).css('display') !== 'none') {
  7499. $(this).click();
  7500. }
  7501. });
  7502. $(e.target).text('下载开始,下载进度见上方按钮哦~').animate({ opacity: '0.5' }, "slow");
  7503. setTimeout(function () {
  7504. $(e.target).text('全部增强下载').animate({ opacity: '1' }, "slow");
  7505. }, 2000)
  7506. });
  7507. doc.on('click', '.listener-link-aria, .listener-copy-all', function (e) {
  7508. e.preventDefault();
  7509. base.setClipboard(decodeURIComponent(e.target.dataset.link));
  7510. $(e.target).text('复制成功').animate({ opacity: '0.5' }, "slow");
  7511. setTimeout(function () {
  7512. $(e.target).text('重新复制').animate({ opacity: '1' }, "slow");
  7513. }, 2000)
  7514. });
  7515. doc.on('click', '.listener-link-rpc', async function (e) {
  7516. let target = $(e.currentTarget);
  7517.  
  7518. target.find('.icon-rpc-devices').remove();
  7519. target.find('.pl-loading').remove();
  7520. target.prepend(base.createLoading());
  7521.  
  7522. let res = await base.sendLinkToRPC(e.currentTarget.dataset.link, e.currentTarget.dataset.filename);
  7523. if (res === 'success') {
  7524. $('.listener-rpc-task').show();
  7525. target.removeClass('pl-btn-danger').html('发送成功了!快去看看吧~').animate({ opacity: '0.5' }, "slow");
  7526. } else if (res === 'assistant') {
  7527. target.addClass('pl-btn-danger').html(`${config.base.assistant.message}👉<a href="${config.base.assistant.link}" target="_blank" class="pl-a">点击此处安装</a>👈`);
  7528. } else {
  7529. target.addClass('pl-btn-danger').text('发送失败,检查一下您的RPC配置信息哦!').animate({ opacity: '0.5' }, "slow");
  7530. }
  7531. });
  7532. doc.on('click', '.listener-send-rpc', function (e) {
  7533. $('.listener-link-rpc').click();
  7534. $(e.target).text('发送完成,发送结果见上方按钮哦~').animate({ opacity: '0.5' }, "slow");
  7535. });
  7536. doc.on('click', '.listener-open-setting', function () {
  7537. base.showSetting();
  7538. });
  7539. doc.on('click', '.listener-open-updatelog', function () {
  7540. base.showUpdate();
  7541. });
  7542. doc.on('click', '.listener-open-beautify', function () {
  7543. base.showBeautify();
  7544. });
  7545. doc.on('click', '.listener-rpc-task', function () {
  7546. let rpc = JSON.stringify({
  7547. domain: base.getValue('setting_rpc_domain'),
  7548. port: base.getValue('setting_rpc_port'),
  7549. }), url = `${config.base.service.rpc}/?rpc=${base.encodeBase(rpc)}#${base.getValue('setting_rpc_token')}`;
  7550. GM_openInTab(url, { active: true });
  7551. });
  7552. },
  7553.  
  7554. greenerPage() {
  7555. base.waitForKeyElements(".new-menu-item-image, .special-menu-item-container-migration--label, .sider-member-btn, .video-new-user-tips", (tag) => {
  7556. if (tag.is(":hidden")) return;
  7557. tag.hide();
  7558. }, true)
  7559. let allowedTexts = ["其他网盘数据转入", "同步空间", "回收站", "下载客户端"];
  7560. base.waitForKeyElements(`.ant-menu.ant-menu-root.ant-menu-inline[role="menu"]`, (tag) => {
  7561. tag.find(`[role="menuitem"]`).each(function () {
  7562. const menuText = $(this).text().trim();
  7563. if (!allowedTexts.includes(menuText) || $(this).is(":hidden")) return;
  7564. $(this).hide();
  7565. });
  7566. }, true)
  7567. base.waitForKeyElements(".special-menu-item-container", (tag) => {
  7568. tag.find(".special-menu-item-container-migration").each(function () {
  7569. const menuText = $(this).text().trim();
  7570. if (!allowedTexts.includes(menuText) || $(this).is(":hidden")) return;
  7571. $(this).hide();
  7572. });
  7573. }, true)
  7574. base.waitForKeyElements(`.header-btn-list`, (tag) => {
  7575. tag.find(`.btn-item`).each(function () {
  7576. const menuText = $(this).text().trim();
  7577. if (!allowedTexts.includes(menuText) || $(this).is(":hidden")) return;
  7578. $(this).hide();
  7579. });
  7580. }, true)
  7581. base.waitForKeyElements(`.rightInfo .register:not(.pl-button, .pl-button-init),
  7582. .homeClass > div > .ant-dropdown-trigger:not(.pl-button, .pl-button-init),
  7583. .homeClass > div > .sysbut`, function (tag) {
  7584. let hasTextNode = false;
  7585. tag.contents().each(function () {
  7586. if (this.nodeType === 3 && $.trim(this.textContent)) {
  7587. hasTextNode = true;
  7588. return;
  7589. }
  7590. });
  7591. if (!hasTextNode) return;
  7592. tag.css({ "width": "38px" });
  7593. tag.contents().each(function () {
  7594. if (this.nodeType === 3) {
  7595. $(this).remove();
  7596. }
  7597. });
  7598. tag.find('svg').css({ "margin-right": "0" });
  7599. });
  7600. base.waitForKeyElements('.rightInfo .qrcode_btn', function (tag) {
  7601. tag.hide();
  7602. }, true);
  7603. },
  7604.  
  7605. getToken() {
  7606. doc.find('.loading-popup .loading-title').html(`令牌获取中`);
  7607. doc.find('.loading-popup .swal2-html-container').html(`<div>正在获取令牌~</div>`);
  7608. let token = base.getStorage("authorToken");
  7609. return token;
  7610. },
  7611.  
  7612. async getLink() {
  7613. Swal.fire({
  7614. showConfirmButton: false,
  7615. allowOutsideClick: false,
  7616. allowEscapeKey: false,
  7617. allowEnterKey: false,
  7618. title: "获取中",
  7619. html: `...`,
  7620. footer: "如果选的文件较多,请耐心等待获取完成哦!",
  7621. customClass: {
  7622. popup: 'loading-popup',
  7623. header: 'loading-header',
  7624. title: 'loading-title',
  7625. content: 'loading-content',
  7626. input: 'loading-input',
  7627. footer: 'loading-footer'
  7628. },
  7629. willOpen: function () {
  7630. Swal.showLoading();
  7631. },
  7632. ...swalDefault
  7633. });
  7634. selectList = this.getSelectedList();
  7635. if (selectList.length === 0) {
  7636. return message.error('提示:<br/>请勾选要下载的文件哦~');
  7637. }
  7638. if (this.isOnlyFolder()) {
  7639. return message.error('提示:<br/>请打开文件夹后再勾选文件~');
  7640. }
  7641. console.log(selectList);
  7642. if (page === 'home') {
  7643. let token = this.getToken();
  7644. let batchSize = 15;
  7645. let processed = 0;
  7646. selectList = selectList.filter(item => item.Type === 0);
  7647. for (let i = 0; i < selectList.length; i += batchSize) {
  7648. let batch = selectList.slice(i, i + batchSize);
  7649. let queue = [];
  7650.  
  7651. doc.find('.loading-popup .loading-title').html(`链接获取中`);
  7652. doc.find('.loading-popup .swal2-html-container').html(`<div>正在获取文件对应的下载链接~</div>`);
  7653. batch.forEach((item, localIndex) => {
  7654. let globalIndex = i + localIndex;
  7655. queue.push(this.getFileUrlByOnce(item, globalIndex, token)
  7656. .then(val => {
  7657. processed++;
  7658. doc.find('.loading-popup .swal2-html-container').html(`<div>已获取 ${processed} / ${selectList.length} 个链接~</div>`);
  7659. return val;
  7660. }));
  7661. });
  7662.  
  7663. let res = await Promise.all(queue);
  7664. res.forEach(val => {
  7665. selectList[val.index].DownloadUrl = val.downloadUrl;
  7666. });
  7667.  
  7668. await base.sleep(1000);
  7669. }
  7670. let html = this.generateDom(selectList);
  7671. base.showMainDialog(config.base.dom.button[mode].title, html, config.base.dom.button[mode].footer);
  7672. } else if (page === 'share') {
  7673. let token = this.getToken();
  7674. let batchSize = 15;
  7675. let processed = 0;
  7676. selectList = selectList.filter(item => item.Type === 0);
  7677. let pathSplit = location.pathname.split('/').filter(Boolean);
  7678. let ShareKey = pathSplit[1];
  7679. console.log(selectList)
  7680. for (let i = 0; i < selectList.length; i += batchSize) {
  7681. let batch = selectList.slice(i, i + batchSize);
  7682. let queue = [];
  7683.  
  7684. doc.find('.loading-popup .loading-title').html(`链接获取中`);
  7685. doc.find('.loading-popup .swal2-html-container').html(`<div>正在获取文件对应的下载链接~</div>`);
  7686. batch.forEach((item, localIndex) => {
  7687. let globalIndex = i + localIndex;
  7688. queue.push(this.getFileUrlByOnce(item, globalIndex, token, ShareKey)
  7689. .then(val => {
  7690. processed++;
  7691. doc.find('.loading-popup .swal2-html-container').html(`<div>已获取 ${processed} / ${selectList.length} 个链接~</div>`);
  7692. return val;
  7693. }));
  7694. });
  7695.  
  7696. let res = await Promise.all(queue);
  7697. res.forEach(val => {
  7698. selectList[val.index].DownloadUrl = val.downloadUrl;
  7699. });
  7700.  
  7701. await base.sleep(1000);
  7702. }
  7703. let html = this.generateDom(selectList);
  7704. base.showMainDialog(config.base.dom.button[mode].title, html, config.base.dom.button[mode].footer);
  7705. } else {
  7706. return message.error('提示:<br/>页面错误~');
  7707. }
  7708. },
  7709.  
  7710. async getFileUrlByOnce(item, index, token, ShareKey) {
  7711. if (item.DownloadUrl) return {
  7712. index,
  7713. downloadUrl: item.DownloadUrl
  7714. };
  7715. let res = null;
  7716. if (ShareKey) {
  7717. res = await base.post(config.$123pan.api.getShareLink, {
  7718. "ShareKey": ShareKey,
  7719. "FileID": item.FileId,
  7720. "S3keyFlag": item.S3KeyFlag,
  7721. "Size": item.Size,
  7722. "Etag": item.Etag
  7723. }, {
  7724. "content-type": "application/json;charset=utf-8",
  7725. "authorization": `Bearer ${token}`,
  7726. "platform": "ios"
  7727. });
  7728. } else {
  7729. res = await base.post(config.$123pan.api.getLink, {
  7730. "driveId": 0,
  7731. "etag": item.Etag,
  7732. "fileId": item.FileId,
  7733. "s3keyFlag": item.S3KeyFlag,
  7734. "type": item.Type,
  7735. "fileName": item.FileName,
  7736. "size": item.Size
  7737. }, {
  7738. "content-type": "application/json;charset=utf-8",
  7739. "authorization": `Bearer ${token}`,
  7740. "platform": "ios"
  7741. });
  7742. }
  7743. if (res.data?.DownloadUrl) {
  7744. let url = res.data.DownloadUrl;
  7745. let surl = new URL(url).searchParams.get("params");
  7746. if (surl) url = base.decodeBase(surl);
  7747. url = await base.getFinalUrl(url);
  7748. return {
  7749. index,
  7750. downloadUrl: url
  7751. };
  7752. } else if (res.data?.DownloadURL) {
  7753. let url = res.data.DownloadURL;
  7754. let surl = new URL(url).searchParams.get("params");
  7755. if (surl) url = base.decodeBase(surl);
  7756. url = await base.getFinalUrl(url);
  7757. return {
  7758. index,
  7759. downloadUrl: url
  7760. };
  7761. } else if (res?.code === 5112) {
  7762. return message.error('提示:<br/>请先登录网盘后再获取链接呢~');
  7763. } else {
  7764. return {
  7765. index,
  7766. downloadUrl: '获取下载地址失败,刷新后再试试吧~'
  7767. };
  7768. }
  7769. },
  7770.  
  7771. generateDom(list) {
  7772. if (!list) {
  7773. return message.error('提示:<br/>获取下载链接失败,刷新网页后再试试吧~');
  7774. }
  7775. let content = '<div class="pl-main">';
  7776. let alinkAllText = '';
  7777. list.forEach((v, i) => {
  7778. if (v.Type !== 0) return;
  7779. let filename = v.FileName;
  7780. let fileid = v.FileId;
  7781. let size = base.sizeFormat(v.Size);
  7782. let dlink = v.DownloadUrl || v.DownloadURL;
  7783. if (!dlink || !dlink.includes("http")) {
  7784. content += `<div class="pl-item">
  7785. <div class="pl-item-name listener-tip" data-size="${size}">${filename}</div>
  7786. <div class="pl-item-tip">获取下载链接失败,刷新网页后再试试吧~</div>
  7787. </div>`;
  7788. } else {
  7789. if (mode === 'api') {
  7790. alinkAllText += dlink + '\r\n';
  7791. content += `<div class="pl-item">
  7792. <div class="pl-item-name listener-tip" data-size="${size}">${filename}</div>
  7793. <button class="pl-item-link listener-tip pl-btn-primary pl-btn-default listener-link-api blob" data-title="不建议使用本功能,若文件过大下载完成后有可能不会弹出窗口,此时请换用“RPC 下载 + Mortix”的组合<br/>基于浏览器的 Blob 文件流下载文件,适用于较新的浏览器,可以在此窗口中显示下载剩余时间和下载速度,此方式下载不会被 IDM 捕获下载链接" data-filename="${filename}" data-size="${v.size}" data-link="${dlink}" data-fid="${fileid}" data-index="${i}">增强下载(基于浏览器文件流)</button>
  7794. <button class="pl-item-link listener-tip pl-btn-primary pl-btn-info listener-link-api browser" data-title="基于浏览器直接打开链接来下载文件,适用于较为古老但支持 iframe 的浏览器,点击“直接下载”后需等待下载提示弹出才能点击下个“直接下载”,否则只会下载后者,此方式下载有可能会被 IDM 捕获下载链接" data-filename="${filename}" data-link="${dlink}" data-fid="${fileid}">直接下载(基于浏览器链接)</button>
  7795. <button class="pl-item-copy pl-btn-primary pl-btn-warning listener-copy-all" href="${dlink}" data-filename="${filename}" data-link="${dlink}">复制链接</button>
  7796. <div class="pl-item-progress" style="display: none">
  7797. <div class="pl-progress">
  7798. <div class="pl-progress-outer"></div>
  7799. <div class="pl-progress-inner" style="width:5%">
  7800. <div class="pl-progress-inner-text">正在加载进度...0%</div>
  7801. </div>
  7802. </div>
  7803. <button class="pl-btn-primary pl-btn-danger pl-progress-stop listener-stop">取消下载</button>
  7804. <button class="pl-btn-primary pl-btn-info pl-progress-back listener-back" style="display: none">返回</button>
  7805. </div>
  7806. </div>`;
  7807. }
  7808. if (mode === 'aria') {
  7809. let alink = base.convertLinkToAria(dlink, filename);
  7810. alinkAllText += alink + '\r\n';
  7811. content += `<div class="pl-item">
  7812. <div class="pl-item-name listener-tip" data-size="${size}">${filename}</div>
  7813. <a class="pl-item-link listener-link-aria" href="${alink}" title="点击复制 aria2c 命令行" data-filename="${filename}" data-link="${alink}">${decodeURIComponent(alink)}<br/>复制 ${filename} 下载命令行</a>
  7814. </div>`;
  7815. }
  7816. if (mode === 'rpc') {
  7817. content += `<div class="pl-item">
  7818. <div class="pl-item-name listener-tip" data-size="${size}">${filename}</div>
  7819. <button class="pl-item-link listener-link-rpc pl-btn-primary pl-btn-info" data-filename="${filename}" data-link="${dlink}"><svg class="icon-rpc-devices" viewBox="-10 0 1034 1024"><g transform="matrix(1 0 0 -1 0 960)"><path fill="currentColor" d="M832 -64h-640q-53 0 -90.5 37.5t-37.5 90.5v768q0 53 37.5 90.5t90.5 37.5h640q53 0 90.5 -37.5t37.5 -90.5v-768q0 -53 -37.5 -90.5t-90.5 -37.5zM832 768q0 27 -18.5 45.5t-45.5 18.5h-512q-27 0 -45.5 -18.5t-18.5 -45.5v-320h640v320v0zM832 320h-640v-192q0 -27 18.5 -45.5t45.5 -18.5h512q27 0 45.5 18.5t18.5 45.5v192v0zM512 128q-27 0 -45.5 18.5t-18.5 45.5t18.5 45.5t45.5 18.5t45.5 -18.5t18.5 -45.5t-18.5 -45.5t-45.5 -18.5z" /></g></svg><span style="margin-left: 5px;">将 ${filename} 推送到 RPC 下载器</span></button>
  7820. </div>`;
  7821. }
  7822. if (mode === 'curl') {
  7823. let alink = base.convertLinkToCurl(dlink, filename);
  7824. alinkAllText += alink + '\r\n';
  7825. content += `<div class="pl-item">
  7826. <div class="pl-item-name listener-tip" data-size="${size}">${filename}</div>
  7827. <a class="pl-item-link listener-link-aria" href="${alink}" title="点击复制 curl 命令行" data-filename="${filename}" data-link="${alink}">${decodeURIComponent(alink)}<br/>复制 ${filename} 下载命令行</a>
  7828. </div>`;
  7829. }
  7830. if (mode === 'bc') {
  7831. let alink = base.convertLinkToBC(dlink, filename);
  7832. alinkAllText += alink + '\r\n';
  7833. content += `<div class="pl-item">
  7834. <div class="pl-item-name listener-tip" data-size="${size}">${filename}</div>
  7835. <a class="pl-item-link" href="${decodeURIComponent(alink)}" title="点击用比特彗星下载" data-filename="${filename}" data-link="${alink}">${decodeURIComponent(alink)}<br/>下载 ${filename}</a>
  7836. </div>`;
  7837. }
  7838. }
  7839. });
  7840. content += '</div>';
  7841.  
  7842. if (mode === 'rpc') {
  7843. content += `<div class="pl-extra">`
  7844. }
  7845. if (list.length >= 2) {
  7846. if (mode === 'api')
  7847. content += `<div class="pl-extra"><button class="pl-btn-primary listener-tip listener-download-all blob" data-title="不建议使用本功能,若文件过大下载完成后有可能不会弹出窗口,此时请换用“RPC 下载 + Mortix”的组合<br/>基于浏览器的 Blob 文件流下载文件,适用于较新的浏览器,可以在此窗口中显示下载剩余时间和下载速度,此方式下载不会被 IDM 捕获下载链接">全部增强下载</button><button class="pl-btn-primary listener-tip listener-copy-all" data-link="${alinkAllText}" data-title="不建议使用本功能,在本网盘单独复制链接并粘贴下载可能会导致服务器回报 403 错误">复制全部链接</button></div>`;
  7848. if (mode === 'aria')
  7849. content += `<div class="pl-extra"><button class="pl-btn-primary listener-copy-all" data-link="${alinkAllText}">复制全部命令行</button></div>`;
  7850. if (mode === 'rpc') {
  7851. content += `<button class="pl-btn-primary listener-send-rpc">发送全部链接</button>`;
  7852. }
  7853. if (mode === 'curl') {
  7854. content += `<div class="pl-extra"><button class="pl-btn-primary listener-copy-all" data-link="${alinkAllText}">复制全部命令行</button><button class="pl-btn-primary pl-btn-warning listener-open-setting" style="margin-left: 10px;">修改终端类型(${terminalType[base.getValue('setting_terminal_type')]})</button></div>`;
  7855. }
  7856. if (mode === 'bc') {
  7857. content += `<div class="pl-extra"><button class="pl-btn-primary listener-copy-all" data-link="${alinkAllText}">复制全部链接</button></div>`;
  7858. }
  7859. }
  7860. if (mode === 'rpc') {
  7861. let rpc = base.getValue('setting_rpc_domain') + ':' + base.getValue('setting_rpc_port') + base.getValue('setting_rpc_path');
  7862. content += `<button title="${rpc}" class="pl-btn-primary pl-btn-warning listener-open-setting" style="margin-left: 10px">修改 RPC 参数(${rpc})</button>
  7863. <button class="pl-btn-primary pl-btn-success pl-btn-opacity listener-rpc-task" style="margin-left: 10px;display: none">查看下载任务</button>
  7864. </div>`;
  7865. }
  7866.  
  7867. return content;
  7868. },
  7869.  
  7870. getSelectedList() {
  7871. try {
  7872. let selectedList = [];
  7873. let reactDom = document.getElementsByClassName('ant-table-body')[0];
  7874. let reactObj = base.findReact(reactDom);
  7875. let props = reactObj.memoizedProps.props;
  7876. if (props) {
  7877. let fileList = props.data || [];
  7878. fileList.forEach(function (val) {
  7879. if (val?.checked === true) {
  7880. selectedList.push(val);
  7881. }
  7882. });
  7883. }
  7884. return selectedList;
  7885. } catch (e) {
  7886. return [];
  7887. }
  7888. },
  7889.  
  7890. isOnlyFolder() {
  7891. for (let i = 0; i < selectList.length; i++) {
  7892. if (selectList[i].Type === 0) return false;
  7893. }
  7894. return true;
  7895. },
  7896.  
  7897. addButton() {
  7898. base.waitForKeyElements(config.$123pan.mount.home, (element) => {
  7899. element = element.parent();
  7900. page = $123pan.detectPage();
  7901. if ($(".pl-button").length > 0 || !page || page !== 'home') return;
  7902. let $button = $(`<div class="ant-dropdown-trigger sysdiv parmiryButton pl-button">
  7903. <svg class="icon" aria-hidden="true" style="font-size: 22px; color: rgb(255, 255, 255);"><use xlink:href="#top_btn_download2"></use></svg>下载助手
  7904. <ul class="pl-dropdown-menu" style="top:37px">
  7905. <li class="pl-dropdown-menu-item pl-button-mode" data-mode="api">API 下载</li>
  7906. <li class="pl-dropdown-menu-item pl-button-mode" data-mode="aria">Aria 下载</li>
  7907. <li class="pl-dropdown-menu-item pl-button-mode" data-mode="rpc">RPC 下载</li>
  7908. <li class="pl-dropdown-menu-item pl-button-mode" data-mode="curl">cURL 下载</li>
  7909. <li class="pl-dropdown-menu-item pl-button-mode" data-mode="bc">BC 下载</li>
  7910. <li class="pl-dropdown-menu-item pl-button-mode listener-open-setting">助手设置</li>
  7911. <li class="pl-dropdown-menu-item pl-button-mode listener-open-beautify">助手美化</li>
  7912. <li class="pl-dropdown-menu-item pl-button-mode listener-open-updatelog">更新日志</li>
  7913. </ul>
  7914. </div>`);
  7915. $button.css({ "margin-right": "22px", "width": "110px" })
  7916. element.prepend($button);
  7917. })
  7918. base.waitForKeyElements(config.$123pan.mount.share, (element) => {
  7919. element = element.parent();
  7920. page = $123pan.detectPage();
  7921. if ($(".pl-button").length > 0 || !page || page !== 'share') return;
  7922. let $button = $(`<div class="register pl-button">
  7923. <svg class="icon" aria-hidden="true" style="color:rgb(255, 255, 255);margin-right:5px;"><use xlink:href="#top_btn_download2"></use></svg>下载助手
  7924. <ul class="pl-dropdown-menu" style="top:37px">
  7925. <li class="pl-dropdown-menu-item pl-button-mode" data-mode="api">API 下载</li>
  7926. <li class="pl-dropdown-menu-item pl-button-mode" data-mode="aria">Aria 下载</li>
  7927. <li class="pl-dropdown-menu-item pl-button-mode" data-mode="rpc">RPC 下载</li>
  7928. <li class="pl-dropdown-menu-item pl-button-mode" data-mode="curl">cURL 下载</li>
  7929. <li class="pl-dropdown-menu-item pl-button-mode" data-mode="bc">BC 下载</li>
  7930. <li class="pl-dropdown-menu-item pl-button-mode listener-open-setting">助手设置</li>
  7931. <li class="pl-dropdown-menu-item pl-button-mode listener-open-beautify">助手美化</li>
  7932. <li class="pl-dropdown-menu-item pl-button-mode listener-open-updatelog">更新日志</li>
  7933. </ul>
  7934. </div>`);
  7935. $button.css({ "width": "100px" })
  7936. element.append($button);
  7937. })
  7938. },
  7939.  
  7940. addInitButton() {
  7941. base.waitForKeyElements(config.$123pan.mount.home, (element) => {
  7942. element = element.parent();
  7943. page = $123pan.detectPage();
  7944. if ($(".pl-button-init").length > 0 || !page || page !== 'home') return;
  7945. let $button = $(`<div class="ant-dropdown-trigger sysdiv parmiryButton pl-button-init">
  7946. <svg class="icon" aria-hidden="true" style="font-size: 22px; color: rgb(255, 255, 255);"><use xlink:href="#top_btn_download2"></use></svg>点我点亮
  7947. </div>`);
  7948. $button.click(function () { base.initDialog() });
  7949. $button.css({ "margin-right": "22px", "width": "110px" })
  7950. element.prepend($button);
  7951. })
  7952. base.waitForKeyElements(config.$123pan.mount.share, (element) => {
  7953. element = element.parent();
  7954. page = $123pan.detectPage();
  7955. if ($(".pl-button-init").length > 0 || !page || page !== 'share') return;
  7956. let $button = $(`<div class="register pl-button-init">
  7957. <svg class="icon" aria-hidden="true" style="color:rgb(255, 255, 255);margin-right:5px;"><use xlink:href="#top_btn_download2"></use></svg>点我点亮
  7958. </div>`);
  7959. $button.click(function () { base.initDialog() });
  7960. $button.css({ "width": "100px" })
  7961. element.append($button);
  7962. })
  7963. },
  7964.  
  7965. detectPage() {
  7966. let path = location.pathname;
  7967. if (/^\/$/.test(path)) return 'home';
  7968. if (/^\/s\//.test(path)) return 'share';
  7969. return '';
  7970. },
  7971.  
  7972. async initPanLinker() {
  7973. base.createTip();
  7974. base.registerMenuCommand();
  7975. if (config.base.num === base.getValue('setting_init_code') || config.base.license === base.getValue('setting_init_license')) {
  7976. this.addButton();
  7977. this.addPageListener();
  7978. } else {
  7979. this.addInitButton();
  7980. }
  7981. },
  7982. };
  7983.  
  7984. // 油小猴
  7985. let $youxiaohou = {
  7986. async initPanLinker() {
  7987. base.createTip();
  7988. base.registerMenuCommand();
  7989. if (config.base.num === base.getValue('setting_init_code') || config.base.license === base.getValue('setting_init_license')) {
  7990. this.addButton();
  7991. } else {
  7992. this.addInitButton();
  7993. }
  7994. },
  7995. addButton() {
  7996. let $button = `<div class="nav-item">
  7997. <div class="dropdown-wrapper">
  7998. <button type="button" aria-label="(改)下载助手" class="dropdown-title">
  7999. <span class="title">(改)下载助手⬇️</span>
  8000. <span class="arrow down"></span>
  8001. </button>
  8002. <button type="button" aria-label="(改)下载助手" class="mobile-dropdown-title">
  8003. <span class="title">(改)下载助手⬇️</span>
  8004. <span class="arrow right"></span>
  8005. </button>
  8006. <ul class="nav-dropdown" style="display:none;">
  8007. <li class="dropdown-item">
  8008. <h4>
  8009. 助手
  8010. </h4>
  8011. <ul class="dropdown-subitem-wrapper">
  8012. <li class="dropdown-subitem">
  8013. <a href="javascript:void(0)" class="listener-open-info nav-link">
  8014. 🛠️ 调试(查看暗号/协议)
  8015. </a>
  8016. </li>
  8017. </ul>
  8018. </li>
  8019. <li class="dropdown-item">
  8020. <h4>
  8021. 选项
  8022. </h4>
  8023. <ul class="dropdown-subitem-wrapper">
  8024. <li class="dropdown-subitem">
  8025. <a href="javascript:void(0)" class="listener-open-setting nav-link">
  8026. ⚙️ 助手设置
  8027. </a>
  8028. </li>
  8029. <li class="dropdown-subitem">
  8030. <a href="javascript:void(0)" class="listener-open-beautify nav-link">
  8031. 🍃️ 助手美化
  8032. </a>
  8033. </li>
  8034. <li class="dropdown-subitem">
  8035. <a href="javascript:void(0)" class="listener-open-updatelog nav-link">
  8036. 📃️ 更新日志
  8037. </a>
  8038. </li>
  8039. </ul>
  8040. </li>
  8041. </ul>
  8042. </div>
  8043. </div>
  8044. `;
  8045. doc.on('click', '.listener-open-setting', function () {
  8046. base.showSetting();
  8047. });
  8048. doc.on('click', '.listener-open-updatelog', function () {
  8049. base.showUpdate();
  8050. });
  8051. doc.on('click', '.listener-open-beautify', function () {
  8052. base.showBeautify();
  8053. });
  8054. doc.on('click', '.listener-open-info', function () {
  8055. base.showDebug();
  8056. });
  8057.  
  8058. document.querySelectorAll(".nav-links").forEach(function (element) {
  8059. element.innerHTML += $button
  8060. })
  8061. },
  8062. addInitButton() {
  8063. let $button = `<div class="nav-item">
  8064. <div class="dropdown-wrapper">
  8065. <a class="nav-link listener-open-init">(改)点我点亮⬇️</a>
  8066. </div>
  8067. </div>
  8068. `;
  8069. doc.on('click', '.listener-open-init', function () {
  8070. base.initDialog();
  8071. });
  8072.  
  8073. document.querySelectorAll(".nav-links").forEach(function (element) {
  8074. element.innerHTML += $button
  8075. })
  8076. }
  8077. }
  8078. // 主代码
  8079. let main = {
  8080. async init() {
  8081. base.waitForKeyElements(`html:not(:has(> ${mount})) head`, (element) => {
  8082. element.after(`<${mount} class="${mount}" />`)
  8083. }, true)
  8084. // 先加载默认设置
  8085. base.initDefaultConfig();
  8086. // 再加载网页样式
  8087. base.addPanLinkerStyle();
  8088. base.createDownloadIframe();
  8089.  
  8090. /**
  8091. * 控制台输出
  8092. * @author 油小猴
  8093. * @author hmjz100
  8094. * @description 来自【网盘智能识别助手】,有改动
  8095. */
  8096. console.log(`%c %c LinkSwift\n一个基于 JavaScript 的网盘文件下载地址获取工具\n版本:${sversion}\n领域:${(window.self !== window.top ? "[iframe] " : "") + (document.title ? (document.title + " (" + location.origin + location.pathname + ")") : location.href)}`, `background:url(${sicon}) center center no-repeat;background-size:12px;padding:3px`, "");
  8097.  
  8098. let storedVersion = base.getValue("setting_init_version");
  8099. if (!storedVersion || base.isNewerVersion(sversion, storedVersion)) {
  8100. base.waitForKeyElements("body", () => {
  8101. base.showUpdate();
  8102. base.setValue("setting_init_version", sversion);
  8103. return true;
  8104. }, true)
  8105. }
  8106.  
  8107. // 最后判断页面地址并加载对应的initPanLinker
  8108. if (/(pan|yun).baidu.com/.test(location.host)) {
  8109. $baidu.initPanLinker();
  8110. $baidu.greenerPage();
  8111. }
  8112. if (/openapi.baidu.com\/oauth/.test(location.href)) {
  8113. $baidu.initAuthorize()
  8114. }
  8115. if (/www.(aliyundrive|alipan).com/.test(location.host)) {
  8116. $aliyun.initPanLinker();
  8117. $aliyun.greenerPage();
  8118. }
  8119. if (/(yun|caiyun).139.com/.test(location.host)) {
  8120. $mcloud.initPanLinker();
  8121. $mcloud.greenerPage();
  8122. }
  8123. if (/cloud.189.cn/.test(location.host)) {
  8124. $tcloud.initPanLinker();
  8125. $tcloud.greenerPage();
  8126. }
  8127. if (/pan.xunlei.com/.test(location.host)) {
  8128. $xunlei.initPanLinker();
  8129. }
  8130. if (/pan.quark.cn/.test(location.host)) {
  8131. $quark.initPanLinker();
  8132. $quark.greenerPage();
  8133. }
  8134. if (/drive.uc.cn/.test(location.host)) {
  8135. $uc.initPanLinker();
  8136. $uc.greenerPage();
  8137. }
  8138. if (/(www|login).(123(pan|684|865|952|912).com|123pan.cn)/.test(location.host)) {
  8139. $123pan.initPanLinker();
  8140. $123pan.greenerPage();
  8141. }
  8142. if (/www.youxiaohou.com/.test(location.host)) {
  8143. $youxiaohou.initPanLinker();
  8144. }
  8145. }
  8146. };
  8147. main.init();
  8148.  
  8149. // 这是啥?我不到啊
  8150. function idontknow(input) {
  8151. let charArray = input.split('');
  8152. // Fisher-Yates 洗牌算法
  8153. for (let i = charArray.length - 1; i > 0; i--) {
  8154. let j = Math.floor(Math.random() * (i + 1));
  8155. [charArray[i], charArray[j]] = [charArray[j], charArray[i]];
  8156. }
  8157. return charArray.join('');
  8158. }
  8159. })();