目前的功能是扫描指定文件夹下(包括子文件夹)的所有视频,移动到特定的文件里。
缺陷是只要是视频的后缀就移动,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>

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