Greasy Fork is available in English.

百度网盘导出数据

导出百度网盘中的所有文件/文件夹的数据,方便进行二级的检索和分析

Fra 02.01.2019. Se den seneste versjonen.

// ==UserScript==
// @name         百度网盘导出数据
// @namespace    http://tampermonkey.net/
// @version      1.1.1
// @description  导出百度网盘中的所有文件/文件夹的数据,方便进行二级的检索和分析
// @author       rxliuli
// @match        https://pan.baidu.com/disk/home*
// @license      MIT
// @note         v1.1.1 添加了错误输出,方便排错
// @note         v1.1.0 优化加载时机,导出数据的格式追加 `origin` 属性,精简了代码
// @note         v1.0.0 实现了基本的导出功能
// ==/UserScript==

;(function() {
  'use strict'
  /**
   * 轮询等待指定资源加载完毕再执行操作
   * 使用 Promises 实现,可以使用 ES7 的 {@async}/{@await} 调用
   * @param {Function} resourceFn 判断必须的资源是否存在的方法
   * @param {Object} options 选项
   * @returns Promise 对象
   */
  function waitResource(resourceFn, options) {
    var optionsRes = Object.assign(
      {
        interval: 1000,
        max: 10
      },
      options
    )
    var current = 0
    return new Promise((resolve, reject) => {
      var timer = setInterval(() => {
        if (resourceFn()) {
          clearInterval(timer)
          resolve()
        }
        current++
        if (current >= optionsRes.max) {
          clearInterval(timer)
          reject('等待超时')
        }
      }, optionsRes.interval)
    })
  }

  /**
   * 将数组异步压平一层
   * @param {Array} arr 数组
   * @param {Function} fn 映射方法
   */
  async function asyncFlatMap(arr, fn) {
    var res = []
    for (const i in arr) {
      res.push(...(await fn(arr[i])))
    }
    return res
  }

  /**
   * 文件数据信息类
   */
  class File {
    /**
     * 构造函数
     * @param {String} path 全路径
     * @param {String} parent 父级路径
     * @param {String} name 文件名
     * @param {String} size 大小(b)
     * @param {String} isdir 是否为文件
     * @param {String} {origin} 百度云文件信息的源对象
     */
    constructor(path, parent, name, size, isdir, origin) {
      this.path = path
      this.parent = parent
      this.name = name
      this.size = size
      this.isdir = isdir
      this.orgin = origin
    }
  }

  /**
   * 文件列表
   * @param {String} path 路径
   */
  async function list(path) {
    async function asyncList(path) {
      var baseUrl = 'https://pan.baidu.com/api/list?'
      try {
        var res = await fetch(`${baseUrl}dir=${encodeURIComponent(path)}`)
        var json = await res.json()
        return json.list
      } catch (err) {
        console.log(`获取文件夹 ${path} 列表发生了错误:`, err)
        return []
      }
    }

    async function children(path) {
      var fileList = await asyncList(path)
      return asyncFlatMap(fileList, async file => {
        var res = new File(
          file.path,
          path,
          file.server_filename,
          file.size,
          file.isdir,
          file
        )
        if (res.isdir !== 1) {
          return [res]
        }
        return [res].concat(await children(res.path))
      })
    }

    /**
     * 获取当前文件夹信息
     * @param {String} path 文件夹路径
     */
    function currentDir(path) {
      var lastIndex = path.lastIndexOf('/')
      return lastIndex <= 0 && !path.substr(lastIndex + 1)
        ? new File('/', '', '/', 0, 1)
        : new File(
            path,
            path.substr(0, lastIndex) || '/',
            path.substr(lastIndex + 1),
            0,
            1
          )
    }
    return [currentDir(path)].concat(await children(path))
  }

  // 统计大小
  function countTreesize(files) {
    // 按照 parent 分组
    var fileMap = files.reduce((res, file) => {
      if (!res[file.parent]) {
        res[file.parent] = []
      }
      res[file.parent].push(file)
      return res
    }, {})

    // 递归计算文件夹大小
    function castDirSize(dir) {
      var temp = fileMap[dir.path]
      return !temp
        ? dir.size
        : temp
            .map(file => {
              if (file.isdir !== 1) {
                return file.size
              } else {
                return castDirSize(file)
              }
            })
            .reduce((a, b) => a + b)
    }

    return files.map(file => {
      file.size = castDirSize(file)
      return file
    })
  }

  /**
   * 将对象转换为 json 并下载
   * @param {Object} obj 要下载的对象数据
   * @param {String} name 文件名
   */
  function downloadJson(obj, name = 'unknown.json') {
    var blob = new Blob([JSON.stringify(obj, null, 2)], {
      type: 'application/json'
    })
    // 创建隐藏的可下载链接
    var eleLink = document.createElement('a')
    eleLink.download = name
    eleLink.style.display = 'none'
    // 字符内容转变成blob地址
    eleLink.href = URL.createObjectURL(blob)
    // 触发点击
    document.body.appendChild(eleLink)
    eleLink.click()
    // 然后移除
    document.body.removeChild(eleLink)
  }

  /**
   * 下载百度网盘的数据
   */
  async function download(path) {
    var files = await list(path)
    var countFiles = countTreesize(files)
    downloadJson(countFiles, 'dataset.json')
  }

  // 追加 [导出数据] 按钮到 [新建文件夹] 后面
  function addExportDataBtn() {
    var exportBtn = document.createElement('span')
    exportBtn.innerHTML = `
    <a id="exportDataId" class="g-button" title="导出数据" href="#">
      <span class="g-button-right">
        <span class="text">导出数据</span>
      </span>
    </a>
    `
    var mkdirBtn = document.querySelector('div.tcuLAu > a:nth-child(3)')
    mkdirBtn.after(exportBtn)
    document
      .querySelector('#exportDataId')
      .addEventListener('click', function() {
        download('/')
      })

    console.log(
      '%c 百度网盘导出数据脚本加载成功!!!',
      'background:#aaa;color:#bada55; font-size: 30px;'
    )
  }

  /**
   * 初始化,等待【新建文件夹】按钮加载完毕再添加按钮
   */
  async function init() {
    // 必须等待按钮加载完毕
    await waitResource(
      () => document.querySelector('div.tcuLAu > a:nth-child(3)'),
      {
        interval: 1000,
        max: 100
      }
    )
    addExportDataBtn()
  }

  init()
})()