123云盘秒传链接

123FastLink是一款适用于123网盘(123Pan) 的秒传链接生成与转存的用户脚本。

目前為 2025-01-30 提交的版本,檢視 最新版本

  1. // ==UserScript==
  2. // @name 123云盘秒传链接
  3. // @namespace https://github.com/Bao-qing/123FastLink
  4. // @version 2025-01-29
  5. // @description 123FastLink是一款适用于123网盘(123Pan) 的秒传链接生成与转存的用户脚本。
  6. // @author Baoqing
  7. // @match *://*.123pan.com/*
  8. // @match *://*.123pan.cn/*
  9. // @icon https://www.google.com/s2/favicons?sz=64&domain=123pan.com
  10. // @license MIT
  11. // @grant none
  12. // ==/UserScript==
  13.  
  14. (function() {
  15. 'use strict';
  16. // ----------------------------------------------------基础环境----------------------------------------------------
  17. // ==================🚀 构建URL函数 ==================
  18. const buildURL = (host, path, queryParams) => {
  19. const queryString = new URLSearchParams(queryParams).toString();
  20. return `${host}${path}?${queryString}`;
  21. };
  22.  
  23. // ==================🌐 发送请求函数 ==================
  24. async function sendRequest(method, path, queryParams, body) {
  25. const config = {
  26. host: 'https://' + window.location.host,
  27. queryParams: { // 🛡️ 预留的签名参数(可选)
  28. //'803521858': '1738073884-258518-2032310069'
  29. },
  30. // 🔑 获取身份认证信息
  31. authToken: localStorage['authorToken'],
  32. loginUuid: localStorage['LoginUuid'],
  33.  
  34. appVersion: '3',
  35. referer: document.location.href,
  36. };
  37.  
  38. const headers = {
  39. 'Content-Type': 'application/json;charset=UTF-8',
  40. 'Authorization': 'Bearer ' + config.authToken,
  41. 'platform': 'web',
  42. 'App-Version': config.appVersion,
  43. 'LoginUuid': config.loginUuid,
  44. 'Origin': config.host,
  45. 'Referer': config.referer,
  46. };
  47.  
  48. try {
  49. const response = await fetch(buildURL(config.host, path, queryParams), {
  50. method: method,
  51. headers: headers,
  52. body: body,
  53. credentials: 'include'
  54. });
  55.  
  56. console.log(`[${response.status}] ${response.statusText}`);
  57. const data = await response.json();
  58. console.table(data); // ✅ 表格化输出
  59.  
  60. if (data.code !== 0) {
  61. console.error('❗ 业务逻辑错误:', data.message);
  62. throw '❗ 业务逻辑错误:' + data.message;
  63. }
  64.  
  65. return data; // ✅ 确保 sendRequest 返回 data
  66. } catch (error) {
  67. console.error('⚠️ 网络请求失败:', error);
  68. throw '未知错误';
  69. return null;
  70. }
  71. }
  72.  
  73. // ----------------------------------------------------生成秒传----------------------------------------------------
  74.  
  75. // ====================== 📂 获取文件信息 ================
  76. async function getFileInfo(idList) {
  77. const transformedList = idList.map(fileId => ({ fileId }));
  78. const responseData = await sendRequest(
  79. "POST",
  80. "/b/api/file/info", {},
  81. JSON.stringify({ // 请求体
  82. fileIdList: transformedList
  83. })
  84. );
  85. return responseData;
  86. }
  87.  
  88. // ===================== 获取选择的文件id =============
  89. function getSelectFile() {
  90. const fileRow = Array.from(document.getElementsByClassName("ant-table-row ant-table-row-level-0 editable-row"));
  91. const selectFile = fileRow.map(function(element, index, array) {
  92. if (element.getElementsByTagName("input")[0].checked) {
  93. return element.getAttribute('data-row-key'); // 返回修改后的元素
  94. }
  95. }).filter(item => item != null);
  96. return selectFile;
  97. }
  98.  
  99. // ====================🔗 生成秒传链接 ===================
  100. async function creatShareLink() {
  101. const fileSelect = getSelectFile();
  102. //console.log("fileS", fileSelect);
  103. if (!fileSelect.length) {
  104. return ""
  105. }
  106. const fileInfo = Array.from((await getFileInfo(fileSelect))['data']['infoList']);
  107. var hasFloder = 0;
  108. const shareLink = fileInfo.map(function(info, infoIndex, infoArray) { //.filter(item => item != null)
  109. if (info.Type == 0) {
  110. return [info.Etag, info.Size, info.FileName.replace("#", "").replace("$", "")].join('#')
  111. } else {
  112. console.log("忽略文件夹", info.FileName);
  113. hasFloder = 1;
  114. }
  115. }).filter(item => item != null).join('$');
  116. if (hasFloder) {
  117. alert("文件夹无法秒传,将被忽略");
  118. }
  119. return shareLink;
  120. }
  121.  
  122.  
  123. // ----------------------------------------------------接受秒传----------------------------------------------------
  124. // ==================📥 参数解析 ====================
  125. function getShareFileInfo(shareLink) {
  126.  
  127. const shareLinkList = Array.from(shareLink.split('$'));
  128. const shareFileInfoList = shareLinkList.map(function(singleShareLink, linkIndex, linkArray) {
  129. const singleFileInfoList = singleShareLink.split('#');
  130. const singleFileInfo = {
  131. etag: singleFileInfoList[0],
  132. size: singleFileInfoList[1],
  133. fileName: singleFileInfoList[2]
  134. };
  135. return singleFileInfo;
  136. });
  137. return shareFileInfoList;
  138. //JSON.parse(decodeURIComponent(atob(shareLink)));
  139. }
  140.  
  141. // ================== 获取单一文件 ===================
  142. async function getSingleFile(shareFileInfo) {
  143. // --------------------- 文件信息 ---------------------
  144. const fileInfo = {
  145. driveId: 0,
  146. etag: shareFileInfo.etag,
  147. fileName: shareFileInfo.fileName,
  148. parentFileId: JSON.parse(sessionStorage['filePath'])['homeFilePath'][0] || 0,
  149. size: shareFileInfo.size,
  150. type: 0,
  151. duplicate: 1
  152. };
  153. // --------------------- 发送请求 ---------------------
  154. const responseData = await sendRequest('POST', '/b/api/file/upload_request', {},
  155. JSON.stringify({...fileInfo, RequestSource: null }));
  156. return responseData;
  157. }
  158.  
  159. // ================== 获取全部文件 ===================
  160. async function getFiles(shareLink) {
  161. try {
  162. const shareFileList = getShareFileInfo(shareLink);
  163. for (var i = 0; i < shareFileList.length; i++) {
  164. getSingleFile(shareFileList[i]);
  165. }
  166. return 1
  167. } catch {
  168. return 0
  169. }
  170. }
  171.  
  172. // ----------------------------------------------------创建按钮----------------------------------------------------
  173. // =================== 📌 创建按钮 ===================
  174. function creatButton() {
  175. const targetElement = document.querySelector('.ant-dropdown-trigger.sysdiv.parmiryButton');
  176.  
  177. if (targetElement && targetElement.parentNode) {
  178. // 创建“展开”按钮
  179. const expandButton = document.createElement('div');
  180. expandButton.className = 'ant-dropdown-trigger sysdiv parmiryButton';
  181. expandButton.style.borderRight = '0.5px solid rgb(217, 217, 217)';
  182. expandButton.style.cursor = 'pointer';
  183. expandButton.style.marginLeft = '20px';
  184. expandButton.innerHTML = `
  185. <span id="fasttrans123" role="img" aria-label="menu" class="anticon anticon-menu" style="margin-right: 6px;">
  186. <svg viewBox="64 64 896 896" focusable="false" data-icon="menu" width="1em" height="1em" fill="currentColor" aria-hidden="true">
  187. <path d="M120 300h720v60H120zm0 180h720v60H120zm0 180h720v60H120z"></path>
  188. </svg>
  189. </span>
  190. 秒传
  191. `;
  192.  
  193. // 创建下拉菜单(默认隐藏)
  194. const dropdownMenu = document.createElement('div');
  195. dropdownMenu.style.display = 'none';
  196. dropdownMenu.style.id = 'fast_trans_button'
  197. dropdownMenu.style.position = 'absolute';
  198. dropdownMenu.style.background = '#fff';
  199. dropdownMenu.style.border = '1px solid #ccc';
  200. dropdownMenu.style.padding = '2px';
  201. dropdownMenu.style.boxShadow = '0px 4px 6px rgba(0, 0, 0, 0.1)';
  202. dropdownMenu.style.marginTop = '5px';
  203.  
  204.  
  205. dropdownMenu.innerHTML = `
  206. <ul class="ant-dropdown-menu ant-dropdown-menu-root ant-dropdown-menu-vertical ant-dropdown-menu-light" role="menu" tabindex="0" data-menu-list="true" style="border-radius: 10px;">
  207. <li id="generateShare" class="ant-dropdown-menu-item ant-dropdown-menu-item-only-child" role="menuitem" tabindex="-1" data-menu-id="rc-menu-uuid-73825-3-1">
  208. <span class="ant-dropdown-menu-title-content">
  209. <div style="width: 100px; height: 20px; line-height: 20px; padding: 0px 6px; position: relative; margin-top: 6px;">
  210. 生成链接
  211. </div>
  212. </span>
  213. </li>
  214. <li id="receiveDirect" class="ant-dropdown-menu-item ant-dropdown-menu-item-only-child" role="menuitem" tabindex="-1" data-menu-id="rc-menu-uuid-73825-3-2">
  215. <span class="ant-dropdown-menu-title-content">
  216. <div style="width: 100px; height: 20px; line-height: 20px; padding: 0px 6px; position: relative;">
  217. 链接转存
  218. </div>
  219. </span>
  220. </li>
  221.  
  222. <li id="closeMenu" class="ant-dropdown-menu-item ant-dropdown-menu-item-only-child" role="menuitem" tabindex="-1" data-menu-id="rc-menu-uuid-73825-3-3">
  223. <span class="ant-dropdown-menu-title-content">
  224. <div style="width: 100px; height: 20px; line-height: 20px; padding: 0px 6px; position: relative;">
  225. 关闭
  226. </div>
  227. </span>
  228. </li>
  229. </ul>
  230. `;
  231.  
  232. // 绑定按钮事件
  233. expandButton.addEventListener('click', () => {
  234. dropdownMenu.style.display = dropdownMenu.style.display === 'none' ? 'block' : 'none';
  235. });
  236.  
  237. // 绑定 "关闭" 按钮事件
  238. dropdownMenu.querySelector('#closeMenu').addEventListener('click', () => {
  239. document.querySelector('#fast_trans_button').display = 'none';
  240. });
  241.  
  242. // 绑定生成直链按钮事件
  243. dropdownMenu.querySelector('#generateShare').addEventListener('click', async() => {
  244. const shareLink = await creatShareLink();
  245. if (shareLink == '') {
  246. alert("没有选择文件");
  247. return
  248. }
  249. showCopyModal(shareLink);
  250. });
  251.  
  252. // 绑定接受直链按钮事件
  253. dropdownMenu.querySelector('#receiveDirect').addEventListener('click', () => {
  254. showCopyModal("", "获取", startGetFile);
  255. });
  256.  
  257. // 插入到目标元素的同级
  258. targetElement.parentNode.insertBefore(expandButton, targetElement.nextSibling);
  259. expandButton.appendChild(dropdownMenu);
  260. }
  261. }
  262.  
  263.  
  264.  
  265. // =================✨ 弹出操作框 ================
  266. function showCopyModal(defaultText = "", buttonText = "复制", buttonFunction = copyToClipboard) {
  267. // 这个样式会遮挡,清除掉
  268. const floatTable = document.getElementsByClassName('ant-table-header ant-table-sticky-holder');
  269. if (floatTable.length > 0) {
  270. floatTable[0].className = "ant-table-header";
  271. }
  272.  
  273. // 检查是否已有样式,防止重复添加
  274. if (!document.getElementById("modal-style")) {
  275. let style = document.createElement("style");
  276. style.id = "modal-style";
  277. style.innerHTML = `
  278. .modal-overlay {display: flex;position: fixed;top: 0;left: 0;width: 100%;height: 100%;background: rgba(0, 0, 0, 0.5);justify-content: center;align-items: center; }
  279. .modal {background: white;padding: 20px;border-radius: 8px;box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2);text-align: center;width: 300px;}
  280. .close-btn {background: #f44336;color: white;border: none;padding: 5px 10px;cursor: pointer;float: right;}
  281. .modal input {width: 100%;padding: 8px;margin: 10px 0;border: 1px solid #ccc;border-radius: 4px;}
  282. .copy-btn {background: #4CAF50;color: white;border: none;padding: 8px 12px;cursor: pointer;border-radius: 4px;}
  283. `;
  284. document.head.appendChild(style);
  285. }
  286.  
  287. // 如果已有弹窗,则删除它
  288. let existingModal = document.getElementById('modal');
  289. if (existingModal) existingModal.remove();
  290.  
  291. // 创建遮罩层
  292. let modalOverlay = document.createElement('div');
  293. modalOverlay.className = 'modal-overlay';
  294. modalOverlay.id = 'modal';
  295.  
  296. // 创建弹窗
  297. modalOverlay.innerHTML = `
  298. <div class="modal">
  299. <button class="close-btn" onclick="document.getElementById('modal').remove()">×</button>
  300. <h3>秒传链接</h3>
  301. <input type="text" id="copyText" value="${defaultText}">
  302. <button class="copy-btn" id="massageboxButton" onclick="${buttonFunction}()">${buttonText}</button>
  303. </div>
  304. `;
  305.  
  306. // 绑定接受直链按钮事件
  307. modalOverlay.querySelector('#massageboxButton').addEventListener('click', () => {
  308. buttonFunction();
  309. });
  310.  
  311. // 添加到 body
  312. document.body.appendChild(modalOverlay);
  313. }
  314.  
  315. // ===================📋 写入剪贴板 ====================
  316. function copyToClipboard() {
  317. let inputField = document.getElementById('copyText');
  318. inputField.select();
  319. document.execCommand('copy');
  320. alert('已尝试写入剪贴板,请确保授予相关权限');
  321. }
  322.  
  323. // ================== 获取文件 ====================
  324. function startGetFile() {
  325. const shareLink = document.getElementById("copyText").value;
  326. if (getFiles(shareLink)) {
  327. alert("获取成功,请刷新目录查看,如没有请检查根目录。");
  328. // 如果已有弹窗,则删除它
  329. let existingModal = document.getElementById('modal');
  330. if (existingModal) existingModal.remove();
  331. } else {
  332. alert("获取失败");
  333. }
  334. }
  335.  
  336. // ⏳ =============== 创建 ======================
  337. function createButtonIfNotExists() {
  338. // 如果未创建按钮
  339. const fastTrans123 = document.getElementById('fasttrans123');
  340. if (fastTrans123 == null) {
  341. creatButton();
  342. }
  343. }
  344.  
  345. setInterval(createButtonIfNotExists, 1000);
  346. })();

QingJ © 2025

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