123pan秒传JSON生成器(夸克网盘/天翼云盘)

夸克网盘、天翼云盘生成123云盘(123pan)的秒传JSON工具

目前為 2025-11-10 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         123pan秒传JSON生成器(夸克网盘/天翼云盘)
// @namespace    http://tampermonkey.net/
// @version      1.0.0
// @description  夸克网盘、天翼云盘生成123云盘(123pan)的秒传JSON工具
// @author       ekxs
// @match        https://pan.quark.cn/*
// @match        https://drive.quark.cn/*
// @match        https://pan.quark.cn/s/*
// @match        https://drive.quark.cn/s/*
// @match        https://cloud.189.cn/web/*
// @grant        GM_setClipboard
// @grant        GM_notification
// @grant        GM_xmlhttpRequest
// @grant        GM_cookie
// @grant        GM_getValue
// @grant        GM_setValue
// @run-at       document-end
// @connect      drive.quark.cn
// @connect      drive-pc.quark.cn
// @connect      pc-api.uc.cn
// @connect      cloud.189.cn
// ==/UserScript==

(function () {
  "use strict";

  const utils = {
    getCachedCookie() {
      return GM_getValue("quark_cookie", "");
    },

    saveCookie(cookie) {
      GM_setValue("quark_cookie", cookie);
    },

    getCookie(name) {
      const value = `; ${document.cookie}`;
      const parts = value.split(`; ${name}=`);
      if (parts.length === 2) return parts.pop().split(";").shift();
      return null;
    },

    showCookieInputDialog(onSave, currentCookie = "") {
      const dialog = document.createElement("div");
      dialog.id = "quark-cookie-input-dialog";
      dialog.innerHTML = `
        <div style="position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.5); z-index: 10000; display: flex; align-items: center; justify-content: center;">
          <div style="background: white; padding: 30px; border-radius: 8px; width: 80%; max-width: 800px; max-height: 80vh; display: flex; flex-direction: column;">
            <div style="font-size: 18px; font-weight: bold; margin-bottom: 15px;">设置夸克网盘Cookie</div>
            <div style="font-size: 14px; color: #666; margin-bottom: 15px;">
              请打开浏览器开发者工具(F12) → Network → 找到任意请求 → 复制完整的Cookie值<br/>
              <strong>必须包含:__puus、__pus、ctoken 等关键Cookie</strong>
            </div>
            <textarea id="quark-cookie-input"
              placeholder="粘贴完整的Cookie字符串,例如:ctoken=xxx; __puus=xxx; __pus=xxx; ..."
              style="flex: 1; min-height: 200px; padding: 10px; border: 1px solid #d9d9d9; border-radius: 4px; font-family: monospace; font-size: 12px; resize: vertical;">${currentCookie}</textarea>
            <div style="display: flex; gap: 10px; justify-content: flex-end; margin-top: 15px;">
              <button id="quark-cookie-save-btn" style="padding: 8px 20px; background: #0d53ff; color: white; border: none; border-radius: 4px; cursor: pointer;">保存</button>
              <button id="quark-cookie-cancel-btn" style="padding: 8px 20px; background: #d9d9d9; color: #333; border: none; border-radius: 4px; cursor: pointer;">取消</button>
            </div>
          </div>
        </div>
      `;
      document.body.appendChild(dialog);

      document.getElementById("quark-cookie-save-btn").onclick = () => {
        const cookie = document
          .getElementById("quark-cookie-input")
          .value.trim();
        if (!cookie) {
          alert("Cookie不能为空");
          return;
        }
        this.saveCookie(cookie);
        dialog.remove();
        GM_notification({
          text: "Cookie已保存",
          timeout: 2000,
        });
        if (onSave) {
          onSave(cookie);
        }
      };

      document.getElementById("quark-cookie-cancel-btn").onclick = () => {
        dialog.remove();
      };
    },

    sleep(ms) {
      return new Promise((resolve) => setTimeout(resolve, ms));
    },

    findReact(dom, traverseUp = 0) {
      let key = Object.keys(dom).find((key) => {
        return (
          key.startsWith("__reactFiber$") ||
          key.startsWith("__reactInternalInstance$")
        );
      });

      let domFiber = dom[key];

      if (domFiber == null) {
        return null;
      }

      if (domFiber._currentElement) {
        let compFiber = domFiber._currentElement._owner;
        for (let i = 0; i < traverseUp; i++) {
          compFiber = compFiber._currentElement._owner;
        }
        return compFiber._instance;
      }

      const GetCompFiber = (fiber) => {
        let parentFiber = fiber.return;
        while (typeof parentFiber.type === "string") {
          parentFiber = parentFiber.return;
        }
        return parentFiber;
      };

      let compFiber = GetCompFiber(domFiber);
      for (let i = 0; i < traverseUp; i++) {
        compFiber = GetCompFiber(compFiber);
      }

      return compFiber.stateNode || compFiber;
    },

    findVue(dom, traverseUp = 0) {
      let i = 0;
      let el = dom;
      while (i < traverseUp) {
        if (!el) return null;
        el = el.parentElement;
        i++;
      }
      return el?.__vue__;
    },

    getCurrentPath() {
      try {
        const urlParams = new URLSearchParams(window.location.search);
        const dirFid = urlParams.get("dir_fid");

        if (!dirFid || dirFid === "0") {
          return "";
        }

        const breadcrumb = document.querySelector(".breadcrumb-list");
        if (breadcrumb) {
          const items = breadcrumb.querySelectorAll(".breadcrumb-item");
          const pathParts = [];

          for (let i = 1; i < items.length; i++) {
            const text = items[i].textContent.trim();
            if (text) {
              pathParts.push(text);
            }
          }

          return pathParts.join("/");
        }

        return "";
      } catch (e) {
        return "";
      }
    },

    getSelectedList() {
      try {
        const fileListDom = document.getElementsByClassName("file-list")[0];

        if (!fileListDom) {
          return [];
        }

        const reactObj = this.findReact(fileListDom);

        const props = reactObj?.props;

        if (props) {
          const fileList = props.list || [];
          const selectedKeys = props.selectedRowKeys || [];

          const selectedList = [];
          fileList.forEach(function (val) {
            if (selectedKeys.includes(val.fid)) {
              selectedList.push(val);
            }
          });

          return selectedList;
        }

        return [];
      } catch (e) {
        return [];
      }
    },

    post(url, data, headers = {}) {
      return new Promise((resolve, reject) => {
        const requestData = JSON.stringify(data);
        const QUARK_UA =
          "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";
        const defaultHeaders = {
          "Content-Type": "application/json;charset=utf-8",
          "User-Agent": QUARK_UA,
          Origin: location.origin,
          Referer: `${location.origin}/`,
          Dnt: "",
          "Cache-Control": "no-cache",
          Pragma: "no-cache",
          Expires: "0",
        };

        GM_xmlhttpRequest({
          method: "POST",
          url: url,
          headers: { ...defaultHeaders, ...headers },
          data: requestData,
          onload: function (response) {
            try {
              const result = JSON.parse(response.responseText);
              resolve(result);
            } catch (e) {
              reject(new Error("响应解析失败"));
            }
          },
          onerror: function (error) {
            reject(new Error("网络请求失败"));
          },
        });
      });
    },

    get(url, headers = {}) {
      return new Promise((resolve, reject) => {
        GM_xmlhttpRequest({
          method: "GET",
          url: url,
          headers: headers,
          onload: function (response) {
            if (response.status >= 200 && response.status < 300) {
              resolve(response.responseText);
            } else {
              console.error("### utils.get failed:", response);
              reject(new Error(`请求失败: ${response.status}`));
            }
          },
          onerror: function (error) {
            console.error("### utils.get network error:", error);
            reject(new Error("网络请求失败"));
          },
        });
      });
    },

    async getFolderFiles(folderId, folderPath = "", onProgress) {
      const API_URL =
        "https://drive-pc.quark.cn/1/clouddrive/file/sort?pr=ucpro&fr=pc";
      const allFiles = [];
      let page = 1;
      const pageSize = 50;

      while (true) {
        const url = `${API_URL}&pdir_fid=${folderId}&_page=${page}&_size=${pageSize}&_fetch_total=1&_fetch_sub_dirs=0&_sort=file_type:asc,updated_at:desc`;

        const result = await new Promise((resolve, reject) => {
          GM_xmlhttpRequest({
            method: "GET",
            url: url,
            onload: function (response) {
              try {
                resolve(JSON.parse(response.responseText));
              } catch (e) {
                reject(new Error("响应解析失败"));
              }
            },
            onerror: () => reject(new Error("网络请求失败")),
          });
        });

        if (result?.code !== 0 || !result?.data?.list) {
          break;
        }

        const items = result.data.list;
        for (const item of items) {
          const itemPath = folderPath
            ? `${folderPath}/${item.file_name}`
            : item.file_name;

          if (item.dir) {
            const subFiles = await this.getFolderFiles(
              item.fid,
              itemPath,
              onProgress,
            );
            allFiles.push(...subFiles);
          } else if (item.file) {
            allFiles.push({ ...item, path: itemPath });
            if (onProgress) {
              onProgress(allFiles.length);
            }
          }
        }

        if (items.length < pageSize) {
          break;
        }
        page++;
      }

      return allFiles;
    },

    async getShareFolderFiles(shareId, stoken, folderId, folderPath = "") {
      const allFiles = [];
      let page = 1;
      const pageSize = 100;

      while (true) {
        const url = `https://pc-api.uc.cn/1/clouddrive/share/sharepage/detail?pwd_id=${shareId}&stoken=${encodeURIComponent(
          stoken,
        )}&pdir_fid=${folderId}&_page=${page}&_size=${pageSize}&pr=ucpro&fr=pc`;

        const result = await new Promise((resolve, reject) => {
          GM_xmlhttpRequest({
            method: "GET",
            url: url,
            headers: {
              Referer: "https://pan.quark.cn/",
            },
            onload: function (response) {
              try {
                resolve(JSON.parse(response.responseText));
              } catch (e) {
                reject(new Error("响应解析失败"));
              }
            },
            onerror: () => reject(new Error("网络请求失败")),
          });
        });

        if (result?.code !== 0 || !result?.data?.list) {
          break;
        }

        const items = result.data.list;
        for (const item of items) {
          const itemPath = folderPath
            ? `${folderPath}/${item.file_name}`
            : item.file_name;

          if (item.dir) {
            const subFiles = await this.getShareFolderFiles(
              shareId,
              stoken,
              item.fid,
              itemPath,
            );
            allFiles.push(...subFiles);
          } else if (item.file) {
            allFiles.push({ ...item, path: itemPath });
          }
        }

        if (items.length < pageSize) {
          break;
        }
        page++;
      }

      return allFiles;
    },

    async getShareToken(shareId, passcode = "", cookie = "") {
      const API_URL = "https://pc-api.uc.cn/1/clouddrive/share/sharepage/token";

      try {
        const result = await new Promise((resolve, reject) => {
          GM_xmlhttpRequest({
            method: "POST",
            url: API_URL,
            headers: {
              "Content-Type": "application/json",
              Cookie: cookie,
              "User-Agent":
                "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
              Referer: "https://pan.quark.cn/",
            },
            data: JSON.stringify({
              pwd_id: shareId,
              passcode: passcode,
            }),
            onload: function (response) {
              try {
                resolve(JSON.parse(response.responseText));
              } catch (e) {
                reject(new Error("响应解析失败"));
              }
            },
            onerror: () => reject(new Error("网络请求失败")),
          });
        });

        if (result?.code === 31001) {
          throw new Error("请先登录网盘");
        }
        if (result?.code !== 0) {
          throw new Error(
            `获取token失败,代码:${result.code},消息:${result.message}`,
          );
        }

        return result.data.stoken;
      } catch (error) {
        throw error;
      }
    },

    async getFilesWithMd5(fileList, onProgress) {
      const API_URL =
        "https://drive.quark.cn/1/clouddrive/file/download?pr=ucpro&fr=pc";
      const BATCH_SIZE = 15;

      const data = [];
      let processed = 0;
      const validFiles = fileList.filter((item) => item.file === true);

      const pathMap = {};
      validFiles.forEach((file) => {
        pathMap[file.fid] = file.path;
      });

      for (let i = 0; i < validFiles.length; i += BATCH_SIZE) {
        const batch = validFiles.slice(i, i + BATCH_SIZE);
        const fids = batch.map((item) => item.fid);

        try {
          const result = await this.post(API_URL, { fids });

          if (result?.code === 31001) {
            throw new Error("请先登录网盘");
          }
          if (result?.code !== 0) {
            throw new Error(
              `获取链接失败,代码:${result.code},消息:${result.message}`,
            );
          }

          if (result?.data) {
            const filesWithPath = result.data.map((file) => ({
              ...file,
              path: pathMap[file.fid] || file.file_name,
            }));
            data.push(...filesWithPath);
          }

          processed += batch.length;
          if (onProgress) {
            onProgress(processed, validFiles.length);
          }

          await this.sleep(1000);
        } catch (error) {
          throw error;
        }
      }

      return data;
    },

    async scanQuarkShareFiles(
      shareId,
      stoken,
      cookie,
      parentFileId = 0,
      path = "",
    ) {
      const fileItems = [];
      let page = 1;

      while (true) {
        const url = `https://pc-api.uc.cn/1/clouddrive/share/sharepage/detail?pwd_id=${shareId}&stoken=${encodeURIComponent(
          stoken,
        )}&pdir_fid=${parentFileId}&_page=${page}&_size=100&pr=ucpro&fr=pc`;

        const result = await new Promise((resolve, reject) => {
          GM_xmlhttpRequest({
            method: "GET",
            url: url,
            headers: {
              Cookie: cookie,
              "User-Agent":
                "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36 Edg/137.0.0.0",
              Referer: "https://pan.quark.cn/",
            },
            onload: function (response) {
              try {
                resolve(JSON.parse(response.responseText));
              } catch (e) {
                reject(new Error("响应解析失败"));
              }
            },
            onerror: () => reject(new Error("网络请求失败")),
          });
        });

        if (result.code !== 0 || !result.data?.list) break;

        for (const item of result.data.list) {
          const itemPath = path ? `${path}/${item.file_name}` : item.file_name;

          if (item.dir) {
            const subFiles = await this.scanQuarkShareFiles(
              shareId,
              stoken,
              cookie,
              item.fid,
              itemPath,
            );
            fileItems.push(...subFiles);
          } else {
            fileItems.push({
              fid: item.fid,
              token: item.share_fid_token,
              name: item.file_name,
              size: item.size,
              path: itemPath,
            });
          }
        }

        if (result.data.list.length < 100) break;
        page++;
      }

      return fileItems;
    },

    async batchGetShareFilesMd5(
      shareId,
      stoken,
      cookie,
      fileItems,
      onProgress,
    ) {
      const md5Map = {};
      const batchSize = 10;
      let totalProcessed = 0;

      for (let i = 0; i < fileItems.length; i += batchSize) {
        const batch = fileItems.slice(i, i + batchSize);
        const fids = batch.map((item) => item.fid);
        const tokens = batch.map((item) => item.token);

        try {
          const requestBody = {
            fids,
            pwd_id: shareId,
            stoken,
            fids_token: tokens,
          };

          const md5Result = await new Promise((resolve, reject) => {
            GM_xmlhttpRequest({
              method: "POST",
              url: `https://pc-api.uc.cn/1/clouddrive/file/download?pr=ucpro&fr=pc&uc_param_str=&__dt=${Math.floor(Math.random() * 4 + 1) * 60 * 1000}&__t=${Date.now()}`,
              headers: {
                "Content-Type": "application/json",
                Cookie: cookie,
                "User-Agent":
                  "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) quark-cloud-drive/3.14.2 Chrome/112.0.5615.165 Electron/24.1.3.8 Safari/537.36 Channel/pckk_other_ch",
                Referer: "https://pan.quark.cn/",
                Accept: "application/json, text/plain, */*",
                Origin: "https://pan.quark.cn",
              },
              data: JSON.stringify(requestBody),
              onload: function (response) {
                try {
                  const parsed = JSON.parse(response.responseText);
                  resolve(parsed);
                } catch (e) {
                  resolve({ code: -1, message: "解析失败" });
                }
              },
              onerror: (error) => {
                resolve({ code: -1, message: "网络错误" });
              },
            });
          });

          if (md5Result.code === 0 && md5Result.data) {
            const dataList = Array.isArray(md5Result.data)
              ? md5Result.data
              : [md5Result.data];

            dataList.forEach((item, idx) => {
              const fid = fids[idx];
              if (!fid) return;

              let md5 = item.md5 || item.hash || "";

              if (md5 && md5.includes("==")) {
                try {
                  const binaryString = atob(md5);
                  if (binaryString.length === 16) {
                    md5 = Array.from(binaryString, (char) =>
                      char.charCodeAt(0).toString(16).padStart(2, "0"),
                    ).join("");
                  } else {
                    md5 = "";
                  }
                } catch (e) {
                  md5 = "";
                }
              }

              md5Map[fid] = md5;
            });
          } else {
            fids.forEach((fid) => (md5Map[fid] = ""));
          }
        } catch (e) {
          fids.forEach((fid) => (md5Map[fid] = ""));
        }

        totalProcessed += batch.length;
        if (onProgress) {
          onProgress(totalProcessed, fileItems.length);
        }

        await this.sleep(1000);
      }

      return md5Map;
    },

    generateRapidTransferJson(filesData) {
      const files = filesData.map((file) => ({
        path: file.path || file.file_name,
        etag: (file.etag || file.md5 || "").toLowerCase(),
        size: file.size,
      }));

      const totalSize = files.reduce((sum, f) => sum + f.size, 0);

      return {
        scriptVersion: "3.0.3",
        exportVersion: "1.0",
        usesBase62EtagsInExport: false,
        commonPath: "",
        files: files,
        totalFilesCount: files.length,
        totalSize: totalSize,
      };
    },

    showLoadingDialog(title, message) {
      const existingDialog = document.getElementById(
        "quark-json-loading-dialog",
      );
      if (existingDialog) {
        existingDialog.remove();
      }

      const dialog = document.createElement("div");
      dialog.id = "quark-json-loading-dialog";
      dialog.innerHTML = `
                <div style="position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.5); z-index: 9999; display: flex; align-items: center; justify-content: center;">
                    <div style="background: white; padding: 30px; border-radius: 8px; min-width: 350px; text-align: center;">
                        <div style="font-size: 18px; font-weight: bold; margin-bottom: 15px;">${title}</div>
                        <div id="quark-json-loading-message" style="font-size: 14px; color: #666; margin-bottom: 10px;">${message}</div>
                        <div id="quark-json-loading-detail" style="font-size: 12px; color: #999; margin-bottom: 10px; min-height: 18px;"></div>
                        <div style="margin-top: 15px;">
                            <div style="width: 100%; height: 8px; background: #f0f0f0; border-radius: 4px; overflow: hidden;">
                                <div id="quark-json-progress-bar" style="width: 0%; height: 100%; background: linear-gradient(90deg, #0d53ff, #52c41a); transition: width 0.3s;"></div>
                            </div>
                            <div id="quark-json-progress-text" style="font-size: 13px; color: #666; margin-top: 8px; font-weight: 500;">0%</div>
                        </div>
                    </div>
                </div>
            `;
      document.body.appendChild(dialog);
      return dialog;
    },

    updateProgress(processed, total, phase = "获取MD5") {
      const messageEl = document.getElementById("quark-json-loading-message");
      const detailEl = document.getElementById("quark-json-loading-detail");
      const progressBar = document.getElementById("quark-json-progress-bar");
      const progressText = document.getElementById("quark-json-progress-text");

      if (messageEl) {
        messageEl.textContent = `正在${phase}...`;
      }
      if (detailEl) {
        detailEl.textContent = `已处理 ${processed} / ${total} 个文件`;
      }
      if (progressBar) {
        const percent = total > 0 ? ((processed / total) * 100).toFixed(1) : 0;
        progressBar.style.width = `${percent}%`;
      }
      if (progressText) {
        const percent = total > 0 ? ((processed / total) * 100).toFixed(1) : 0;
        progressText.textContent = `${percent}%`;
      }
    },

    updateScanProgress(count) {
      const messageEl = document.getElementById("quark-json-loading-message");
      const detailEl = document.getElementById("quark-json-loading-detail");
      if (messageEl) {
        messageEl.textContent = "正在扫描文件...";
      }
      if (detailEl) {
        detailEl.textContent = `已发现 ${count} 个文件`;
      }
    },

    updateScanComplete(total) {
      const messageEl = document.getElementById("quark-json-loading-message");
      const detailEl = document.getElementById("quark-json-loading-detail");
      if (messageEl) {
        messageEl.textContent = "扫描完成,准备获取MD5...";
      }
      if (detailEl) {
        detailEl.textContent = `共发现 ${total} 个文件`;
      }
    },

    closeLoadingDialog() {
      const dialog = document.getElementById("quark-json-loading-dialog");
      if (dialog) {
        dialog.remove();
      }
    },

    showResultDialog(json) {
      const jsonStr = JSON.stringify(json, null, 2);
      const dialog = document.createElement("div");
      dialog.innerHTML = `
                <div style="position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.5); z-index: 9999; display: flex; align-items: center; justify-content: center;">
                    <div style="background: white; padding: 30px; border-radius: 8px; width: 80%; max-width: 800px; max-height: 80vh; display: flex; flex-direction: column;">
                        <div style="font-size: 18px; font-weight: bold; margin-bottom: 15px;">秒传JSON生成成功</div>
                        <div style="flex: 1; overflow: auto; background: #f5f5f5; padding: 15px; border-radius: 4px; font-family: monospace; font-size: 12px; margin-bottom: 15px;">
                            <pre style="margin: 0; white-space: pre-wrap; word-wrap: break-word;">${jsonStr}</pre>
                        </div>
                        <div style="display: flex; gap: 10px; justify-content: flex-end;">
                            <button id="quark-json-copy-btn" style="padding: 8px 20px; background: #0d53ff; color: white; border: none; border-radius: 4px; cursor: pointer;">复制JSON</button>
                            <button id="quark-json-download-btn" style="padding: 8px 20px; background: #52c41a; color: white; border: none; border-radius: 4px; cursor: pointer;">下载文件</button>
                            <button id="quark-json-close-btn" style="padding: 8px 20px; background: #d9d9d9; color: #333; border: none; border-radius: 4px; cursor: pointer;">关闭</button>
                        </div>
                    </div>
                </div>
            `;
      document.body.appendChild(dialog);

      document.getElementById("quark-json-copy-btn").onclick = () => {
        GM_setClipboard(jsonStr);
        this.showToast("已复制到剪贴板");
      };

      document.getElementById("quark-json-download-btn").onclick = () => {
        const blob = new Blob([jsonStr], { type: "application/json" });
        const url = URL.createObjectURL(blob);
        const a = document.createElement("a");
        a.href = url;
        a.download = `quark_rapid_transfer_${Date.now()}.json`;
        a.click();
        URL.revokeObjectURL(url);
        this.showToast("下载已开始");
      };

      document.getElementById("quark-json-close-btn").onclick = () => {
        dialog.remove();
      };
    },

    showError(message, showCookieButton = false) {
      const dialog = document.createElement("div");
      dialog.id = "quark-json-error-dialog";
      dialog.innerHTML = `
        <div style="position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.5); z-index: 10001; display: flex; align-items: center; justify-content: center; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';">
            <div style="background: white; padding: 24px; border-radius: 8px; width: 90%; max-width: 420px; box-shadow: 0 4px 12px rgba(0,0,0,0.15); display: flex; flex-direction: column; align-items: center;">
                <div style="color: #ff4d4f; margin-bottom: 16px;">
                    <svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" fill="currentColor" viewBox="0 0 16 16">
                        <path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zM5.354 4.646a.5.5 0 1 0-.708.708L7.293 8l-2.647 2.646a.5.5 0 0 0 .708.708L8 8.707l2.646 2.647a.5.5 0 0 0 .708-.708L8.707 8l2.647-2.646a.5.5 0 0 0-.708-.708L8 7.293 5.354 4.646z"/>
                    </svg>
                </div>
                <div style="font-size: 20px; font-weight: 600; margin-bottom: 8px; color: #333;">操作失败</div>
                <div style="font-size: 14px; color: #555; margin-bottom: 24px; text-align: center; white-space: pre-line;">${message}</div>
                <div style="display: flex; gap: 12px; justify-content: center; width: 100%;">
                    ${showCookieButton ? '<button id="quark-json-error-cookie-btn" style="flex: 1; padding: 10px 20px; background: #0d53ff; color: white; border: none; border-radius: 6px; cursor: pointer; font-size: 14px; font-weight: 500;">修改Cookie</button>' : ""}
                    <button id="quark-json-error-close-btn" style="flex: 1; padding: 10px 20px; background: #f0f0f0; color: #333; border: 1px solid #d9d9d9; border-radius: 6px; cursor: pointer; font-size: 14px; font-weight: 500;">确定</button>
                </div>
            </div>
        </div>
    `;
      document.body.appendChild(dialog);

      if (showCookieButton) {
        document.getElementById("quark-json-error-cookie-btn").onclick = () => {
          dialog.remove();
          this.showCookieInputDialog(null, this.getCachedCookie());
        };
      }

      document.getElementById("quark-json-error-close-btn").onclick = () => {
        dialog.remove();
      };
    },

    showToast(message) {
      const existingToast = document.getElementById("quark-json-toast");
      if (existingToast) {
        existingToast.remove();
      }

      const toast = document.createElement("div");
      toast.id = "quark-json-toast";
      toast.textContent = message;
      toast.style.cssText = `
            position: fixed;
            top: 20px;
            left: 50%;
            transform: translateX(-50%);
            background-color: rgba(0, 0, 0, 0.75);
            color: white;
            padding: 12px 24px;
            border-radius: 25px;
            font-size: 14px;
            font-weight: 500;
            z-index: 10002;
            opacity: 0;
            transition: opacity 0.3s ease-in-out, top 0.3s ease-in-out;
        `;

      document.body.appendChild(toast);

      setTimeout(() => {
        toast.style.opacity = "1";
        toast.style.top = "40px";
      }, 10);

      setTimeout(() => {
        toast.style.opacity = "0";
        toast.style.top = "20px";
        setTimeout(() => {
          toast.remove();
        }, 300);
      }, 2500);
    },

    parseSize(sizeStr) {
      if (typeof sizeStr === "number") {
        return sizeStr;
      }
      if (typeof sizeStr !== "string") {
        return 0;
      }
      const sizeMatch = sizeStr.match(/^([\d.]+)\s*([a-z]+)/i);
      if (!sizeMatch) {
        const num = parseInt(sizeStr, 10);
        return isNaN(num) ? 0 : num;
      }
      const size = parseFloat(sizeMatch[1]);
      const unit = sizeMatch[2].toUpperCase();
      switch (unit) {
        case "G":
        case "GB":
          return Math.round(size * 1024 * 1024 * 1024);
        case "M":
        case "MB":
          return Math.round(size * 1024 * 1024);
        case "K":
        case "KB":
          return Math.round(size * 1024);
        case "B":
        default:
          return Math.round(size);
      }
    },
  };

  const tianyiService = {
    getSelectedFiles() {
      try {
        if (typeof unsafeWindow !== "undefined") {
          let list;
          if (/\/web\/share/.test(location.href)) {
            list = unsafeWindow.shareUser?.getSelectedFileList();
          } else {
            list = unsafeWindow.file?.getSelectedFileList();
          }
          if (list && list.length > 0) {
            return list;
          }
        }
      } catch (e) {
        // ignore
      }

      const selectedItems = [];
      let selectedElements = document.querySelectorAll("li.c-file-item-select");

      if (selectedElements.length === 0) {
        const checkedBoxes = document.querySelectorAll(".ant-checkbox-checked");
        if (checkedBoxes.length > 0) {
          selectedElements = Array.from(checkedBoxes)
            .map((box) => box.closest("li.c-file-item"))
            .filter((el) => el);
        }
      }

      if (selectedElements.length === 0) {
        return [];
      }

      selectedElements.forEach((itemEl) => {
        if (itemEl.__vue__) {
          const vueInstance = itemEl.__vue__;
          const fileData =
            vueInstance.fileItem ||
            vueInstance.fileInfo ||
            vueInstance.item ||
            vueInstance.file;
          if (fileData) {
            if (
              !selectedItems.some(
                (item) => item.fileId === (fileData.id || fileData.fileId),
              )
            ) {
              const normalizedItem = {
                fileId: fileData.id || fileData.fileId,
                fileName: fileData.name || fileData.fileName,
                isFolder: fileData.isFolder || fileData.fileCata === 2,
                md5: fileData.md5,
                size: fileData.size,
              };
              selectedItems.push(normalizedItem);
            }
          }
        }
      });
      return selectedItems;
    },

    async getPersonalFolderFiles(folderId, path = "", onProgress = null) {
      const files = [];
      let pageNum = 1;
      const pageSize = 100;

      while (true) {
        const appKey = "600100422";
        const timestamp = Date.now().toString();
        const urlParams = {
          folderId: folderId,
          pageNum: pageNum,
          pageSize: pageSize,
          orderBy: "lastOpTime",
          descending: "true",
        };

        const signParams = {
          ...urlParams,
          Timestamp: timestamp,
          AppKey: appKey,
        };
        const signature = this.get189Signature(signParams);

        const url = `https://cloud.189.cn/api/open/file/listFiles.action?${new URLSearchParams(urlParams)}`;

        const text = await utils.get(url, {
          Accept: "application/json;charset=UTF-8",
          "Sign-Type": "1",
          Signature: signature,
          Timestamp: timestamp,
          AppKey: appKey,
        });

        const data = JSON.parse(text);

        if (data.res_code !== 0) break;

        const fileList = data.fileListAO?.fileList || [];
        const folderList = data.fileListAO?.folderList || [];

        if (fileList.length === 0 && folderList.length === 0) break;

        for (const file of fileList) {
          const filePath = path ? `${path}/${file.name}` : file.name;
          files.push({
            path: filePath,
            etag: (file.md5 || "").toLowerCase(),
            size: file.size,
            fileId: file.id,
          });
          if (onProgress) onProgress();
        }

        for (const folder of folderList) {
          const folderPath = path ? `${path}/${folder.name}` : folder.name;
          const subFiles = await this.getPersonalFolderFiles(
            folder.id,
            folderPath,
            onProgress,
          );
          files.push(...subFiles);
        }

        if (fileList.length + folderList.length < pageSize) break;
        pageNum++;
      }
      return files;
    },

    async getBaseShareInfo(shareUrl, sharePwd) {
      let match =
        shareUrl.match(/\/t\/([a-zA-Z0-9]+)/) ||
        shareUrl.match(/[?&]code=([a-zA-Z0-9]+)/);
      if (!match) throw new Error("无效的189网盘分享链接");

      const shareCode = match[1];
      let accessCode = sharePwd || "";

      if (!accessCode) {
        const cookieName = `share_${shareCode}`;
        const cookiePwd = utils.getCookie(cookieName);
        if (cookiePwd) {
          accessCode = cookiePwd;
        } else {
          try {
            const decodedUrl = decodeURIComponent(shareUrl);
            const pwdMatch = decodedUrl.match(
              /[((]访问码[::]\s*([a-zA-Z0-9]+)/,
            );
            if (pwdMatch && pwdMatch[1]) {
              accessCode = pwdMatch[1];
            }
          } catch (e) {
            /* ignore decoding errors */
          }
        }
      }

      let shareId = shareCode;

      if (accessCode) {
        const checkUrl = `https://cloud.189.cn/api/open/share/checkAccessCode.action?shareCode=${shareCode}&accessCode=${accessCode}`;
        try {
          const checkText = await utils.get(checkUrl, {
            Accept: "application/json;charset=UTF-8",
            Referer: "https://cloud.189.cn/web/main/",
          });
          const checkData = JSON.parse(checkText);
          if (checkData.shareId) shareId = checkData.shareId;
        } catch (e) {
          /* ignore */
        }
      }

      const params = { shareCode, accessCode: accessCode };
      const timestamp = Date.now().toString();
      const appKey = "600100422";
      const signData = { ...params, Timestamp: timestamp, AppKey: appKey };
      const signature = this.get189Signature(signData);
      const apiUrl = `https://cloud.189.cn/api/open/share/getShareInfoByCodeV2.action?${new URLSearchParams(params)}`;

      const text = await utils.get(apiUrl, {
        Accept: "application/json;charset=UTF-8",
        "Sign-Type": "1",
        Signature: signature,
        Timestamp: timestamp,
        AppKey: appKey,
        Referer: "https://cloud.189.cn/web/main/",
      });

      let data;
      try {
        data = JSON.parse(
          text.replace(
            /"(id|fileId|parentId|shareId)":"?(\d{15,})"?/g,
            '"$1":"$2"',
          ),
        );
      } catch (e) {
        throw new Error("解析分享信息失败");
      }

      if (data.res_code !== 0) {
        if (data.res_code === 40401 && !accessCode)
          throw new Error("该分享需要提取码,请输入提取码");
        throw new Error(`获取分享信息失败: ${data.res_message || "未知错误"}`);
      }

      return {
        shareId: data.shareId || shareId,
        shareMode: data.shareMode || "0",
        accessCode: accessCode,
        shareCode: shareCode,
      };
    },

    async createRapidTransfer(shareUrl, sharePwd) {
      let match = shareUrl.match(/\/t\/([a-zA-Z0-9]+)/);
      if (!match) {
        match = shareUrl.match(/[?&]code=([a-zA-Z0-9]+)/);
      }
      if (!match)
        throw new Error(
          "无效的189网盘分享链接 (支持格式: https://cloud.189.cn/t/xxx 或 https://cloud.189.cn/web/share?code=xxx)",
        );

      const shareCode = match[1];
      let shareId = shareCode;

      if (sharePwd) {
        const checkUrl = `https://cloud.189.cn/api/open/share/checkAccessCode.action?shareCode=${shareCode}&accessCode=${sharePwd}`;
        const checkText = await utils.get(checkUrl, {
          Accept: "application/json;charset=UTF-8",
          Referer: "https://cloud.189.cn/web/main/",
        });
        try {
          const checkData = JSON.parse(checkText);
          if (checkData.shareId) {
            shareId = checkData.shareId;
          }
        } catch (e) {
          // a
        }
      }

      const params = { shareCode, accessCode: sharePwd || "" };
      const timestamp = Date.now().toString();
      const appKey = "600100422";
      const signData = { ...params, Timestamp: timestamp, AppKey: appKey };
      const signature = this.get189Signature(signData);
      const queryString = new URLSearchParams(params).toString();
      const apiUrl = `https://cloud.189.cn/api/open/share/getShareInfoByCodeV2.action?${queryString}`;

      const text = await utils.get(apiUrl, {
        Accept: "application/json;charset=UTF-8",
        "Sign-Type": "1",
        Signature: signature,
        Timestamp: timestamp,
        AppKey: appKey,
        Referer: "https://cloud.189.cn/web/main/",
      });

      let data;
      if (text.trim().startsWith("<")) {
        data = this.parseXMLResponse(text);
      } else {
        try {
          const fixedText = text.replace(
            /"(id|fileId|parentId|shareId)":"?(\d{15,})"?/g,
            '"$1":"$2"',
          );
          data = JSON.parse(fixedText);
        } catch (e) {
          data = JSON.parse(text);
        }
      }

      if (data.res_code !== 0) {
        if (data.res_code === 40401 && !sharePwd) {
          throw new Error("该分享需要提取码,请输入提取码");
        }
        throw new Error(
          `获取189分享信息失败: ${data.res_message || "未知错误"}`,
        );
      }

      if (data.shareId && data.shareId !== shareCode) {
        shareId = data.shareId;
      }

      const { fileId, needAccessCode, isFolder, shareMode = "0" } = data;

      if (!shareId || !fileId) {
        if (needAccessCode === "1" && !sharePwd) {
          throw new Error("该分享需要提取码,请输入提取码");
        }
        throw new Error("获取189分享信息失败,可能是分享链接无效或已过期");
      }

      const files = await this.get189ShareFiles(
        shareId,
        fileId,
        fileId,
        "",
        shareMode,
        sharePwd,
        shareCode,
      );

      return {
        commonPath: "",
        files,
        totalFilesCount: files.length,
        totalSize: files.reduce((sum, f) => sum + f.size, 0),
      };
    },

    async get189ShareFiles(
      shareId,
      shareDirFileId,
      fileId,
      path = "",
      shareMode = "0",
      accessCode = "",
      shareCode = "",
      onProgress = null,
    ) {
      const files = [];
      let page = 1;

      while (true) {
        const params = {
          pageNum: page.toString(),
          pageSize: "100",
          fileId: fileId.toString(),
          shareDirFileId: shareDirFileId.toString(),
          isFolder: "true",
          shareId: shareId.toString(),
          shareMode: shareMode,
          iconOption: "5",
          orderBy: "lastOpTime",
          descending: "true",
          accessCode: accessCode || "",
        };
        const queryString = new URLSearchParams(params).toString();
        const url = `https://cloud.189.cn/api/open/share/listShareDir.action?${queryString}`;

        const headers = {
          Accept: "application/json;charset=UTF-8",
          Referer: "https://cloud.189.cn/web/main/",
        };
        if (shareCode && accessCode) {
          headers["Cookie"] = `share_${shareCode}=${accessCode}`;
        }

        const text = await utils.get(url, headers);
        let data;
        try {
          const fixedText = text.replace(
            /"(id|fileId|parentId|shareId)":(\d{15,})/g,
            '"$1":"$2"',
          );
          data = JSON.parse(fixedText);
        } catch (e) {
          break;
        }

        if (data.res_code !== 0) {
          if (data.res_code === "FileNotFound" && path) {
            console.log(
              `[189] 警告:子文件夹 "${path}" 访问失败,189网盘分享可能需要登录才能访问子文件夹`,
            );
          }
          break;
        }

        const fileList = data.fileListAO?.fileList || [];
        const folderList = data.fileListAO?.folderList || [];

        for (const file of fileList) {
          const filePath = path ? `${path}/${file.name}` : file.name;
          files.push({
            path: filePath,
            etag: (file.md5 || "").toLowerCase(),
            size: file.size,
          });
          if (onProgress) onProgress();
        }

        for (const folder of folderList) {
          const folderPath = path ? `${path}/${folder.name}` : folder.name;
          const subFiles = await this.get189ShareFiles(
            shareId,
            folder.id,
            folder.id,
            folderPath,
            shareMode,
            accessCode,
            shareCode,
            onProgress,
          );
          files.push(...subFiles);
        }

        if (fileList.length + folderList.length < 100) {
          break;
        }
        page++;
      }
      return files;
    },

    parseXMLResponse(xmlText) {
      const getTagValue = (xml, tagName) =>
        xml.match(new RegExp(`<${tagName}>([^<]*)<\/${tagName}>`, "i"))?.[1] ||
        null;
      return {
        res_code: parseInt(getTagValue(xmlText, "res_code") || "0"),
        res_message: getTagValue(xmlText, "res_message") || "",
        shareId: getTagValue(xmlText, "shareId") || "",
        fileId: getTagValue(xmlText, "fileId") || "",
        shareMode: getTagValue(xmlText, "shareMode") || "0",
        isFolder: getTagValue(xmlText, "isFolder") === "true",
        needAccessCode: getTagValue(xmlText, "needAccessCode") || "0",
        fileName: getTagValue(xmlText, "fileName") || "",
      };
    },

    get189Signature(params) {
      const sortedKeys = Object.keys(params).sort();
      const sortedParams = sortedKeys
        .map((key) => `${key}=${params[key]}`)
        .join("&");
      return this.simpleMD5(sortedParams);
    },

    simpleMD5(str) {
      function rotateLeft(value, shift) {
        return (value << shift) | (value >>> (32 - shift));
      }
      function addUnsigned(x, y) {
        const lsw = (x & 0xffff) + (y & 0xffff);
        const msw = (x >> 16) + (y >> 16) + (lsw >> 16);
        return (msw << 16) | (lsw & 0xffff);
      }
      function F(x, y, z) {
        return (x & y) | (~x & z);
      }
      function G(x, y, z) {
        return (x & z) | (y & ~z);
      }
      function H(x, y, z) {
        return x ^ y ^ z;
      }
      function I(x, y, z) {
        return y ^ (x | ~z);
      }
      function FF(a, b, c, d, x, s, ac) {
        a = addUnsigned(a, addUnsigned(addUnsigned(F(b, c, d), x), ac));
        return addUnsigned(rotateLeft(a, s), b);
      }
      function GG(a, b, c, d, x, s, ac) {
        a = addUnsigned(a, addUnsigned(addUnsigned(G(b, c, d), x), ac));
        return addUnsigned(rotateLeft(a, s), b);
      }
      function HH(a, b, c, d, x, s, ac) {
        a = addUnsigned(a, addUnsigned(addUnsigned(H(b, c, d), x), ac));
        return addUnsigned(rotateLeft(a, s), b);
      }
      function II(a, b, c, d, x, s, ac) {
        a = addUnsigned(a, addUnsigned(addUnsigned(I(b, c, d), x), ac));
        return addUnsigned(rotateLeft(a, s), b);
      }
      function convertToWordArray(str) {
        const lWordCount = ((str.length + 8) >>> 6) + 1;
        const lMessageLength = lWordCount * 16;
        const lWordArray = new Array(lMessageLength - 1);
        let lBytePosition = 0;
        let lByteCount = 0;
        while (lByteCount < str.length) {
          const lWordIndex = (lByteCount - (lByteCount % 4)) / 4;
          lBytePosition = (lByteCount % 4) * 8;
          lWordArray[lWordIndex] =
            lWordArray[lWordIndex] |
            (str.charCodeAt(lByteCount) << lBytePosition);
          lByteCount++;
        }
        const lWordIndex = (lByteCount - (lByteCount % 4)) / 4;
        lBytePosition = (lByteCount % 4) * 8;
        lWordArray[lWordIndex] =
          lWordArray[lWordIndex] | (0x80 << lBytePosition);
        lWordArray[lMessageLength - 2] = str.length << 3;
        lWordArray[lMessageLength - 1] = str.length >>> 29;
        return lWordArray;
      }
      function wordToHex(value) {
        let result = "";
        for (let i = 0; i <= 3; i++) {
          const byte = (value >>> (i * 8)) & 255;
          result += ("0" + byte.toString(16)).slice(-2);
        }
        return result;
      }
      const x = convertToWordArray(str);
      let a = 0x67452301,
        b = 0xefcdab89,
        c = 0x98badcfe,
        d = 0x10325476;
      const S11 = 7,
        S12 = 12,
        S13 = 17,
        S14 = 22;
      const S21 = 5,
        S22 = 9,
        S23 = 14,
        S24 = 20;
      const S31 = 4,
        S32 = 11,
        S33 = 16,
        S34 = 23;
      const S41 = 6,
        S42 = 10,
        S43 = 15,
        S44 = 21;
      for (let k = 0; k < x.length; k += 16) {
        const AA = a,
          BB = b,
          CC = c,
          DD = d;
        a = FF(a, b, c, d, x[k + 0], S11, 0xd76aa478);
        d = FF(d, a, b, c, x[k + 1], S12, 0xe8c7b756);
        c = FF(c, d, a, b, x[k + 2], S13, 0x242070db);
        b = FF(b, c, d, a, x[k + 3], S14, 0xc1bdceee);
        a = FF(a, b, c, d, x[k + 4], S11, 0xf57c0faf);
        d = FF(d, a, b, c, x[k + 5], S12, 0x4787c62a);
        c = FF(c, d, a, b, x[k + 6], S13, 0xa8304613);
        b = FF(b, c, d, a, x[k + 7], S14, 0xfd469501);
        a = FF(a, b, c, d, x[k + 8], S11, 0x698098d8);
        d = FF(d, a, b, c, x[k + 9], S12, 0x8b44f7af);
        c = FF(c, d, a, b, x[k + 10], S13, 0xffff5bb1);
        b = FF(b, c, d, a, x[k + 11], S14, 0x895cd7be);
        a = FF(a, b, c, d, x[k + 12], S11, 0x6b901122);
        d = FF(d, a, b, c, x[k + 13], S12, 0xfd987193);
        c = FF(c, d, a, b, x[k + 14], S13, 0xa679438e);
        b = FF(b, c, d, a, x[k + 15], S14, 0x49b40821);
        a = GG(a, b, c, d, x[k + 1], S21, 0xf61e2562);
        d = GG(d, a, b, c, x[k + 6], S22, 0xc040b340);
        c = GG(c, d, a, b, x[k + 11], S23, 0x265e5a51);
        b = GG(b, c, d, a, x[k + 0], S24, 0xe9b6c7aa);
        a = GG(a, b, c, d, x[k + 5], S21, 0xd62f105d);
        d = GG(d, a, b, c, x[k + 10], S22, 0x2441453);
        c = GG(c, d, a, b, x[k + 15], S23, 0xd8a1e681);
        b = GG(b, c, d, a, x[k + 4], S24, 0xe7d3fbc8);
        a = GG(a, b, c, d, x[k + 9], S21, 0x21e1cde6);
        d = GG(d, a, b, c, x[k + 14], S22, 0xc33707d6);
        c = GG(c, d, a, b, x[k + 3], S23, 0xf4d50d87);
        b = GG(b, c, d, a, x[k + 8], S24, 0x455a14ed);
        a = GG(a, b, c, d, x[k + 13], S21, 0xa9e3e905);
        d = GG(d, a, b, c, x[k + 2], S22, 0xfcefa3f8);
        c = GG(c, d, a, b, x[k + 7], S23, 0x676f02d9);
        b = GG(b, c, d, a, x[k + 12], S24, 0x8d2a4c8a);
        a = HH(a, b, c, d, x[k + 5], S31, 0xfffa3942);
        d = HH(d, a, b, c, x[k + 8], S32, 0x8771f681);
        c = HH(c, d, a, b, x[k + 11], S33, 0x6d9d6122);
        b = HH(b, c, d, a, x[k + 14], S34, 0xfde5380c);
        a = HH(a, b, c, d, x[k + 1], S31, 0xa4beea44);
        d = HH(d, a, b, c, x[k + 4], S32, 0x4bdecfa9);
        c = HH(c, d, a, b, x[k + 7], S33, 0xf6bb4b60);
        b = HH(b, c, d, a, x[k + 10], S34, 0xbebfbc70);
        a = HH(a, b, c, d, x[k + 13], S31, 0x289b7ec6);
        d = HH(d, a, b, c, x[k + 0], S32, 0xeaa127fa);
        c = HH(c, d, a, b, x[k + 3], S33, 0xd4ef3085);
        b = HH(b, c, d, a, x[k + 6], S34, 0x4881d05);
        a = HH(a, b, c, d, x[k + 9], S31, 0xd9d4d039);
        d = HH(d, a, b, c, x[k + 12], S32, 0xe6db99e5);
        c = HH(c, d, a, b, x[k + 15], S33, 0x1fa27cf8);
        b = HH(b, c, d, a, x[k + 2], S34, 0xc4ac5665);
        a = II(a, b, c, d, x[k + 0], S41, 0xf4292244);
        d = II(d, a, b, c, x[k + 7], S42, 0x432aff97);
        c = II(c, d, a, b, x[k + 14], S43, 0xab9423a7);
        b = II(b, c, d, a, x[k + 5], S44, 0xfc93a039);
        a = II(a, b, c, d, x[k + 12], S41, 0x655b59c3);
        d = II(d, a, b, c, x[k + 3], S42, 0x8f0ccc92);
        c = II(c, d, a, b, x[k + 10], S43, 0xffeff47d);
        b = II(b, c, d, a, x[k + 1], S44, 0x85845dd1);
        a = II(a, b, c, d, x[k + 8], S41, 0x6fa87e4f);
        d = II(d, a, b, c, x[k + 15], S42, 0xfe2ce6e0);
        c = II(c, d, a, b, x[k + 6], S43, 0xa3014314);
        b = II(b, c, d, a, x[k + 13], S44, 0x4e0811a1);
        a = II(a, b, c, d, x[k + 4], S41, 0xf7537e82);
        d = II(d, a, b, c, x[k + 11], S42, 0xbd3af235);
        c = II(c, d, a, b, x[k + 2], S43, 0x2ad7d2bb);
        b = II(b, c, d, a, x[k + 9], S44, 0xeb86d391);
        a = addUnsigned(a, AA);
        b = addUnsigned(b, BB);
        c = addUnsigned(c, CC);
        d = addUnsigned(d, DD);
      }
      return (
        wordToHex(a) +
        wordToHex(b) +
        wordToHex(c) +
        wordToHex(d)
      ).toLowerCase();
    },
  };

  async function generateJson() {
    try {
      const hostname = location.hostname;
      const path = location.pathname;

      if (hostname.includes("cloud.189.cn")) {
        if (path.startsWith("/web/main")) {
          await generateTianyiHomeJson();
        } else {
          await generateTianyiShareJson();
        }
      } else if (hostname.includes("quark.cn")) {
        const isSharePage = /^\/(s|share)\//.test(path);
        if (isSharePage) {
          await generateShareJson();
        } else {
          await generateHomeJson();
        }
      }
    } catch (error) {
      utils.closeLoadingDialog();
      utils.showError(error.message || "生成JSON失败");
    }
  }

  async function generateTianyiShareJson() {
    utils.showLoadingDialog("正在扫描文件", "准备中...");

    try {
      const selectedFiles = tianyiService.getSelectedFiles();

      if (selectedFiles.length === 0) {
        utils.closeLoadingDialog();
        utils.showError("请先勾选要生成JSON的文件或文件夹");
        return;
      }

      const shareUrl = window.location.href;
      let sharePwd = "";

      const allFiles = [];
      let itemsProcessed = 0;
      let filesFound = 0;

      const onProgress = () => {
        filesFound++;
        utils.updateScanProgress(filesFound);
      };

      utils.updateProgress(0, selectedFiles.length, "扫描文件");
      utils.updateScanProgress(0);

      const { shareId, shareMode, accessCode, shareCode } =
        await tianyiService.getBaseShareInfo(shareUrl, sharePwd);

      for (const item of selectedFiles) {
        if (item.isFolder) {
          const folderPath = item.fileName;
          const subFiles = await tianyiService.get189ShareFiles(
            shareId,
            item.fileId,
            item.fileId,
            folderPath,
            shareMode,
            accessCode,
            shareCode,
            onProgress,
          );
          allFiles.push(...subFiles);
        } else {
          allFiles.push({
            path: item.fileName,
            etag: (item.md5 || "").toLowerCase(),
            size: item.size,
          });
          onProgress();
        }
        itemsProcessed++;
        utils.updateProgress(itemsProcessed, selectedFiles.length, "扫描文件");
      }

      utils.updateScanComplete(allFiles.length);
      await utils.sleep(300);

      const finalJson = utils.generateRapidTransferJson(allFiles);
      utils.closeLoadingDialog();
      utils.showResultDialog(finalJson);
    } catch (error) {
      utils.closeLoadingDialog();
      utils.showError(error.message || "生成JSON失败");
    }
  }

  async function generateTianyiHomeJson() {
    utils.showLoadingDialog("正在扫描文件", "准备中...");

    try {
      const selectedFiles = tianyiService.getSelectedFiles();
      if (selectedFiles.length === 0) {
        utils.closeLoadingDialog();
        utils.showError("请先勾选要生成JSON的文件或文件夹");
        return;
      }

      const allFiles = [];
      let filesFound = 0;
      const onProgress = () => {
        filesFound++;
        utils.updateScanProgress(filesFound);
      };
      utils.updateScanProgress(0);

      for (const item of selectedFiles) {
        if (item.isFolder) {
          const subFiles = await tianyiService.getPersonalFolderFiles(
            item.fileId,
            item.fileName,
            onProgress,
          );
          allFiles.push(...subFiles);
        } else {
          allFiles.push({
            path: item.fileName,
            size: item.size,
            fileId: item.fileId,
            etag: (item.md5 || "").toLowerCase(),
          });
          onProgress();
        }
      }

      utils.updateScanComplete(allFiles.length);
      await utils.sleep(300);

      const filesMissingMd5 = allFiles.filter((f) => !f.etag);
      if (filesMissingMd5.length > 0) {
        utils.updateProgress(0, filesMissingMd5.length, "获取MD5");
        let md5Processed = 0;

        for (const file of filesMissingMd5) {
          try {
            const details = await tianyiService.getPersonalFileDetails(
              file.fileId,
            );
            file.etag = (details.md5 || "").toLowerCase();
          } catch (e) {
            console.error(`获取文件MD5失败: ${file.path}`, e);
          }
          md5Processed++;
          utils.updateProgress(md5Processed, filesMissingMd5.length, "获取MD5");
          await utils.sleep(100); // 防止请求过快
        }
      }

      const finalJson = utils.generateRapidTransferJson(allFiles);
      utils.closeLoadingDialog();
      utils.showResultDialog(finalJson);
    } catch (error) {
      console.error("### generateTianyiHomeJson error:", error);
      utils.closeLoadingDialog();
      utils.showError(error.message || "生成JSON失败");
    }
  }

  async function generateHomeJson() {
    const selectedItems = utils.getSelectedList();

    if (selectedItems.length === 0) {
      utils.showError("请先勾选要生成JSON的文件或文件夹");
      return;
    }

    utils.showLoadingDialog("正在扫描文件", "准备中...");

    const currentPath = utils.getCurrentPath();

    const allFiles = [];
    for (const item of selectedItems) {
      if (item.file) {
        const filePath = currentPath
          ? `${currentPath}/${item.file_name}`
          : item.file_name;
        allFiles.push({ ...item, path: filePath });
        utils.updateScanProgress(allFiles.length);
      } else if (item.dir) {
        const folderPath = currentPath
          ? `${currentPath}/${item.file_name}`
          : item.file_name;
        const folderFiles = await utils.getFolderFiles(
          item.fid,
          folderPath,
          (count) => {
            utils.updateScanProgress(allFiles.length + count);
          },
        );
        allFiles.push(...folderFiles);
      }
    }

    if (allFiles.length === 0) {
      utils.closeLoadingDialog();
      utils.showError("没有找到任何文件");
      return;
    }

    const filesData = await utils.getFilesWithMd5(
      allFiles,
      (processed, total) => {
        utils.updateProgress(processed, total, "获取MD5");
      },
    );

    const json = utils.generateRapidTransferJson(filesData);

    utils.closeLoadingDialog();

    utils.showResultDialog(json);
  }

  async function generateShareJson() {
    const selectedItems = utils.getSelectedList();

    const match = location.pathname.match(/\/(s|share)\/([a-zA-Z0-9]+)/);
    if (!match) {
      utils.showError("无法获取分享ID");
      return;
    }
    const shareId = match[2];

    let cookie = utils.getCachedCookie();

    if (!cookie || cookie.length < 10) {
      utils.showCookieInputDialog((newCookie) => {
        setTimeout(() => generateShareJson(), 100);
      });
      return;
    }

    utils.showLoadingDialog("正在扫描文件", "准备中...");

    try {
      const stoken = await utils.getShareToken(shareId, "", cookie);

      const fileItems = await utils.scanQuarkShareFiles(
        shareId,
        stoken,
        cookie,
        0,
        "",
      );

      if (fileItems.length === 0) {
        utils.closeLoadingDialog();
        utils.showError("没有找到任何文件", true);
        return;
      }

      utils.updateScanComplete(fileItems.length);
      await utils.sleep(300);

      const md5Map = await utils.batchGetShareFilesMd5(
        shareId,
        stoken,
        cookie,
        fileItems,
        (processed, total) => {
          utils.updateProgress(processed, total, "获取分享文件MD5");
        },
      );

      const files = fileItems.map((item) => ({
        path: item.path,
        etag: (md5Map[item.fid] || "").toLowerCase(),
        size: item.size,
      }));

      const json = {
        scriptVersion: "3.0.3",
        exportVersion: "1.0",
        usesBase62EtagsInExport: false,
        commonPath: "",
        files,
        totalFilesCount: files.length,
        totalSize: files.reduce((sum, f) => sum + f.size, 0),
      };

      utils.closeLoadingDialog();

      utils.showResultDialog(json);
    } catch (error) {
      utils.closeLoadingDialog();
      const errorMsg = error.message || "生成JSON失败";
      const isCookieError =
        errorMsg.includes("登录") ||
        errorMsg.includes("token") ||
        errorMsg.includes("Cookie") ||
        errorMsg.includes("23018");
      utils.showError(
        errorMsg +
          (isCookieError ? "\n\n可能是Cookie失效,请尝试更新Cookie" : ""),
        isCookieError,
      );
    }
  }

  function addButton() {
    const hostname = location.hostname;
    let container;

    if (document.getElementById("quark-json-generator-btn")) {
      return;
    }

    if (hostname.includes("cloud.189.cn")) {
      const isMainPage = location.pathname.startsWith("/web/main");

      if (isMainPage) {
        container = document.querySelector(
          '[class*="FileHead_file-head-left"]',
        );
      } else {
        container = document.querySelector(".file-operate");
      }

      if (!container) return;

      const button = document.createElement("a");
      button.id = "quark-json-generator-btn";
      button.className = "btn";
      button.href = "javascript:;";
      button.textContent = "生成JSON";
      if (isMainPage) {
        button.style.cssText =
          "width: 76px; height: 30px; padding: 0; border-radius: 4px; line-height: 30px; color: #fff; text-align: center; font-size: 12px; background: #52c41a; border: 1px solid #46a219; position: relative; display: block; margin-right: 12px;";
      } else {
        button.style.cssText =
          "width: 116px; height: 36px; padding: 0; border-radius: 4px; line-height: 36px; color: #fff; text-align: center; font-size: 14px; background: #52c41a; border: 1px solid #46a219; position: relative; display: block;margin-right:20px;";
      }

      container.insertBefore(button, container.firstChild);

      if (!isMainPage) {
        const styleId = "quark-json-flex-style";
        if (!document.getElementById(styleId)) {
          const style = document.createElement("style");
          style.id = styleId;
          style.textContent = `
                  .outlink-box-b .file-operate {
                      display: flex !important;
                      flex-wrap: nowrap !important;
                      justify-content: flex-end !important;
                      align-items: center !important;
                      /* Override conflicting styles */
                      float: none !important;
                      text-align: unset !important;
                  }
                  .btn-save-as{
                  margin-left: 0 !important;
                  }
              `;
          document.head.appendChild(style);
        }
      }

      button.onclick = generateJson;
    } else if (hostname.includes("quark.cn")) {
      const path = location.pathname;
      const isSharePage = /^\/(s|share)\//.test(path);
      if (isSharePage) {
        container = document.querySelector(".share-btns");
        if (!container) {
          const alternatives = [
            ".ant-layout-content .operate-bar",
            ".share-detail-header .operate-bar",
            ".share-header-btns",
            ".share-operate-btns",
            "[class*='share'][class*='btn']",
            ".ant-btn-group",
          ];
          for (const selector of alternatives) {
            container = document.querySelector(selector);
            if (container) break;
          }
        }
      } else {
        container = document.querySelector(".btn-operate .btn-main");
      }
      if (!container) return;

      const buttonWrapper = document.createElement("div");
      buttonWrapper.id = "quark-json-generator-btn";
      buttonWrapper.className = "ant-dropdown-trigger pl-button-json";

      const isSharePageQuark = /^\/(s|share)\//.test(location.pathname);
      if (isSharePageQuark) {
        buttonWrapper.style.cssText =
          "display: inline-block; margin-left: 16px;";
        buttonWrapper.innerHTML = `
            <button type="button" class="ant-btn ant-btn-primary" style="background: #52c41a; border-color: #52c41a; height: 40px;">
                <svg style="width: 16px; height: 16px; margin-right: 4px; vertical-align: -3px;" viewBox="0 0 16 16" fill="currentColor"><path d="M8 2a.5.5 0 0 1 .5.5v5h5a.5.5 0 0 1 0 1h-5v5a.5.5 0 0 1-1 0v-5h-5a.5.5 0 0 1 0-1h5v-5A.5.5 0 0 1 8 2z"/></svg>
                <span>生成JSON</span>
            </button>`;
        container.appendChild(buttonWrapper);
      } else {
        buttonWrapper.style.cssText =
          "display: inline-block; margin-right: 16px;";
        buttonWrapper.innerHTML = `
            <div class="ant-upload ant-upload-select ant-upload-select-text">
                <button type="button" class="ant-btn ant-btn-primary" style="background: #52c41a; border-color: #52c41a;">
                    <svg style="width: 16px; height: 16px; margin-right: 4px; vertical-align: -3px;" viewBox="0 0 16 16" fill="currentColor"><path d="M8 2a.5.5 0 0 1 .5.5v5h5a.5.5 0 0 1 0 1h-5v5a.5.5 0 0 1-1 0v-5h-5a.5.5 0 0 1 0-1h5v-5A.5.5 0 0 1 8 2z"/></svg>
                    <span>生成JSON</span>
                </button>
            </div>`;
        container.insertBefore(buttonWrapper, container.firstChild);
      }
      buttonWrapper.querySelector("button").onclick = generateJson;
    }
  }
  function init() {
    const hostname = location.hostname;
    if (hostname.includes("quark.cn") || hostname.includes("cloud.189.cn")) {
      const observer = new MutationObserver(() => {
        addButton();
      });

      observer.observe(document.body, {
        childList: true,
        subtree: true,
      });

      addButton();
    }
  }

  if (document.readyState === "loading") {
    document.addEventListener("DOMContentLoaded", init);
  } else {
    init();
  }
})();