Greasy Fork

Greasy Fork is available in English.

夸克网盘树状目录

夸克网盘分享页显示树状列表,点击顶部右边笑脸即可

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         夸克网盘树状目录
// @version      1.0
// @description  夸克网盘分享页显示树状列表,点击顶部右边笑脸即可
// @author       sunzehui
// @license      MIT
// @match        https://pan.quark.cn/s/*
// @grant        GM_xmlhttpRequest
// @require       https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js
// @require https://cdnjs.cloudflare.com/ajax/libs/jquery.fancytree/2.38.3/jquery.fancytree-all-deps.min.js
// @namespace http://greasyfork.icu/users/454712
// ==/UserScript==

(function () {
  var listeners = [];
  var doc = window.document;
  varMutationObserver =
    window.MutationObserver || window.WebKitMutationObserver;
  var observer;

  function domReady(selector, fn) {
    // 储存选择器和回调函数
    listeners.push({
      selector: selector,
      fn: fn,
    });
    if (!observer) {
      // 监听document变化
      observer = new MutationObserver(check);
      observer.observe(doc.documentElement, {
        childList: true,
        subtree: true,
      });
    }
    // 检查该节点是否已经在DOM中
    check();
  }

  function check() {
    // 检查是否匹配已储存的节点
    for (var i = 0; i < listeners.length; i++) {
      var listener = listeners[i];
      // 检查指定节点是否有匹配
      var elements = doc.querySelectorAll(listener.selector);
      for (var j = 0; j < elements.length; j++) {
        var element = elements[j];
        // 确保回调函数只会对该元素调用一次
        if (!element.ready) {
          element.ready = true;
          // 对该节点调用回调函数
          listener.fn.call(element, element);
        }
      }
    }
  }

  // 对外暴露ready
  window.domReady = domReady;
})();
const api = {
  'fileList': "https://drive-pc.quark.cn/1/clouddrive/share/sharepage/detail"
}

class QuarkScript {
  constructor() {
    this.config = {
      insertContainer: "div.CommonHeader--container--LPZpeBK",
      tagClassname : 'script-tag',
      insertTreeViewContainer: ".DetailLayout--container--264z8Xd",
      lazyLoad: true,
      fancytreeCSS_CDN: "https://cdnjs.cloudflare.com/ajax/libs/jquery.fancytree/2.27.0/skin-win8/ui.fancytree.css"
    }
    this.api = api

    this.headers = {
      accept: "application/json, text/plain, */*",
      "accept-language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6",
      "content-type": "application/json;charset=UTF-8",
      "sec-fetch-mode": "cors",
      "sec-fetch-site": "same-site",
      "x-canary": "client=web,app=adrive,version=v2.3.1",
    };
    this.params = {
      pwd_id: this.getPwdId()
    }
    this.nowSelectNode = null;
    this.isLoading = false;
  }

  // ... existing code ...

  parseCookie(str) {
    // ... existing code ...
  }

  getPwdId() {
    const url = location.pathname;
    return url.match(/(?<=\/s\/)(\w+)(?=#)?/g)[0];
  }
  getStoken(){
    const tokenStorage = JSON.parse(sessionStorage.getItem("_share_args"));
    return tokenStorage.value.stoken ? tokenStorage.value.stoken : "";
  }

  loading(type = 'start') {
    const tag = $( '.' + this.config.tagClassname)

    if(this.isLoading==false && type == 'start'){
      this.isLoading = true;
      setTimeout(() => {
        if(!this.isLoading)return;
        if (tag.html() == 'o') {
          tag.html( "0");
        } else {
          tag.html( "o");
        }
        this.loading("start");
      }, 500);
    }

    if(this.isLoading==true && type == 'stop'){
      this.isLoading = false
      tag.html("&#128515;");
    }
  }
  async handleTagClick(){
    console.log('clicked')
      // const tokenStorage = JSON.parse(localStorage.getItem("shareToken"));
      // const token = tokenStorage.share_token ? tokenStorage.share_token : "";
      // headers["x-share-token"] = token;
    const $existsView = $('.tree-container')
    if($existsView.length > 0){
      return $existsView.show();
    }
    this.loading();
    await this.renderView();
    this.loading('stop');
  }

  renderTag() {
    const tag = document.createElement("div");
    tag.classList.add(this.config.tagClassname);
    tag.innerHTML = "&#128515;";

    let that = this;
    $(document).on('click','.'+this.config.tagClassname, function() {
      that.handleTagClick()
    })

    const insertContainer = this.config.insertContainer
    domReady(insertContainer,function(){
      document
        .querySelector(
          insertContainer
        )
        .appendChild(tag);
    })
  }

  listAdapter(list, isFirst = true) {
    return list.map((item) => {
      const hasFolder = !!item.children;
      const obj = {
        title: item.name,
        folder: hasFolder,
        expanded: isFirst,
      };
      if (hasFolder) {
        obj.children = this.listAdapter(item.children, false);
      }
      return obj;
    });
  }

  async buildFancytreeCfg() {
    const that = this
    const cfg = {
      selectMode: 1,
      autoScroll: true,
      activate: function (event, data) {
        console.log('active',data)
        that.nowSelectNode = data.node;
      },
    };
    const loadRootNode = async (event, data) => {
      const list = await this.getList({ parent_file_id: 0 });

      const children = await Promise.all(
        list.map(async (pItem) => {
          const cList = await this.getList({ parent_file_id: pItem.fid });
          return cList.map((cItem) => {
            return {
              title: cItem.file_name,
              folder: cItem.dir,
              key: cItem.fid,
              lazy: true,
            };
          });
        })
      );
      return list.map((item) => ({
        title: item.file_name,
        folder: item.dir,
        key: item.fid,
        expanded: true,
        lazy: true,
        children: children.flat(1),
      }));
    };

    const loadNode = function (event, data) {
      data.result = that.getList({ parent_file_id: data.node.key }).then((list) => {
        return list.map((item) => ({
          title: item.file_name,
          folder: item.dir,
          key: item.fid,
          lazy: item.dir,
        }));
      });
    };
    if (this.config.lazyLoad) {
      cfg["source"] = loadRootNode();
      cfg["lazyLoad"] = loadNode;
    } else {
      const tree = await this.buildTree();
      cfg["source"] = await this.listAdapter(tree.children);
    }
    return cfg;
  }

  async renderView() {
    const cfg = await this.buildFancytreeCfg();
    const $treeContainer = $(`
      <div class="tree-container">
        <div class="bar">
          <button class="btn sunzehuiBtn">进入选中文件夹</button>
          <button class="btn close-btn">X</button>
        </div>
        <div class="tree"></div>
      </div>
    `);
    $treeContainer.find('.tree').fancytree(cfg);

    const that = this;
    $(document).on('click','.tree-container .bar .sunzehuiBtn',function(){
      const selectedNode = that.nowSelectNode;
      if(!selectedNode || !selectedNode.folder) return alert('未选中文件夹');
      // 文件路径 = https://pan.quark.cn/s/{pwd_id}#/list/share/{文件id}-{文件名}/{文件id}-{文件名}/
      const pList = [...selectedNode.getParentList(), selectedNode];
      let filePath = `https://pan.quark.cn/s/${that.getPwdId()}#/list/share/`

      const link = pList.reduce((acc,cur)=>{
        return `${acc}${cur.key}-${cur.title}/`
      }, filePath)
      window.open(link, '_blank');
    })

    $(document).on('click','.tree-container .bar .close-btn',function(){
      $('.tree-container').hide();
    })


    const insertTreeViewContainer = this.config.insertTreeViewContainer
    domReady(insertTreeViewContainer, function () {
      $(insertTreeViewContainer).append($treeContainer)
    });
  }

  async getList({ parent_file_id }) {
    let url = new URL(this.api.fileList);
    let params = {
      pr: 'ucpro',
      fr: 'pc',
      uc_param_str:'',
      pwd_id: this.getPwdId(),
      stoken: this.getStoken(),
      pdir_fid: parent_file_id || 0,
      force: 0,
      _page: 1,
      _size: 50,
      _fetch_banner: 0,
      _fetch_share: 0,
      _fetch_total: 1,
      _sort: "file_type:asc,updated_at:desc",
      __dt: 959945,
      __t: +new Date()
    };
    Object.keys(params).forEach(key => url.searchParams.append(key, params[key]))
    const result = await fetch(
      url,
      {
        headers: this.headers,
        referrerPolicy: "origin",
        method: "GET",
        mode: "cors",
        credentials: "omit",
      }
    );
    const resp = await result.json();

    return resp.data.list;
  }

  async buildTree(parent_file_id) {
    const treeNode = {};
    const list = await this.getList({ parent_file_id });
    treeNode.children = [];
    for (let i = 0; i < list.length; i++) {
      let node = void 0;
      const item = list[i]
      if (item.dir) {
        node = await this.buildTree(item.fid);
        node.name = item.file_name;
      } else {
        node = item;
      }
      treeNode.children.push(node);
    }
    return treeNode;
  }

  insertCSS() {
    const cssElem = document.createElement("link");
    cssElem.setAttribute("rel", "stylesheet");
    cssElem.setAttribute(
      "href",
      this.config.fancytreeCSS_CDN
    );
    document.body.appendChild(cssElem);
    const cssElem2 = document.createElement("style");
    cssElem2.innerHTML = `
    .tree-container{
      height: 100%;
      background: #ecf0f1;
      position: fixed;
      top: 60px;
      z-index: 9999;
      left: 0;
      overflow-y:scroll
    }
    .tree-container .bar{
      background: #bdc3c7;
      padding: 0 20px;
      display: flex;
      justify-content: space-between;
      align-items: center;
      height: 40px;
    }
    .btn{
      padding: 0;
      height: 30px;
    }
    .sunzehuiBtn{
      display: inline-block;
      font-weight: 400;
      text-align: center;
      vertical-align: middle;
      user-select: none;
      border: 1px solid transparent;
      transition: color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;
      padding: 0 8px;
      font-size: 14px;
      border-radius: .2rem;
      color: #fff;
      background-color: #6c757d;
      border-color: #6c757d;
      cursor: pointer;
    }
    .sunzehuiBtn:hover{
       text-decoration: none;
       background-color: #5a6268;
       border-color: #545b62;
    }
    .sunzehuiBtn:focus {
      box-shadow: 0 0 0 0.2rem rgb(130 138 145 / 50%);
    }
    ul.fancytree-container{
      background-color:transparent !important;
      border:none !important;
    }
    .${this.config.tagClassname}{
      width: 20px;
      height: 20px;
      margin-right: auto;
      transform: translateY(-3px);
      margin-left: 20px;
      cursor: pointer;
    }
    `;
    document.body.appendChild(cssElem2);
  }

  async init() {
    this.insertCSS()
    this.renderTag();
  }
}


$(async function () {
  const quarkScript = new QuarkScript();
  await quarkScript.init();
});