夸克网盘批量工具集

批量重命名夸克网盘文件,获取文件列表

// ==UserScript==
// @name         夸克网盘批量工具集
// @namespace    http://tampermonkey.net/
// @version      1.3
// @description  批量重命名夸克网盘文件,获取文件列表
// @author       21zys
// @match        *://pan.quark.cn/*
// @grant        none
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    const cookie = document.cookie;

    // Step 1: 找到指定的div元素并在其内部添加按钮
    function addexecuteButton() {
        const targetDiv = document.querySelector('div.SectionHeaderController--section-header-right--QIJ-wNk');
        if (targetDiv) {
            const executeButton = document.createElement('button');
            executeButton.type = 'button';
            executeButton.className = 'ant-btn btn-file btn-create-folder';
            executeButton.style.marginRight = '12px;'
            executeButton.innerText = '打开21对话框';
            executeButton.textContent = '21对话框';
            executeButton.addEventListener('click', showRenameDialog);
            targetDiv.insertBefore(executeButton, targetDiv.firstChild);
        }
    }

    // Step 3: 显示对话框,包含文本框和重命名按钮
    function showRenameDialog() {
        // 创建对话框容器
        const dialog = document.createElement('div');
        dialog.className = 'batch-options-dialog';
        dialog.style.width = '500px';
        dialog.style.position = 'fixed';
        dialog.style.top = '50%';
        dialog.style.left = '50%';
        dialog.style.transform = 'translate(-50%, -50%)';
        dialog.style.backgroundColor = 'white';
        dialog.style.border = '1px solid #ccc';
        dialog.style.padding = '20px';
        dialog.style.zIndex = '10000';

        // 创建单选按钮组
        const radioGroup = document.createElement('div');
        const options = [
            { id: 'remove-number-prefix', label: '删除数字字符前缀' },
            { id: 'regex-remove', label: '正则删除' },
            { id: 'add-number-prefix', label: '增加数字前缀' },
            { id: 'remove-specific', label: '删除指定字符' },
            { id: 'get-file-list', label: '获取文件列表' },
            { id: 'batch-delete-file', label: '批量删除文件' },
        ];

        options.forEach(option => {
            const radioWrapper = document.createElement('div');
            const radio = document.createElement('input');
            radio.type = 'radio';
            radio.name = 'rename-option';
            radio.value = option.id;
            radio.id = option.id;
            radioWrapper.appendChild(radio);

            const label = document.createElement('label');
            label.htmlFor = option.id;
            label.textContent = option.label;
            radioWrapper.appendChild(label);

            radioGroup.appendChild(radioWrapper);
        });
        dialog.appendChild(radioGroup);

        // 创建文本框
        const textarea = document.createElement('textarea');
		textarea.className = 'ayi-textarea';
        textarea.style.width = '100%';
        textarea.style.height = '300px';
        textarea.placeholder = '请输入参数';
        textarea.style.marginBottom = '10px';
        dialog.appendChild(textarea);

        // 创建执行按钮
        const executeButton = document.createElement('button');
        executeButton.textContent = '执行';
        executeButton.style.display = 'inline-block';
        executeButton.style.width = '45%';
        executeButton.addEventListener('click', function() {
            const selectedOption = document.querySelector('input[name="rename-option"]:checked');
            if (selectedOption) {
				const cancelButton = document.querySelector('button.ayi-cancel-btn');
				cancelButton.disable = true;
                const actionType = selectedOption.value;
                const inputValue = textarea.value;
                if (actionType === 'remove-specific' && inputValue === '') {
                    alert('请输入需要删除的字符!!!')
                } else if (actionType === 'regex-remove' && inputValue === '') {
                    alert('请输入需要删除的正则表达式匹配内容!!!');
                } else if (actionType === 'add-number-prefix' && inputValue === '') {
                    alert('请输入分隔符!!!')
                } else if (actionType === 'get-file-list') {
					executeOption(actionType, inputValue);
				} else if (actionType === 'batch-delete-file') {
					executeOption(actionType, inputValue);
                    textarea.value = '';
					setLogToTextarea(textarea, "执行成功!!!");
				} else {
                    executeOption(actionType, inputValue.split('\n')[0]);
                    textarea.value = '';
                    setLogToTextarea(textarea, "执行成功!!!");
                }
				cancelButton.disable = false;
            } else {
                alert('请选择一个操作选项');
            }
        });
        dialog.appendChild(executeButton);

		// 创建取消按钮
		const cancelButton = document.createElement('button');
		cancelButton.className = 'ayi-cancel-btn';
		cancelButton.textContent = '关闭';
        cancelButton.style.display = 'inline-block';
        cancelButton.style.width = '45%';
		cancelButton.addEventListener('click', function() {
			document.body.removeChild(dialog);
        });
		dialog.appendChild(cancelButton);

        // 添加对话框到页面
        document.body.appendChild(dialog);
    }

    // 获取文件列表
    function fetchFileList(page) {
        // 获取当前父文件的 fid
        const url = window.location.href;
        const lastSegment = url.split('/').pop();
        let pdir_fid = lastSegment.split('-')[0];

        // 保存文件列表 fid, file_name
        let results = [];
        // 发送请求
        function fetchPage(page) {
            const xhr = new XMLHttpRequest();
            const url = `https://drive-pc.quark.cn/1/clouddrive/file/sort?pr=ucpro&fr=pc&uc_param_str=&pdir_fid=${pdir_fid}&_page=${page}&_size=500&_fetch_total=1&_fetch_sub_dirs=0&_sort=file_type:asc,file_name:asc`;

            xhr.open('GET', url, false);

            // 设置 withCredentials 为 true,允许跨域请求发送 cookie
            xhr.withCredentials = true;

            // 设置cookie请求头
            // xhr.setRequestHeader('Cookie', cookie);

            xhr.onreadystatechange = function () {
                if (xhr.readyState === 4 && xhr.status === 200) {
                    const response = JSON.parse(xhr.responseText);
                    const data = response.data;

                    // 如果data或list为空,则停止请求
                    if (!data || !data.list || data.list.length === 0) {
                        return;
                    }

                    // 将每个对象的 fid, file_name 追加到 results 数组中
                    data.list.forEach(item => {
                        results.push({'fid': item.fid, 'file_name':item.file_name});
                    });

                    fetchPage(page + 1)
                }
            };

            xhr.send();
        }

        fetchPage(page);
        return results;
    }

    function setLogToTextarea(textarea, log) {
        if (textarea) {
            textarea.value = log;
        }
	}

    function deleteFile(textarea, fid, oldFileName, index, length) {
        const url = `https://drive-pc.quark.cn/1/clouddrive/file/delete?pr=ucpro&fr=pc&uc_param_str=`;
        // 请求体
        const requestBody = {
            actoin_type: 2,
            exclude_fids: [],
            filelist: [fid]
        };

        // 发送请求
        const xhr = new XMLHttpRequest();
        xhr.open('POST', url, false);
        // 设置 withCredentials 为 true,允许跨域请求发送 cookie
        xhr.withCredentials = true;
        xhr.setRequestHeader('Content-Type', 'application/json;charset=UTF-8');
        // 设置cookie请求头
        // xhr.setRequestHeader('Cookie', cookie);

        xhr.onreadystatechange = function() {
            if (xhr.readyState === 4) { // 请求完成
                if (xhr.status === 200) { // 请求成功
                    console.log(`${index}/${length}:${oldFileName} 文件删除成功!!!`);
                } else if (xhr.status === 400){ // 请求失败
                    console.log(`${index}/${length}:${oldFileName} 文件删除失败!!!原因:${xhr.responseText}`)
                }
            }
        };
        setLogToTextarea(textarea, `${index}/${length}`);
        xhr.send(JSON.stringify(requestBody));
    }

    function renameFile(textarea, fid, oldFileName, newFileName, index, length) {
        const url = `https://drive-pc.quark.cn/1/clouddrive/file/rename?pr=ucpro&fr=pc&uc_param_str=`;
        // 请求体
        const requestBody = {
            fid: fid,
            file_name: newFileName
        };

        // 发送请求
        const xhr = new XMLHttpRequest();
        xhr.open('POST', url, false);
        // 设置 withCredentials 为 true,允许跨域请求发送 cookie
        xhr.withCredentials = true;
        xhr.setRequestHeader('Content-Type', 'application/json;charset=UTF-8');
        // 设置cookie请求头
        // xhr.setRequestHeader('Cookie', cookie);

        xhr.onreadystatechange = function() {
            if (xhr.readyState === 4) { // 请求完成
                if (xhr.status === 200) { // 请求成功
                    console.log(`${index}/${length}:文件 ${oldFileName} ——> ${newFileName} 成功!!!`);
                } else if (xhr.status === 400){ // 请求失败
                    let responseCode = JSON.parse(xhr.responseText).code;
                    console.log(responseCode);
                    if (responseCode === 23008) {
                        console.log(`${index}/${length}:${oldFileName} ——> ${newFileName} 重命名失败,已存在同名文件。正在执行删除操作...`)
                        deleteFile(textarea, fid, oldFileName, index, length);
                    } else {
                        console.log(`${index}/${length}:${oldFileName} ——> ${newFileName} 重命名失败,未知原因。${xhr.response.Text}`);
                    }
                }
            }
        };
        xhr.send(JSON.stringify(requestBody));
        setLogToTextarea(textarea, `${index}/${length}`);
    }

    function sleep(ms) {
        const start = Date.now();
        // 循环直到指定的毫秒数过去
        while (Date.now() - start < ms) {
            // 空循环持续到时间过去
        }
    }

    function formatNumber(number, length) {
        // 将数字转换为字符串
        const numberStr = number.toString();

        // 使用padStart来填充前导零
        return numberStr.padStart(length, '0');
    }

    function getNumberStringLength(number) {
        // 将数字转换为字符串
        const numberStr = number.toString();

        // 获取字符串的长度
        return numberStr.length;
    }

    // Step 4: 执行相应逻辑
    function executeOption(actionType, inputText) {
        const textarea = document.querySelector('textarea.ayi-textarea');
        function fetchAndRename() {
            let results = fetchFileList(1);
            let index = 0;
            let length = results.length;
            let lengthStringLength = getNumberStringLength(length);
            results.forEach(item => {
                index++;
                let fid = item['fid'];
                let file_name = item['file_name'];
                // 生成 3 到 5 秒之间的随机毫秒数
                const delay = Math.floor(Math.random() * (5000 - 2000 + 1)) + 2000;

                if (actionType === 'remove-number-prefix') {
                    // 删除数字字符前缀
                    const prefixPattern = '[【\\[\\((]?\\d+[】\\]\\))\\-_\\.::]+([\\u4e00-\\u9fa5a-zA-Z0-9()、。《》“”,:;?!-%—\\(\\)\\-\\.,:;\\?\\!]*)';
                    if (inputText !== '') {
                        prefixPattern = inputText
                    }
                    const prefixReg = new RegExp(prefixPattern);
                    if (prefixReg.test(file_name)) {
                        renameFile(null, fid, file_name, file_name.replace(prefixReg, '$1'), index, length);
                        sleep(delay);
                    }
                } else if (actionType === 'regex-remove') {
                    const regex = new RegExp(inputText, 'g');
                    if (regex.test(file_name)) {
                        renameFile(null, fid, file_name, file_name.replace(regex, ''), index, length);
                        sleep(delay);
                    }
                } else if (actionType === 'add-number-prefix') {
                    renameFile(null, fid, file_name, `${formatNumber(index, lengthStringLength)}${inputText}${file_name}`, index, length);
                    sleep(delay);
                } else if (actionType === 'remove-specific' && file_name.search(inputText) !== -1) {
                    // 删除特定字符
                    renameFile(null, fid, file_name, file_name.split(inputText).join(''), index, length);
                    sleep(delay)
                }
            });
        }
		function getFileList() {
			let results = fetchFileList(1);
			if (results.length > 0) {
                setLogToTextarea(textarea, results.map(item => item.file_name).join('\n'));
			}
		}
		function batchDeleteFile() {
			const fidList = inputText.split('\n');
			let index = 0;
			const length = fidList.length;
			fidList.forEach(item => {
				index++;
				// 生成 1 到 2 秒之间的随机毫秒数
                const delay = Math.floor(Math.random() * (2000 - 1000 + 1)) + 1000;
				deleteFile(textarea, item, '', index, length);
			});
		}
		if (actionType === 'remove-number-prefix' || actionType === 'regex-remove' || actionType === 'add-number-prefix' || actionType === 'remove-specific') {
			fetchAndRename();
		} else if (actionType === 'get-file-list') {
			getFileList();
		} else if (actionType === 'batch-delete-file') {
			batchDeleteFile();
		}
    }

    // 初始化脚本
    addexecuteButton();

})();