- // ==UserScript==
- // @name 阿里云盘、夸克网盘树状目录
- // @version 1.0
- // @description 分享页显示树状列表,点击logo旁边笑脸即可
- // @author sunzehui
- // @license MIT
- // @match https://www.alipan.com/s/*
- // @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 https://github.com/sunzehui/alipan_treefolder
- // ==/UserScript==
-
- // 等待dom加载
- ;(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
- })()
-
- class AliPanTree {
- constructor() {
- this.tokenStorage = JSON.parse(localStorage.getItem('shareToken'))
- this.token = this.tokenStorage.share_token ? this.tokenStorage.share_token : ''
- this.device_id = this.parseCookie(document.cookie)['cna']
- this.share_id = this.getShareId()
- 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',
- 'x-device-id': this.device_id,
- 'x-share-token': this.token,
- }
- 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 = {
- fileList: 'https://api.aliyundrive.com/adrive/v3/file/list',
- }
- this.params = {}
- this.isLoading = false
- this.nowSelectNode = null
- }
-
- parseCookie(str) {
- return str.split(';').map(v => v.split('=')).reduce((acc, v) => {
- acc[decodeURIComponent(v[0].trim())] = decodeURIComponent(v[1].trim())
- return acc
- }, {})
- }
- getShareId() {
- const url = location.pathname
- return url.match(/(?<=\/s\/)(\w+)(?=\/folder)?/g)[0]
- }
- 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('😃')
- }
- }
-
- renderTag() {
- const tag = document.createElement('div')
- tag.classList.add(this.config.tagClassname)
- tag.innerHTML = '😃'
-
- let that = this
- $(document).on('click', '.' + this.config.tagClassname, function() {
- that.handleTagClick()
- })
-
- domReady('div.banner--7Ux0y', function() {
- document.querySelector('#root > div > div.page--W3d1U > .banner--7Ux0y').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) {
- that.nowSelectNode = data.node
- },
- }
-
- const loadRootNode = async (event, data) => {
- const list = await that.getList({ parent_file_id: 'root' })
- const children = await Promise.all(
- list.items.map(async pItem => {
- const cList = await that.getList({ parent_file_id: pItem.file_id })
- return cList.items.map(cItem => {
- return {
- title: cItem.name,
- folder: cItem.type === 'folder',
- key: cItem.file_id,
- lazy: true,
- }
- })
- })
- )
- return list.items.map(item => ({
- title: item.name,
- folder: item.type === 'folder',
- key: item.file_id,
- 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.items.map(item => ({
- title: item.name,
- folder: item.type === 'folder',
- key: item.file_id,
- lazy: item.type === 'folder',
- }))
- })
- }
- 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 handleTagClick() {
- console.log('clicked')
- const $existsView = $('.tree-container')
- if ($existsView.length > 0) {
- return $existsView.show()
- }
- this.loading()
- await this.renderView()
- this.loading('stop')
- }
- // 显示侧边栏
- 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() {
- let link = null
- const nowSelectNode = that.nowSelectNode
- if (nowSelectNode && nowSelectNode.folder) {
- link = `/s/${that.getShareId()}/folder/${nowSelectNode.key}`
- }
- if (link == null) alert('请选择文件夹')
- window.open(link, '_blank')
- })
-
- $(document).on('click', '.tree-container .bar .close-btn', function() {
- $('.tree-container').hide()
- })
-
- domReady('div.content--t4XI8', function() {
- $('#root > div > div.page--W3d1U').append($treeContainer)
- })
- }
-
- // 获取文件列表
- async getList({ parent_file_id }) {
- const result = await fetch(this.api.fileList, {
- headers: this.headers,
- referrerPolicy: 'origin',
- body: JSON.stringify({
- share_id: this.share_id,
- parent_file_id: parent_file_id || 'root',
- limit: 100,
- image_thumbnail_process: 'image/resize,w_160/format,jpeg',
- image_url_process: 'image/resize,w_1920/format,jpeg',
- video_thumbnail_process: 'video/snapshot,t_1000,f_jpg,ar_auto,w_300',
- order_by: 'name',
- order_direction: 'DESC',
- }),
- method: 'POST',
- mode: 'cors',
- credentials: 'omit',
- })
- return await result.json()
- }
-
- async buildTree(parent_file_id) {
- const treeNode = {}
- const root = await this.getList({ parent_file_id })
- treeNode.children = []
- for (let i = 0; i < root.items.length; i++) {
- let node = void 0
- if (root.items[i].type === 'folder') {
- node = await this.buildTree(root.items[i].file_id)
- node.name = root.items[i].name
- } else {
- node = root.items[i]
- }
- 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;
- }
- .close-btn{
- background: transparent;
- border: none;
- }
- .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()
- }
- }
-
- class QuarkPanTree {
- constructor() {
- this.api = {
- fileList: 'https://drive-pc.quark.cn/1/clouddrive/share/sharepage/detail',
- }
- 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.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('😃')
- }
- }
- async handleTagClick() {
- 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 = '😃'
-
- 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) {
- 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 thisHost = window.location.host
- switch (thisHost) {
- case 'www.alipan.com':
- const aliScript = new AliPanTree()
- aliScript.init()
- break
- case 'pan.quark.cn':
- const quarkScript = new QuarkPanTree()
- quarkScript.init()
- break
- default:
- console.error('暂不支持该网站')
- break
- }
- })