成人交流 · text · 0 分 · 评论 0

115脚本 移动视频 未完成品(求大佬)

本帖最後由 mi_too 於 2026-4-19 22:30 編輯

目前的功能是扫描指定文件夹下(包括子文件夹)的所有视频,移动到特定的文件里。
缺陷是只要是视频的后缀就移动,AI写的扫描判断视频大小功能一直失败

代码在下面,看看哪位大佬优化下




[code]// ==UserScript==
// @name         115视频批量移动助手 扫描识别修复版
// @namespace    http://tampermonkey.net/
// @Version      2.0
// @description  115网盘视频批量移动,修复扫描识别问题,全字段兼容+增强调试
// @author       You
// @match        *://*.115.com/*
// @match        *://115.com/*
// @grant        GM_addStyle
// @grant        GM_xmlhttpRequest
// @run-at       document-end
// @connect      webapi.115.com
// ==/UserScript==

(function() {
    'use strict';

    // ========== 核心配置 ==========
    // 全覆盖视频后缀白名单,包含所有常见+冷门视频格式
    const VIDEO_EXTENSIONS = [
        'mp4', 'mkv', 'avi', 'mov', 'wmv', 'flv', 'rmvb', 'm4v', '3gp', 'ts', 'webm', 'm2ts', 'iso',
        'mts', 'vob', 'mpg', 'mpeg', 'mpe', 'dat', 'm1v', 'm2v', 'm4p', 'mp2', 'm2p', 'divx', 'xvid',
        'ogm', 'ogv', 'asf', 'rm', 'ram', 'dvr-ms', 'mxf', 'roq', 'nsv', 'f4v', 'f4p', 'f4a', 'f4b'
    ];
    const BATCH_SIZE = 15;
    const REQUEST_DELAY = 800;
    let IS_INITED = false;

    // ========== 全局状态 ==========
    const state = {
        sourceCid: '',
        targetCid: '',
        videoFidList: [],
        isRunning: false
    };

    // ========== 样式注入 ==========
    GM_addStyle(`
        #simple-115-move-panel {
            position: fixed;
            top: 100px;
            right: 20px;
            width: 320px;
            background: #fff;
            border: 1px solid #ddd;
            border-radius: 8px;
            box-shadow: 0 4px 12px rgba(0,0,0,0.15);
            z-index: 999999;
            padding: 16px;
            font-family: sans-serif;
        }
        #simple-115-move-panel h3 {
            margin: 0 0 12px 0;
            font-size: 18px;
            text-align: center;
            color: #333;
        }
        #simple-115-move-panel .input-group {
            margin-bottom: 12px;
        }
        #simple-115-move-panel label {
            font-size: 13px;
            color: #666;
            margin-bottom: 4px;
            display: block;
        }
        #simple-115-move-panel input,
        #simple-115-move-panel button {
            width: 100%;
            padding: 10px;
            border-radius: 6px;
            border: 1px solid #ccc;
            font-size: 15px;
            box-sizing: border-box;
        }
        #simple-115-move-panel button {
            background: #00a1d6;
            color: white;
            border: none;
            cursor: pointer;
            margin-bottom: 8px;
            font-weight: 500;
        }
        #simple-115-move-panel button:hover { background: #008ebf; }
        #simple-115-move-panel button:disabled { background: #ccc; cursor: not-allowed; }
        #simple-115-move-panel .log-area {
            margin-top: 10px;
            padding: 10px;
            background: #f5f5f5;
            border-radius: 6px;
            font-size: 12px;
            color: #333;
            max-height: 240px;
            overflow-y: auto;
            line-height: 1.6;
            word-break: break-all;
        }
    `);

    // ========== 工具函数 ==========
    function log(msg, type = 'info') {
        const box = document.getElementById('simple-log-box');
        if (!box) return;
        const time = new Date().toLocaleTimeString();
        const prefix = type === 'error' ? '❌' : type === 'warn' ? '⚠️' : 'ℹ️';
        const logText = `[${time}] ${prefix} ${msg}`;
        box.innerHTML = logText + '<br>' + box.innerHTML;
        console.log('[115助手]', msg);
    }

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

    function updateButtons() {
        const scanBtn = document.getElementById('simple-btn-scan');
        const moveBtn = document.getElementById('simple-btn-move');
        scanBtn.disabled = !state.sourceCid || state.isRunning;
        moveBtn.disabled = !state.targetCid || state.videoFidList.length === 0 || state.isRunning;
    }

    // ========== 【修复】核心字段全兼容解析 ==========
    // 判断是否为文件夹
    function isFolderItem(item) {
        return !!(
            item.ifd === 1 ||
            item.is_dir === 1 ||
            item.isFolder === 1 ||
            item.folder === 1 ||
            item.type === 'folder' ||
            (item.sha === undefined && item.size === undefined && (item.cid || item.fcid))
        );
    }

    // 获取文件夹/文件ID
    function getItemId(item) {
        return item.cid || item.fid || item.id || item.file_id || item.fcid || item.folder_id;
    }

    // 【修复】全字段获取文件名,兼容所有返回格式
    function getItemName(item) {
        const name = item.n || item.name || item.file_name || item.title || item.filename || item.fileName || item.sha_name || item.file_n;
        return String(name || '').trim();
    }

    // 获取文件FID(用于移动)
    function getFileFid(item) {
        return item.fid || item.id || item.file_id || item.sha1 || item.sha || item.fcid;
    }

    // 【修复】增强视频识别逻辑
    function isVideoFile(fileName) {
        if (!fileName || fileName === '') {
            log('文件无名称,跳过', 'warn');
            return false;
        }
        // 提取后缀,兼容带特殊字符、多后缀的文件名
        const nameParts = fileName.split('.');
        if (nameParts.length < 2) return false;
        const ext = nameParts.pop()?.toLowerCase().trim();
        const isMatch = ext && VIDEO_EXTENSIONS.includes(ext);

        // 调试日志
        if (isMatch) {
            log(`识别到视频文件: ${fileName},后缀: ${ext}`);
        } else {
            console.log(`非视频文件: ${fileName},后缀: ${ext || '无'}`);
        }
        return isMatch;
    }

    // ========== 请求封装 ==========
    function requestGet(url) {
        return new Promise((resolve, reject) => {
            GM_xmlhttpRequest({
                method: 'GET',
                url: url,
                timeout: 20000,
                withCredentials: true,
                headers: {
                    'Accept': 'application/json, text/javascript, */*; q=0.01',
                    'Referer': 'https://115.com/',
                    'User-Agent': navigator.userAgent,
                    'X-Requested-With': 'XMLHttpRequest'
                },
                onload: function(res) {
                    try {
                        const result = JSON.parse(res.responseText);
                        console.log('===== 接口返回数据 =====', url, result);
                        resolve(result);
                    } catch (e) {
                        log('接口响应解析失败', 'error');
                        console.error('原始响应:', res.responseText);
                        reject(new Error('解析失败'));
                    }
                },
                onerror: () => {
                    log('接口请求失败', 'error');
                    reject(new Error('请求失败'));
                },
                ontimeout: () => {
                    log('接口请求超时', 'error');
                    reject(new Error('请求超时'));
                }
            });
        });
    }

    function requestPost(url, formData) {
        return new Promise((resolve, reject) => {
            GM_xmlhttpRequest({
                method: 'POST',
                url: url,
                timeout: 20000,
                withCredentials: true,
                data: formData,
                headers: {
                    'Accept': 'application/json, text/javascript, */*; q=0.01',
                    'Referer': 'https://115.com/',
                    'User-Agent': navigator.userAgent,
                    'X-Requested-With': 'XMLHttpRequest'
                },
                onload: function(res) {
                    try {
                        const result = JSON.parse(res.responseText);
                        resolve(result);
                    } catch (e) {
                        log('移动接口解析失败', 'error');
                        reject(new Error('解析失败'));
                    }
                },
                onerror: () => {
                    log('移动请求失败', 'error');
                    reject(new Error('请求失败'));
                },
                ontimeout: () => {
                    log('移动请求超时', 'error');
                    reject(new Error('请求超时'));
                }
            });
        });
    }

    // ========== API封装 ==========
    async function fetchFileList(cid) {
        try {
            // 补全接口参数,确保返回完整文件信息
            const url = `https://webapi.115.com/files?cid=${cid}&limit=10000&show_dir=1&o=user_ptime&asc=0&natsort=1&format=json&fc_mix=0`;
            return await requestGet(url);
        } catch (e) {
            log(`获取文件夹[${cid}]文件列表失败: ${e.message}`, 'error');
            return null;
        }
    }

    async function batchMoveFiles(fidList, targetCid) {
        try {
            const url = 'https://webapi.115.com/files/move';
            const formData = new FormData();
            formData.append('pid', targetCid);
            // 修复批量移动参数,使用数组格式
            fidList.forEach(fid => formData.append('fid[]', fid));
            return await requestPost(url, formData);
        } catch (e) {
            log(`移动失败: ${e.message}`, 'error');
            return null;
        }
    }

    // ========== 【修复】增强递归扫描逻辑 ==========
    async function scanVideoRecursive(cid, folderName = '未知文件夹') {
        const fileData = await fetchFileList(cid);
        if (!fileData || !fileData.state) {
            log(`文件夹[${folderName}]扫描失败,接口无有效返回`, 'error');
            return;
        }

        // 兼容所有数据结构
        const itemList = fileData.data || fileData.items || fileData.list || [];
        log(`扫描文件夹: ${folderName},共${itemList.length}个项目`);

        // 遍历所有项目
        for (const item of itemList) {
            const isFolder = isFolderItem(item);
            const itemId = getItemId(item);
            const itemName = getItemName(item);

            // 【修复】子文件夹递归扫描
            if (isFolder && itemId) {
                log(`进入子文件夹: ${itemName} (CID: ${itemId})`);
                await scanVideoRecursive(itemId, itemName);
                // 子文件夹扫描后加延迟,避免接口风控
                await sleep(REQUEST_DELAY);
            }
            // 视频文件识别
            else {
                const fileName = getItemName(item);
                const fileFid = getFileFid(item);

                // 校验文件有效性
                if (!fileName || !fileFid) {
                    log(`跳过无效文件: 无名称/无ID`, 'warn');
                    console.log('无效文件详情:', item);
                    continue;
                }

                // 判断是否为视频
                if (isVideoFile(fileName)) {
                    state.videoFidList.push(fileFid);
                    // 每5个输出一次进度
                    if (state.videoFidList.length % 5 === 0) {
                        log(`已累计找到${state.videoFidList.length}个视频文件`);
                    }
                }
            }
        }
    }

    // ========== 按钮事件处理 ==========
    async function handleScan() {
        if (!state.sourceCid || state.isRunning) return;
        state.isRunning = true;
        state.videoFidList = [];
        updateButtons();
        log('====================');
        log('开始扫描视频...');

        try {
            await scanVideoRecursive(state.sourceCid, `源文件夹[${state.sourceCid}]`);
            log('====================');
            log(`扫描完成!共找到${state.videoFidList.length}个视频文件`, state.videoFidList.length > 0 ? 'success' : 'warn');
        } catch (e) {
            log(`扫描出错: ${e.message}`, 'error');
            console.error('扫描异常详情:', e);
        } finally {
            state.isRunning = false;
            updateButtons();
        }
    }

    async function handleMove() {
        if (!state.targetCid || state.videoFidList.length === 0 || state.isRunning) return;
        state.isRunning = true;
        updateButtons();
        log('====================');
        log(`开始批量移动${state.videoFidList.length}个视频...`);

        let successCount = 0;
        let failCount = 0;

        // 分批移动,降低风控
        for (let i = 0; i < state.videoFidList.length; i += BATCH_SIZE) {
            const batch = state.videoFidList.slice(i, i + BATCH_SIZE);
            log(`正在移动第${i+1}-${Math.min(i+BATCH_SIZE, state.videoFidList.length)}个文件`);
            const result = await batchMoveFiles(batch, state.targetCid);

            if (result && result.state) {
                successCount += batch.length;
                log(`✅ 批次移动成功,累计成功: ${successCount}/${state.videoFidList.length}`);
            } else {
                failCount += batch.length;
                const errorMsg = result?.error || result?.msg || '未知错误';
                log(`❌ 批次移动失败: ${errorMsg}`, 'error');
            }

            // 批次间隔
            await sleep(REQUEST_DELAY);
        }

        log('====================');
        log(`移动任务全部完成!成功: ${successCount} 个,失败: ${failCount} 个`, successCount > 0 ? 'success' : 'error');

        // 重置状态
        state.videoFidList = [];
        state.isRunning = false;
        updateButtons();
    }

    // ========== UI初始化 ==========
    function initPanel() {
        if (IS_INITED || document.getElementById('simple-115-move-panel')) return;
        IS_INITED = true;

        const panelHTML = `
            <h3>


评论 (0)

登录 后参与讨论。

还没有评论,来抢沙发吧。