您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
检查115网盘中"全xx集"文件夹的完整性,用绿色圆点标注完整的,红色圆点标注不完整的。支持点击单个绿标或批量重命名为"影视名称 (年份)"格式
// ==UserScript== // @name 115文件完整性批量检查重命名 // @namespace http://tampermonkey.net/ // @version 2.2 // @description 检查115网盘中"全xx集"文件夹的完整性,用绿色圆点标注完整的,红色圆点标注不完整的。支持点击单个绿标或批量重命名为"影视名称 (年份)"格式 // @author zscc // @match https://115.com/* // @match https://*.115.com/* // @grant none // @updateURL // @downloadURL // @supportURL // @homepageURL // @license MIT // ==/UserScript== /* * 115网盘文件完整性检查器增强版 * * 功能说明: * 1. 自动检测"全xx集"格式的文件夹完整性 * 2. 用绿色圆点标注完整的文件夹,红色圆点标注不完整的 * 3. 点击绿色圆点可一键重命名为"影视名称 (年份)"格式 * 4. 支持批量重命名所有绿标(完整)文件 * 5. 智能提取影视剧名称和年份信息 * 6. 支持多种115网盘页面布局 * 7. 智能文件数量检测,支持多种115网盘布局 * 8. 可配置无法确定文件数量时的处理方式(显示为红色/隐藏标注) * 9. 严格模式:启用更多文件数量检测方法 * 10. 优化API调用:参考115pan_aria2脚本的文件获取逻辑 * 11. 智能文件夹ID提取:支持多种115网盘页面布局和属性 * 12. 纯API检测:仅使用官方API获取准确的文件数量 * * 重命名规则: * • 优先从第一个"】"和"["符号之间提取影视名称 * • 示例:"【高清剧集网发布 www.PTHDTV.com】成家[全36集]..." → "成家 (2025)" * • 自动提取年份信息(2000-2039年范围) * • 如果没有年份,则只使用影视名称 * * 使用方法: * 1. 安装脚本后访问115网盘 * 2. 脚本会自动在文件夹图标上添加彩色圆点 * 3. 绿色圆点表示文件完整,可点击进行单个重命名 * 4. 红色圆点表示文件不完整 * 5. 点击页面右上角的"批量重命名绿标文件"按钮进行批量操作 * 6. 重命名前会显示提取信息供确认 * * 支持的文件名格式: * • 【发布组】影视名称[集数信息]技术参数 * • 影视名称.技术参数.年份.格式信息 * • 其他包含】和[符号的格式 * * 自定义配置: * 可以修改下方CONFIG对象中的设置来自定义脚本行为 * * 新增配置选项: * • STRICT_FILE_COUNT_CHECK: 启用严格的文件数量检查模式 * • SHOW_UNKNOWN_AS_INCOMPLETE: 无法确定文件数量时显示为不完整(红色) * • HIDE_UNKNOWN_FILES: 隐藏无法确定文件数量的文件(不显示任何标注) * * 问题修复: * • 修复了绿标文件误判问题:当无法获取实际文件数量时,不再错误地使用文件名中的集数 * • 改进了115网盘文件数量检测算法,支持更多页面布局 * • 增加了多种文件数量提取方法,提高检测准确性 * * 最新优化(v2.4): * • 修复API调用问题:使用GM.xmlHttpRequest替代fetch,解决CORS跨域限制 * • 参考115pan_aria2脚本的API调用方法,使用官方API获取准确的文件数量 * • 智能文件夹ID提取:支持从元素属性、父元素、链接URL、onclick事件等多种方式获取 * • API调用优化:使用正确的请求头、认证信息和错误处理机制 * • 文件类型过滤:准确区分文件和文件夹,只统计实际文件数量 * • 纯API检测:移除所有DOM解析备用方法,确保数据准确性 * • 增强错误处理:针对权限错误、网络错误等不同情况采用不同的重试策略 */ (function() { 'use strict'; // 脚本配置 - 用户可以根据需要修改这些设置 const CONFIG = { // 调试模式:开启后会在控制台输出详细的调试信息 // 如果遇到问题,建议开启此选项以便排查 DEBUG_MODE: true, // 自动处理延迟(毫秒):页面加载后等待多久开始处理文件 // 如果网络较慢,可以适当增加这个值 PROCESS_DELAY: 3000, // 页面变化检测延迟(毫秒):检测到页面变化后等待多久重新处理 // 避免频繁触发,提高性能 MUTATION_DELAY: 1000, // 重命名功能开关:是否启用点击绿色圆点进行重命名的功能 // 如果只想要完整性检查功能,可以设置为false ENABLE_RENAME: true, // 文件数量检测超时(毫秒):获取文件数量的最大等待时间 FILE_COUNT_TIMEOUT: 5000, // 圆点样式配置 DOT_SIZE: '12px', // 圆点大小 DOT_BORDER_RADIUS: '50%', // 圆点圆角 DOT_OPACITY: '0.8', // 圆点透明度 // 颜色配置 COMPLETE_COLOR: '#4CAF50', // 完整文件的圆点颜色(绿色) INCOMPLETE_COLOR: '#F44336', // 不完整文件的圆点颜色(红色) // 重命名配置 AUTO_REFRESH_AFTER_RENAME: true, // 重命名成功后是否自动刷新页面 SHOW_RENAME_CONFIRMATION: true, // 是否显示重命名确认对话框 COPY_TO_CLIPBOARD_ON_ERROR: true, // 重命名失败时是否复制建议名称到剪贴板 // 批量重命名配置 ENABLE_BATCH_RENAME: true, // 是否启用批量重命名功能 BATCH_RENAME_DELAY: 1000, // 批量重命名时每个文件之间的延迟(毫秒) BATCH_SHOW_PROGRESS: true, // 是否显示批量重命名进度 BATCH_CONFIRM_BEFORE_START: true, // 批量重命名前是否显示确认对话框 BATCH_STOP_ON_ERROR: false, // 遇到错误时是否停止批量操作 // 文件数量检测配置 STRICT_FILE_COUNT_CHECK: true, // 是否启用严格的文件数量检查 SHOW_UNKNOWN_AS_INCOMPLETE: true, // 无法确定文件数量时是否显示为不完整(红色) HIDE_UNKNOWN_FILES: false // 是否隐藏无法确定文件数量的文件(不显示任何标注) }; // 调试日志函数 function debugLog(...args) { if (CONFIG.DEBUG_MODE) { console.log('[115文件检查器]', ...args); } } debugLog('115网盘文件完整性检查器增强版已加载'); debugLog('配置:', CONFIG); // 测试提取函数(仅在调试模式下运行) if (CONFIG.DEBUG_MODE) { // 测试用户提供的示例 const testFileName = '【高清剧集网发布 www.PTHDTV.com】成家[全36集][国语配音+中文字幕].Home.About.Us.S01.2025.2160p.WEB-DL.DDP2.0.H265-ZeroTV星标'; const testResult = extractTitleAndYear(testFileName); debugLog('测试提取结果:', testResult); debugLog('期望结果: {title: "成家", year: "2025"}'); // 测试其他常见格式 const testCases = [ '【字幕组】电影名称[1080p][中文字幕].Movie.Name.2023.1080p.BluRay.x264', '影视剧名称.TV.Show.S01.2024.WEB-DL.1080p.H264', '】测试影片[全24集][国语].Test.Movie.2022.720p.WEB-DL' ]; testCases.forEach((testCase, index) => { const result = extractTitleAndYear(testCase); debugLog(`测试案例${index + 1}: "${testCase}" => `, result); }); } // 等待页面加载完成 function waitForElement(selector, timeout = 10000) { return new Promise((resolve, reject) => { const startTime = Date.now(); const checkElement = () => { const element = document.querySelector(selector); if (element) { resolve(element); } else if (Date.now() - startTime > timeout) { reject(new Error(`Element ${selector} not found within ${timeout}ms`)); } else { setTimeout(checkElement, 100); } }; checkElement(); }); } // 创建标注圆点 function createDot(color, count, episodeNumber, fileName, fileElement) { const dot = document.createElement('span'); dot.style.cssText = ` display: inline-block; width: ${CONFIG.DOT_SIZE}; height: ${CONFIG.DOT_SIZE}; border-radius: ${CONFIG.DOT_BORDER_RADIUS}; background-color: ${color}; position: absolute; top: 50%; left: 0; transform: translateY(-50%); border: 2px solid white; box-shadow: 0 2px 6px rgba(0,0,0,0.4); z-index: 1000; cursor: pointer; opacity: ${CONFIG.DOT_OPACITY}; transition: all 0.2s ease; `; // 添加悬停效果 dot.addEventListener('mouseenter', function() { this.style.opacity = '1'; this.style.transform = 'translateY(-50%) scale(1.1)'; }); dot.addEventListener('mouseleave', function() { this.style.opacity = CONFIG.DOT_OPACITY; this.style.transform = 'translateY(-50%) scale(1)'; }); // 添加文件数量提示 if (count !== undefined && episodeNumber !== undefined) { const status = count >= episodeNumber ? '完整' : '不完整'; let tooltip = `文件${status}: ${count}/${episodeNumber}集`; if (color === CONFIG.COMPLETE_COLOR && CONFIG.ENABLE_RENAME) { tooltip += '\n\n点击进行一键重命名'; } dot.title = tooltip; } else if (count !== undefined) { dot.title = `文件数量: ${count}`; } // 如果是绿色圆点(完整)且启用了重命名功能,添加点击事件进行重命名 if (color === CONFIG.COMPLETE_COLOR && CONFIG.ENABLE_RENAME) { dot.addEventListener('click', function(e) { e.preventDefault(); e.stopPropagation(); renameFolder(fileName, fileElement); }); // 添加特殊样式表示可点击 dot.style.cursor = 'pointer'; dot.style.boxShadow = '0 2px 6px rgba(76, 175, 80, 0.4)'; } return dot; } // 提取"全xx集"中的数字 function extractEpisodeNumber(text) { const match = text.match(/全(\d+)集/); return match ? parseInt(match[1]) : null; } // 从文件名中提取清晰度信息 function extractQuality(fileName) { debugLog('开始提取清晰度信息:', fileName); // 清晰度匹配模式,按优先级排序 const qualityPatterns = [ /\b(2160p|4K)\b/i, /\b(1080p)\b/i, /\b(720p)\b/i, /\b(480p)\b/i, /\b(UHD)\b/i, /\b(HDR)\b/i ]; for (const pattern of qualityPatterns) { const match = fileName.match(pattern); if (match) { const quality = match[1].toLowerCase(); debugLog('提取到清晰度:', quality); return quality === '4k' ? '2160p' : quality; // 统一4K为2160p } } debugLog('未找到清晰度信息'); return null; } // 从复杂文件名中提取影视剧名称和年份 function extractTitleAndYear(fileName) { debugLog('开始提取影视剧名称和年份:', fileName); let title = ''; let year = null; // 新的提取规则:从第一个"】"和"["符号之间提取影视名称 // 示例:"【高清剧集网发布 www.PTHDTV.com】成家[全36集][国语配音+中文字幕]..." // 提取:"成家" const titleMatch = fileName.match(/】([^\[]+)\[/); if (titleMatch && titleMatch[1]) { title = titleMatch[1].trim(); debugLog('从】和[之间提取到标题:', title); } else { // 备用方案:如果没有找到】和[的组合,尝试其他方式 debugLog('未找到】和[的组合,尝试备用提取方案'); // 方案1:移除【】内容后,取第一个[前的内容 let cleanName = fileName.replace(/【[^】]*】/g, '').trim(); const beforeBracket = cleanName.match(/^([^\[]+)/); if (beforeBracket && beforeBracket[1]) { title = beforeBracket[1].trim(); debugLog('备用方案1提取到标题:', title); } else { // 方案2:传统清理方式 title = fileName .replace(/【[^】]*】/g, '') // 移除【】内容 .replace(/\[[^\]]*\]/g, '') // 移除[]内容 .replace(/\([^)]*\)/g, '') // 移除()内容 .replace(/\{[^}]*\}/g, '') // 移除{}内容 .replace(/www\.[^\s]+/gi, '') // 移除网址 .replace(/[\u4e00-\u9fff]+网/g, '') // 移除中文网站名 .replace(/发布|字幕组|压制|制作/g, '') .replace(/\b(1080p|720p|480p|2160p|4K|UHD|HDR|WEB-DL|BluRay|BDRip|DVDRip|HDTV|WEBRip)\b/gi, '') .replace(/\b(x264|x265|H264|H265|HEVC|AVC|AAC|AC3|DTS|DD5\.1|DDP2\.0|DDP5\.1)\b/gi, '') .replace(/\b(mkv|mp4|avi|rmvb|flv|wmv|mov)\b/gi, '') .replace(/\b(S\d{2}|Season\s*\d+|第[一二三四五六七八九十\d]+季)\b/gi, '') .replace(/\b(E\d{2}|Episode\s*\d+|第[一二三四五六七八九十\d]+集)\b/gi, '') .replace(/\b(全\d+集|共\d+集|\d+集全)\b/gi, '') .replace(/[\-_\.]+/g, ' ') // 替换连字符、下划线、点为空格 .replace(/\s+/g, ' ') // 合并多个空格 .trim(); debugLog('备用方案2提取到标题:', title); } } // 进一步清理标题中的技术参数 title = title .replace(/\b(1080p|720p|480p|2160p|4K|UHD|HDR|WEB-DL|BluRay|BDRip|DVDRip|HDTV|WEBRip)\b/gi, '') .replace(/\b(x264|x265|H264|H265|HEVC|AVC|AAC|AC3|DTS|DD5\.1|DDP2\.0|DDP5\.1)\b/gi, '') .replace(/\b(S\d{2}|Season\s*\d+)\b/gi, '') .replace(/[\-_\.]+/g, ' ') .replace(/\s+/g, ' ') .trim(); // 提取年份 (四位数字,通常在2000-2030之间) const yearMatch = fileName.match(/(20[0-3]\d)/g); if (yearMatch && yearMatch.length > 0) { // 如果有多个年份,选择最后一个(通常是发布年份) year = yearMatch[yearMatch.length - 1]; } // 从标题中移除年份(如果存在) if (year) { title = title.replace(new RegExp(year, 'g'), '').trim(); } // 最终清理标题 title = title .replace(/[\s\-_\.]+/g, ' ') .replace(/\s+/g, ' ') .trim(); debugLog('最终提取的标题:', title, '年份:', year); return { title: title || '未知影视剧', year: year }; } // 检查当前页面是否存在重复的文件名 function checkDuplicateName(proposedName, currentElement) { debugLog('检查重复文件名:', proposedName); // 获取当前页面所有文件/文件夹元素 const allFileElements = document.querySelectorAll('tr[nid], tr[data-id], .file-item, .list-item'); for (const element of allFileElements) { // 跳过当前正在重命名的元素 if (element === currentElement) continue; // 获取元素的文件名 let existingName = ''; if (element.title) { existingName = element.title.trim(); } else { const nameCell = element.querySelector('td:nth-child(2), .file-name, .name'); if (nameCell) { existingName = nameCell.textContent.trim(); } } // 比较文件名(忽略大小写) if (existingName.toLowerCase() === proposedName.toLowerCase()) { debugLog('发现重复文件名:', existingName); return true; } } debugLog('未发现重复文件名'); return false; } // 重命名文件夹 function renameFolder(fileName, fileElement) { debugLog('开始重命名文件夹:', fileName); debugLog('文件元素:', fileElement); // 提取影视剧名称和年份 const extracted = extractTitleAndYear(fileName); if (!extracted.title) { console.log('无法提取有效的影视剧名称'); alert('无法从文件名中提取有效的影视剧名称\n\n请确保文件名包含有效的影视剧信息'); return; } // 验证提取的标题是否有效 if (!extracted.title || extracted.title === '未知影视剧' || extracted.title.length < 2) { alert(`无法从文件名中提取有效的影视剧名称\n\n原文件名: "${fileName}"\n提取结果: "${extracted.title}"\n\n请检查文件名格式是否正确,或手动重命名。`); return; } // 构建基础新文件名:"影视名称 (年份)" let baseName; if (extracted.year) { baseName = `${extracted.title} (${extracted.year})`; } else { // 如果没有年份,只使用影视名称 baseName = extracted.title; } let newName = baseName; // 检查是否存在重复名称 const isDuplicate = checkDuplicateName(newName, fileElement.closest('tr') || fileElement); if (isDuplicate) { // 如果存在重复,尝试添加清晰度信息 const quality = extractQuality(fileName); if (quality) { newName = `${baseName} ${quality}`; debugLog('检测到重复名称,添加清晰度后缀:', newName); // 再次检查添加清晰度后是否还有重复 const stillDuplicate = checkDuplicateName(newName, fileElement.closest('tr') || fileElement); if (stillDuplicate) { // 如果还是重复,添加时间戳 const timestamp = new Date().getTime().toString().slice(-4); newName = `${baseName} ${quality} (${timestamp})`; debugLog('添加清晰度后仍重复,添加时间戳:', newName); } } else { // 如果没有清晰度信息,直接添加时间戳 const timestamp = new Date().getTime().toString().slice(-4); newName = `${baseName} (${timestamp})`; debugLog('未找到清晰度信息,添加时间戳:', newName); } } debugLog('最终的新文件名:', newName); // 弹出确认对话框(如果启用) let userConfirmed = true; if (CONFIG.SHOW_RENAME_CONFIRMATION) { let confirmMessage = `是否将文件夹重命名为:\n\n"${newName}"\n\n原文件名:\n"${fileName}"\n\n提取信息:\n• 影视名称: ${extracted.title}\n• 年份: ${extracted.year || '未检测到'}`; const quality = extractQuality(fileName); if (quality) { confirmMessage += `\n• 清晰度: ${quality}`; } if (isDuplicate) { confirmMessage += `\n\n⚠️ 检测到重复名称,已自动添加区分信息`; } confirmMessage += `\n\n点击"确定"进行重命名,点击"取消"放弃操作。`; userConfirmed = confirm(confirmMessage); } if (userConfirmed) { // 获取文件夹ID const folderId = getFolderId(fileElement); if (folderId) { performRename(folderId, newName, fileName); } else { console.error('无法获取文件夹ID,元素信息:', { element: fileElement, tagName: fileElement.tagName, className: fileElement.className, id: fileElement.id, attributes: Array.from(fileElement.attributes).map(attr => `${attr.name}="${attr.value}"`), parentElement: fileElement.parentElement, closestTr: fileElement.closest('tr') }); // 提供更详细的错误信息和可能的解决方案 const errorMessage = `无法获取文件夹ID,重命名失败\n\n可能的原因:\n1. 当前页面结构与脚本不兼容\n2. 115网盘更新了页面结构\n3. 文件夹不是标准的115网盘文件夹\n\n建议:\n- 刷新页面后重试\n- 确保在115网盘的文件列表页面使用\n- 联系脚本开发者更新兼容性`; alert(errorMessage); // 尝试手动重命名提示 if (CONFIG.COPY_TO_CLIPBOARD_ON_ERROR) { const manualRename = confirm(`是否要复制建议的文件名到剪贴板?\n您可以手动重命名文件夹\n\n建议文件名:"${newName}"`); if (manualRename) { try { navigator.clipboard.writeText(newName).then(() => { alert('文件名已复制到剪贴板!\n您可以手动重命名文件夹'); }).catch(() => { // 降级方案:显示文件名让用户手动复制 prompt('请复制以下文件名进行手动重命名:', newName); }); } catch (error) { // 降级方案:显示文件名让用户手动复制 prompt('请复制以下文件名进行手动重命名:', newName); } } } } } } // 获取文件夹ID function getFolderId(fileElement) { console.log('开始获取文件夹ID,元素:', fileElement); // 尝试从不同的属性中获取文件夹ID const row = fileElement.closest('tr'); console.log('找到的行元素:', row); let folderId = null; if (row) { // 115网盘的文件夹ID通常存储在这些属性中 const possibleAttributes = ['cate_id', 'data-id', 'nid', 'file_id', 'fid', 'id', 'data-fid', 'data-file-id']; for (const attr of possibleAttributes) { const value = row.getAttribute(attr); if (value) { debugLog(`从行元素的${attr}属性获取到ID:`, value); folderId = value; break; } } // 如果还没找到,尝试从行内的链接或按钮中获取 if (!folderId) { const links = row.querySelectorAll('a[href*="cid="], a[href*="fid="]'); for (const link of links) { const href = link.getAttribute('href'); const cidMatch = href.match(/cid=([^&]+)/); const fidMatch = href.match(/fid=([^&]+)/); if (cidMatch) { folderId = cidMatch[1]; debugLog('从链接href中获取到cid:', folderId); break; } else if (fidMatch) { folderId = fidMatch[1]; debugLog('从链接href中获取到fid:', folderId); break; } } } // 尝试从onclick事件中获取ID if (!folderId) { const clickableElements = row.querySelectorAll('[onclick]'); for (const element of clickableElements) { const onclick = element.getAttribute('onclick'); const idMatch = onclick.match(/['"]([a-zA-Z0-9]+)['"]/); if (idMatch && idMatch[1].length > 5) { // 假设ID长度大于5 folderId = idMatch[1]; debugLog('从onclick事件中获取到ID:', folderId); break; } } } } // 尝试从元素本身获取 if (!folderId) { const possibleAttributes = ['cate_id', 'data-id', 'nid', 'file_id', 'fid', 'id', 'data-fid', 'data-file-id']; for (const attr of possibleAttributes) { const value = fileElement.getAttribute(attr); if (value) { debugLog(`从文件元素的${attr}属性获取到ID:`, value); folderId = value; break; } } } // 如果还是没找到,尝试从父元素中查找 if (!folderId) { let parent = fileElement.parentElement; let depth = 0; while (parent && depth < 5) { const possibleAttributes = ['cate_id', 'data-id', 'nid', 'file_id', 'fid', 'id', 'data-fid', 'data-file-id']; for (const attr of possibleAttributes) { const value = parent.getAttribute(attr); if (value && value.length > 3) { // 基本的ID长度检查 debugLog(`从父元素的${attr}属性获取到ID:`, value); folderId = value; break; } } if (folderId) break; parent = parent.parentElement; depth++; } } debugLog('最终获取到的文件夹ID:', folderId); return folderId; } // 批量重命名相关变量 let batchRenameInProgress = false; let batchRenameResults = []; // 创建批量重命名按钮 function createBatchRenameButton() { if (!CONFIG.ENABLE_BATCH_RENAME) { return; } // 检查是否有绿标文件 const completeFiles = collectCompleteFiles(); const existingButton = document.getElementById('batch-rename-btn'); if (completeFiles.length === 0) { // 如果没有绿标文件,移除按钮(如果存在) if (existingButton) { existingButton.remove(); debugLog('没有绿标文件,已移除批量重命名按钮'); } return; } // 如果按钮已存在且有绿标文件,不重复创建 if (existingButton) { return; } const button = document.createElement('button'); button.id = 'batch-rename-btn'; button.innerHTML = `🔄 批量重命名绿标文件 (${completeFiles.length})`; button.style.cssText = ` position: fixed; top: 20px; right: 20px; z-index: 10000; padding: 10px 15px; background: linear-gradient(135deg, #4CAF50, #45a049); color: white; border: none; border-radius: 8px; cursor: pointer; font-size: 14px; font-weight: bold; box-shadow: 0 4px 12px rgba(76, 175, 80, 0.3); transition: all 0.3s ease; min-width: 160px; `; // 悬停效果 button.addEventListener('mouseenter', function() { this.style.transform = 'translateY(-2px)'; this.style.boxShadow = '0 6px 16px rgba(76, 175, 80, 0.4)'; }); button.addEventListener('mouseleave', function() { this.style.transform = 'translateY(0)'; this.style.boxShadow = '0 4px 12px rgba(76, 175, 80, 0.3)'; }); button.addEventListener('click', startBatchRename); document.body.appendChild(button); debugLog(`批量重命名按钮已创建,检测到 ${completeFiles.length} 个绿标文件`); } // 收集所有绿标(完整)文件 function collectCompleteFiles() { const completeFiles = []; const greenDots = document.querySelectorAll('.file-status-dot'); for (const dot of greenDots) { // 检查是否是绿色圆点(完整文件) const bgColor = window.getComputedStyle(dot).backgroundColor; const isGreen = bgColor.includes('76, 175, 80') || bgColor.includes('rgb(76, 175, 80)') || dot.style.backgroundColor === CONFIG.COMPLETE_COLOR; if (isGreen) { const fileElement = dot.closest('tr, li, .file-item, .list-item'); if (fileElement) { let fileName = ''; if (fileElement.title) { fileName = fileElement.title; } else if (fileElement.textContent) { fileName = fileElement.textContent.trim(); } else if (fileElement.innerText) { fileName = fileElement.innerText.trim(); } if (fileName && fileName.includes('集')) { completeFiles.push({ element: fileElement, fileName: fileName, dot: dot }); } } } } debugLog(`收集到 ${completeFiles.length} 个完整文件`); return completeFiles; } // 开始批量重命名 async function startBatchRename() { if (batchRenameInProgress) { alert('批量重命名正在进行中,请稍候...'); return; } const completeFiles = collectCompleteFiles(); if (completeFiles.length === 0) { alert('未找到可重命名的完整文件(绿标文件)\n\n请确保:\n1. 页面已完全加载\n2. 存在标记为完整的文件夹\n3. 文件夹名称包含"集"字符'); return; } // 预处理:检查哪些文件可以成功提取标题,并处理重复名称 const validFiles = []; const invalidFiles = []; const usedNames = new Set(); // 用于跟踪已使用的文件名 for (const file of completeFiles) { const extracted = extractTitleAndYear(file.fileName); if (extracted.title && extracted.title !== '未知影视剧' && extracted.title.length >= 2) { // 构建基础新文件名 let baseName = extracted.year ? `${extracted.title} (${extracted.year})` : extracted.title; let finalName = baseName; // 检查是否与已处理的文件名重复 if (usedNames.has(finalName.toLowerCase())) { // 如果重复,尝试添加清晰度信息 const quality = extractQuality(file.fileName); if (quality) { finalName = `${baseName} ${quality}`; debugLog('批量重命名检测到重复名称,添加清晰度后缀:', finalName); // 再次检查添加清晰度后是否还有重复 if (usedNames.has(finalName.toLowerCase())) { // 如果还是重复,添加时间戳 const timestamp = new Date().getTime().toString().slice(-4); finalName = `${baseName} ${quality} (${timestamp})`; debugLog('批量重命名添加清晰度后仍重复,添加时间戳:', finalName); } } else { // 如果没有清晰度信息,直接添加时间戳 const timestamp = new Date().getTime().toString().slice(-4); finalName = `${baseName} (${timestamp})`; debugLog('批量重命名未找到清晰度信息,添加时间戳:', finalName); } } // 再次检查是否与页面现有文件名重复 const currentElement = file.element.closest('tr') || file.element; if (checkDuplicateName(finalName, currentElement)) { const quality = extractQuality(file.fileName); if (quality && !finalName.includes(quality)) { finalName = `${baseName} ${quality}`; if (checkDuplicateName(finalName, currentElement)) { const timestamp = new Date().getTime().toString().slice(-4); finalName = `${baseName} ${quality} (${timestamp})`; } } else { const timestamp = new Date().getTime().toString().slice(-4); finalName = `${baseName} (${timestamp})`; } } // 记录已使用的文件名 usedNames.add(finalName.toLowerCase()); validFiles.push({ ...file, extracted: extracted, newName: finalName, quality: extractQuality(file.fileName) }); } else { invalidFiles.push(file); } } let confirmMessage = `准备批量重命名 ${completeFiles.length} 个完整文件\n\n`; confirmMessage += `✅ 可重命名: ${validFiles.length} 个\n`; if (invalidFiles.length > 0) { confirmMessage += `❌ 无法提取标题: ${invalidFiles.length} 个\n\n`; confirmMessage += '无法提取标题的文件:\n'; invalidFiles.slice(0, 3).forEach(file => { confirmMessage += `• ${file.fileName.substring(0, 50)}...\n`; }); if (invalidFiles.length > 3) { confirmMessage += `• 还有 ${invalidFiles.length - 3} 个文件...\n`; } confirmMessage += '\n'; } if (validFiles.length > 0) { confirmMessage += '预览重命名结果(前3个):\n'; validFiles.slice(0, 3).forEach(file => { let preview = `• "${file.fileName.substring(0, 30)}..." → "${file.newName}"`; if (file.quality) { preview += ` [${file.quality}]`; } confirmMessage += preview + '\n'; }); if (validFiles.length > 3) { confirmMessage += `• 还有 ${validFiles.length - 3} 个文件...\n`; } // 统计清晰度信息 const qualityCount = validFiles.filter(f => f.quality).length; if (qualityCount > 0) { confirmMessage += `\n📺 检测到清晰度信息: ${qualityCount} 个文件\n`; } } confirmMessage += '\n是否继续批量重命名?'; if (CONFIG.BATCH_CONFIRM_BEFORE_START && !confirm(confirmMessage)) { return; } if (validFiles.length === 0) { alert('没有可以重命名的文件,操作取消。'); return; } // 开始批量重命名 await performBatchRename(validFiles); } // 执行批量重命名 async function performBatchRename(files) { batchRenameInProgress = true; batchRenameResults = []; const button = document.getElementById('batch-rename-btn'); const originalText = button.innerHTML; let successCount = 0; let failCount = 0; const usedNames = new Set(); // 跟踪已使用的文件名 try { for (let i = 0; i < files.length; i++) { const file = files[i]; // 更新按钮状态 if (CONFIG.BATCH_SHOW_PROGRESS) { button.innerHTML = `🔄 重命名中... (${i + 1}/${files.length})`; button.style.background = 'linear-gradient(135deg, #FF9800, #F57C00)'; } try { debugLog(`批量重命名 ${i + 1}/${files.length}: ${file.fileName} → ${file.newName}`); const folderId = getFolderId(file.element); if (folderId) { const result = await performRenameAsync(folderId, file.newName, file.fileName, usedNames); if (result.success) { successCount++; batchRenameResults.push({ fileName: file.fileName, newName: result.finalName, status: 'success' }); } else { failCount++; batchRenameResults.push({ fileName: file.fileName, newName: file.newName, status: 'failed', error: result.error || '重命名API调用失败' }); if (CONFIG.BATCH_STOP_ON_ERROR) { break; } } } else { failCount++; batchRenameResults.push({ fileName: file.fileName, newName: file.newName, status: 'failed', error: '无法获取文件夹ID' }); if (CONFIG.BATCH_STOP_ON_ERROR) { break; } } } catch (error) { failCount++; batchRenameResults.push({ fileName: file.fileName, newName: file.newName, status: 'failed', error: error.message }); debugLog(`批量重命名失败: ${file.fileName}`, error); if (CONFIG.BATCH_STOP_ON_ERROR) { break; } } // 延迟以避免请求过于频繁 if (i < files.length - 1) { await new Promise(resolve => setTimeout(resolve, CONFIG.BATCH_RENAME_DELAY)); } } } finally { batchRenameInProgress = false; // 恢复按钮状态 button.innerHTML = originalText; button.style.background = 'linear-gradient(135deg, #4CAF50, #45a049)'; // 显示结果 showBatchRenameResults(successCount, failCount); // 如果有成功的重命名且启用了自动刷新 if (successCount > 0 && CONFIG.AUTO_REFRESH_AFTER_RENAME) { setTimeout(() => { location.reload(); }, 2000); } } } // 异步版本的重命名函数 function performRenameAsync(folderId, newName, originalName, usedNames = new Set()) { return new Promise((resolve) => { debugLog('执行异步重命名操作:', { folderId, newName, originalName }); // 验证文件夹ID if (!folderId || folderId.length < 3) { debugLog('无效的文件夹ID:', folderId); resolve({ success: false, error: '无效的文件夹ID' }); return; } // 清理文件名 const cleanName = newName .replace(/\\/g, "") .replace(/\//g, " ") .replace(/:/g, " ") .replace(/\?/g, " ") .replace(/"/g, " ") .replace(/</g, " ") .replace(/>/g, " ") .replace(/\|/g, "") .replace(/\*/g, " ") .trim(); // 准备API请求参数 const requestBody = new URLSearchParams({ fid: folderId, file_name: cleanName }); // 调用115的重命名API fetch('https://webapi.115.com/files/edit', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'X-Requested-With': 'XMLHttpRequest' }, body: requestBody, credentials: 'include' }) .then(response => { if (!response.ok) { throw new Error(`HTTP错误: ${response.status}`); } return response.json(); }) .then(data => { if (data.state === true || data.state === 1) { debugLog('异步重命名成功:', cleanName); usedNames.add(cleanName.toLowerCase()); resolve({ success: true, finalName: cleanName }); } else { debugLog('异步重命名失败,API返回:', data); const errorMsg = data.error || data.msg || data.message || '未知错误'; // 检查是否是重复名称错误,如果是则尝试添加清晰度参数 if (errorMsg.includes('已存在') || errorMsg.includes('重复') || errorMsg.includes('duplicate')) { debugLog('检测到重复名称错误,尝试添加清晰度参数重新重命名'); // 提取清晰度信息 const quality = extractQuality(originalName); if (quality && !cleanName.includes(quality)) { const newNameWithQuality = `${cleanName} ${quality}`; if (!usedNames.has(newNameWithQuality.toLowerCase())) { debugLog('尝试使用带清晰度的文件名重新重命名:', newNameWithQuality); // 递归调用,使用带清晰度的文件名 performRenameAsync(folderId, newNameWithQuality, originalName, usedNames) .then(result => resolve(result)); return; } } // 如果没有清晰度信息或已包含清晰度,尝试添加时间戳 const timestamp = new Date().getTime().toString().slice(-4); const newNameWithTimestamp = `${cleanName} (${timestamp})`; if (!usedNames.has(newNameWithTimestamp.toLowerCase())) { debugLog('尝试添加时间戳:', newNameWithTimestamp); // 递归调用,使用带时间戳的文件名 performRenameAsync(folderId, newNameWithTimestamp, originalName, usedNames) .then(result => resolve(result)); return; } } resolve({ success: false, error: errorMsg }); } }) .catch(error => { debugLog('异步重命名请求失败:', error); resolve({ success: false, error: error.message }); }); }); } // 显示批量重命名结果 function showBatchRenameResults(successCount, failCount) { let message = `批量重命名完成!\n\n`; message += `✅ 成功: ${successCount} 个\n`; message += `❌ 失败: ${failCount} 个\n\n`; if (failCount > 0) { message += '失败的文件:\n'; const failedResults = batchRenameResults.filter(r => r.status === 'failed'); failedResults.slice(0, 5).forEach(result => { message += `• ${result.fileName.substring(0, 30)}... (${result.error})\n`; }); if (failedResults.length > 5) { message += `• 还有 ${failedResults.length - 5} 个失败的文件...\n`; } message += '\n'; } if (successCount > 0) { message += '成功重命名的文件:\n'; const successResults = batchRenameResults.filter(r => r.status === 'success'); // 统计清晰度信息 const qualityResults = successResults.filter(r => { const quality = extractQuality(r.fileName); return quality && r.newName.includes(quality); }); if (qualityResults.length > 0) { message += `📺 包含清晰度信息: ${qualityResults.length} 个\n\n`; } successResults.slice(0, 3).forEach(result => { const quality = extractQuality(result.fileName); let displayText = `• "${result.fileName.substring(0, 20)}..." → "${result.newName}"`; if (quality && result.newName.includes(quality)) { displayText += ` [${quality}]`; } message += displayText + '\n'; }); if (successResults.length > 3) { message += `• 还有 ${successResults.length - 3} 个成功的文件...\n`; } } if (successCount > 0 && CONFIG.AUTO_REFRESH_AFTER_RENAME) { message += '\n页面将在2秒后自动刷新以显示更改。'; } alert(message); } // 执行重命名操作 function performRename(folderId, newName, originalName) { debugLog('执行重命名操作:', { folderId, newName, originalName }); // 验证文件夹ID if (!folderId || folderId.length < 3) { console.error('无效的文件夹ID:', folderId); alert('文件夹ID无效,无法执行重命名操作'); return; } // 清理文件名,移除115不支持的字符 const cleanName = newName .replace(/\\/g, "") .replace(/\//g, " ") .replace(/:/g, " ") .replace(/\?/g, " ") .replace(/"/g, " ") .replace(/</g, " ") .replace(/>/g, " ") .replace(/\|/g, "") .replace(/\*/g, " ") .trim(); debugLog('清理后的文件名:', cleanName); // 显示进度提示 const progressMessage = `正在重命名文件夹...\n\n原文件名: "${originalName}"\n新文件名: "${cleanName}"`; console.log(progressMessage); // 准备API请求参数 const requestBody = new URLSearchParams({ fid: folderId, file_name: cleanName }); debugLog('API请求参数:', { url: 'https://webapi.115.com/files/edit', method: 'POST', fid: folderId, file_name: cleanName }); // 调用115的重命名API fetch('https://webapi.115.com/files/edit', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'X-Requested-With': 'XMLHttpRequest' }, body: requestBody, credentials: 'include' // 包含cookies }) .then(response => { debugLog('API响应状态:', response.status, response.statusText); if (!response.ok) { throw new Error(`HTTP错误: ${response.status} ${response.statusText}`); } return response.json(); }) .then(data => { debugLog('API响应数据:', data); if (data.state === true || data.state === 1) { debugLog('重命名成功:', cleanName); alert(`重命名成功!\n\n原文件名: "${originalName}"\n新文件名: "${cleanName}"${CONFIG.AUTO_REFRESH_AFTER_RENAME ? '\n\n页面将在1秒后刷新以显示更改' : ''}`); // 刷新页面以显示更改(如果启用) if (CONFIG.AUTO_REFRESH_AFTER_RENAME) { setTimeout(() => { location.reload(); }, 1000); } } else { debugLog('重命名失败,API返回:', data); const errorMsg = data.error || data.msg || data.message || '未知错误'; // 检查是否是重复名称错误,如果是则尝试添加清晰度参数 if (errorMsg.includes('已存在') || errorMsg.includes('重复') || errorMsg.includes('duplicate')) { debugLog('检测到重复名称错误,尝试添加清晰度参数重新重命名'); // 提取清晰度信息 const quality = extractQuality(originalName); if (quality && !cleanName.includes(quality)) { const newNameWithQuality = `${cleanName} ${quality}`; debugLog('尝试使用带清晰度的文件名重新重命名:', newNameWithQuality); // 确认是否要使用带清晰度的文件名重新尝试 const retryConfirm = confirm(`重命名失败:${errorMsg}\n\n检测到文件清晰度信息:${quality}\n\n是否尝试使用以下文件名重新重命名?\n"${newNameWithQuality}"\n\n点击"确定"重新尝试,点击"取消"放弃操作。`); if (retryConfirm) { // 递归调用重命名,使用带清晰度的文件名 performRename(folderId, newNameWithQuality, originalName); return; // 避免显示原始错误消息 } } else { // 如果没有清晰度信息或已包含清晰度,尝试添加时间戳 const timestamp = new Date().getTime().toString().slice(-4); const newNameWithTimestamp = `${cleanName} (${timestamp})`; debugLog('没有清晰度信息或已包含,尝试添加时间戳:', newNameWithTimestamp); const retryConfirm = confirm(`重命名失败:${errorMsg}\n\n是否尝试使用以下文件名重新重命名?\n"${newNameWithTimestamp}"\n\n点击"确定"重新尝试,点击"取消"放弃操作。`); if (retryConfirm) { // 递归调用重命名,使用带时间戳的文件名 performRename(folderId, newNameWithTimestamp, originalName); return; // 避免显示原始错误消息 } } } // 显示原始错误消息 alert(`重命名失败\n\n错误信息: ${errorMsg}\n\n可能的原因:\n1. 没有重命名权限\n2. 文件名包含不支持的字符\n3. 文件夹正在被使用\n4. 网络连接问题\n5. 目录名称已存在`); } }) .catch(error => { debugLog('重命名请求失败:', error); let errorMessage = `重命名请求失败\n\n错误详情: ${error.message}`; if (error.message.includes('HTTP错误')) { errorMessage += `\n\n可能的原因:\n1. 未登录(不可用)115网盘\n2. 会话已过期\n3. 网络连接问题\n4. 115服务器暂时不可用`; } else if (error.message.includes('Failed to fetch')) { errorMessage += `\n\n可能的原因:\n1. 网络连接中断\n2. 115网盘服务器无响应\n3. 浏览器阻止了请求`; } alert(errorMessage); }); } // 查找文件夹图标元素 function findFolderIcon(fileElement) { // 115网盘特定的文件夹图标选择器 const iconSelectors = [ // 115网盘文件夹图标的常见选择器 '.list-thumb', '.file-icon', '.folder-icon', 'img[src*="folder"]', 'img[src*="dir"]', '.icon', 'i[class*="folder"]', 'span[class*="icon"]', // 通用图标选择器 '.fa-folder', '.glyphicon-folder' ]; // 首先在当前行元素中查找图标 const rowElement = fileElement.closest('tr') || fileElement; for (const selector of iconSelectors) { const icon = rowElement.querySelector(selector); if (icon) { return icon; } } // 如果没找到,尝试查找第一个td元素(通常包含图标) const firstCell = rowElement.querySelector('td:first-child'); if (firstCell) { // 在第一个单元格中查找任何图片或图标元素 const imgIcon = firstCell.querySelector('img, i, span[class*="icon"], .icon'); if (imgIcon) { return imgIcon; } // 如果还是没找到,返回第一个单元格本身作为图标容器 return firstCell; } return null; } // 获取文件夹内文件数量 // 文件数量获取函数 - 使用115官方API async function getFileCount(fileElement, retries = 3, delay = 1000) { const cid = extractFolderId(fileElement); if (!cid) { debugLog('无法提取文件夹ID,返回null'); return null; } debugLog('使用115官方API获取文件数量,文件夹ID:', cid); const apiCount = await getFileCountFromAPI(cid, retries, delay); if (apiCount !== null) { debugLog('API成功获取文件数量:', apiCount); return apiCount; } debugLog('API获取文件数量失败'); return null; } // 提取文件夹ID的函数 - 支持多种115网盘页面布局 function extractFolderId(fileElement) { // 尝试多种方式获取文件夹ID let cid = null; // 方式1: 从元素属性中获取 cid = fileElement.getAttribute('cate_id') || fileElement.getAttribute('data-id') || fileElement.getAttribute('data-cid') || fileElement.getAttribute('fid'); if (cid) { debugLog('从元素属性获取到文件夹ID:', cid); return cid; } // 方式2: 从父元素或行元素中获取 const row = fileElement.closest('tr, li, .file-item, .list-item'); if (row) { cid = row.getAttribute('cate_id') || row.getAttribute('data-id') || row.getAttribute('data-cid') || row.getAttribute('nid') || row.getAttribute('fid'); if (cid) { debugLog('从行元素获取到文件夹ID:', cid); return cid; } } // 方式3: 从文件夹链接中提取 const folderLink = fileElement.querySelector('a[href*="cid="]') || fileElement.closest('tr, li')?.querySelector('a[href*="cid="]'); if (folderLink) { const href = folderLink.getAttribute('href'); const cidMatch = href.match(/cid=([^&]+)/); if (cidMatch) { cid = cidMatch[1]; debugLog('从链接URL提取到文件夹ID:', cid); return cid; } } // 方式4: 从onclick事件中提取 const clickableElement = fileElement.querySelector('[onclick*="cid"]') || fileElement.closest('tr, li')?.querySelector('[onclick*="cid"]'); if (clickableElement) { const onclick = clickableElement.getAttribute('onclick'); const cidMatch = onclick.match(/cid['"]?\s*[:=]\s*['"]?([^'"\s,)]+)/); if (cidMatch) { cid = cidMatch[1]; debugLog('从onclick事件提取到文件夹ID:', cid); return cid; } } debugLog('无法提取文件夹ID'); return null; } // 使用115 API获取文件数量 - 参考115pan_aria2的实现 async function getFileCountFromAPI(cid, retries = 3, delay = 1000) { if (!cid || cid.length < 3) { debugLog('无效的文件夹ID:', cid); return null; } // 使用与115pan_aria2相同的API端点和参数 const limit = 1000; // 使用较大的limit以获取完整的文件数量 const url = `https://webapi.115.com/files?aid=1&limit=${limit}&offset=0&show_dir=1&cid=${cid}`; for (let i = 0; i < retries; i++) { try { debugLog(`API调用尝试 ${i + 1}/${retries}:`, url); const data = await new Promise((resolve, reject) => { const gmRequest = GM.xmlHttpRequest || GM_xmlhttpRequest; if (!gmRequest) { reject(new Error('GM.xmlHttpRequest 不可用')); return; } gmRequest({ method: 'GET', url: url, headers: { 'User-Agent': navigator.userAgent, 'Referer': 'https://115.com/', 'X-Requested-With': 'XMLHttpRequest' }, onload: function(response) { if (response.status >= 200 && response.status < 300) { try { const data = JSON.parse(response.responseText); resolve(data); } catch (parseError) { reject(new Error(`JSON解析错误: ${parseError.message}`)); } } else { reject(new Error(`HTTP错误: ${response.status} ${response.statusText}`)); } }, onerror: function(error) { reject(new Error(`网络错误: ${error.message || '未知错误'}`)); }, ontimeout: function() { reject(new Error('请求超时')); }, timeout: 10000 }); }); debugLog('API响应数据:', data); if (data.state === true || data.state === 1) { // 计算实际文件数量(排除文件夹) let fileCount = 0; if (data.data && Array.isArray(data.data)) { // 统计非文件夹项目(有sha值的是文件,没有sha值的是文件夹) fileCount = data.data.filter(item => item.sha && item.sha.length > 0).length; debugLog(`API返回总项目数: ${data.data.length}, 文件数: ${fileCount}`); } else if (typeof data.count === 'number') { // 如果没有详细数据,使用总数作为近似值 fileCount = data.count; debugLog(`API返回总数: ${fileCount}`); } return fileCount; } else { const errorMsg = data.error || data.msg || data.message || '未知API错误'; debugLog('API返回错误状态:', errorMsg); // 如果是权限错误,不重试 if (errorMsg.includes('权限') || errorMsg.includes('登录(不可用)') || errorMsg.includes('认证')) { debugLog('检测到权限错误,停止重试'); return null; } } } catch (error) { debugLog(`API调用失败 (尝试 ${i + 1}/${retries}):`, error.message); // 如果是网络错误或超时,可以重试 if (i < retries - 1 && (error.message.includes('网络错误') || error.message.includes('请求超时') || error.message.includes('HTTP错误'))) { debugLog(`等待 ${delay}ms 后重试...`); await new Promise(resolve => setTimeout(resolve, delay)); continue; } } // 如果不是最后一次尝试,等待后重试 if (i < retries - 1) { await new Promise(resolve => setTimeout(resolve, delay)); } } debugLog('所有API调用尝试均失败'); return null; } // 处理iframe内容 function processIframes() { const iframes = document.querySelectorAll('iframe'); for (const iframe of iframes) { try { if (iframe.contentDocument && iframe.contentDocument.body) { debugLog('找到可访问的iframe,开始处理其内容...'); processFileItemsInDocument(iframe.contentDocument); } } catch (error) { debugLog('无法访问iframe内容(可能是跨域限制):', error.message); } } } // 在指定文档中处理文件列表项 async function processFileItemsInDocument(doc = document) { debugLog('开始处理文件列表项...'); // 查找文件列表容器的多种可能选择器 const possibleSelectors = [ // 115网盘特定选择器 - 针对文件夹行 'tr[data-id]', '.list-contents tr', '.file-opr-wraper', '.list-item', // 通用选择器 '.file-item', '.file-name', '[data-file-name]', 'a[title]', '.filename' ]; let fileItems = []; for (const selector of possibleSelectors) { const elements = doc.querySelectorAll(selector); if (elements.length > 0) { fileItems = Array.from(elements); debugLog(`找到 ${fileItems.length} 个文件项,使用选择器: ${selector}`); break; } } if (fileItems.length === 0) { debugLog('未找到文件列表项,尝试查找所有可能的文本元素...'); // 如果没有找到特定的文件列表,尝试查找所有包含文本的元素 const allElements = doc.querySelectorAll('*'); fileItems = Array.from(allElements).filter(el => { const text = el.textContent || el.innerText || ''; return text.includes('集') && el.children.length === 0; // 只选择叶子节点 }); debugLog(`通过文本搜索找到 ${fileItems.length} 个可能的文件项`); } for (const item of fileItems) { try { // 获取文件名 let fileName = ''; if (item.title) { fileName = item.title; } else if (item.textContent) { fileName = item.textContent.trim(); } else if (item.innerText) { fileName = item.innerText.trim(); } if (!fileName) continue; debugLog(`处理文件: ${fileName}`); // 检查是否已经添加过标注 if (item.querySelector('.file-status-dot')) { continue; } // 检查是否包含"全xx集"模式 const episodeNumber = extractEpisodeNumber(fileName); if (episodeNumber) { // 获取文件夹内文件数量 let fileCount = await getFileCount(item); if (fileCount === null) { debugLog('API方法获取文件数量失败'); fileCount = -1; // 设置为未知 } debugLog(`${fileName}: 提取的集数=${episodeNumber}, 获取的文件数量=${fileCount}`); // 创建标注圆点 let dot; if (fileCount === -1) { // 无法获取文件数量的处理 if (CONFIG.HIDE_UNKNOWN_FILES) { // 隐藏无法确定文件数量的文件,不显示任何标注 debugLog(`${fileName}: 无法确定文件数量,已隐藏标注`); continue; } else if (CONFIG.SHOW_UNKNOWN_AS_INCOMPLETE) { // 显示为不完整(红色) dot = createDot(CONFIG.INCOMPLETE_COLOR, '?', episodeNumber, fileName, item); debugLog(`${fileName}: 未知文件数量,标记为不完整 (?/${episodeNumber})`); } else { // 不显示标注 debugLog(`${fileName}: 无法确定文件数量,跳过标注`); continue; } } else if (fileCount >= episodeNumber) { dot = createDot(CONFIG.COMPLETE_COLOR, fileCount, episodeNumber, fileName, item); // 完整 debugLog(`${fileName}: 完整 (${fileCount}/${episodeNumber})`); } else { dot = createDot(CONFIG.INCOMPLETE_COLOR, fileCount, episodeNumber, fileName, item); // 不完整 debugLog(`${fileName}: 不完整 (${fileCount}/${episodeNumber})`); } dot.className = 'file-status-dot'; // 找到文件夹图标并添加圆点 const iconElement = findFolderIcon(item); if (iconElement) { // 确保图标容器有相对定位 const computedStyle = window.getComputedStyle(iconElement); if (computedStyle.position === 'static') { iconElement.style.position = 'relative'; } // 确保图标容器有足够的尺寸来显示圆点 if (iconElement.offsetWidth < 20 || iconElement.offsetHeight < 20) { iconElement.style.minWidth = '20px'; iconElement.style.minHeight = '20px'; } iconElement.appendChild(dot); debugLog(`${fileName}: 圆点已添加到图标元素`, iconElement); } else { // 备用方案:添加到文件名前面 if (item.firstChild) { item.insertBefore(dot, item.firstChild); } else { item.appendChild(dot); } debugLog(`${fileName}: 未找到图标元素,使用备用方案`); } } } catch (error) { debugLog('处理文件项时出错:', error); } } } // 处理文件列表项(包装函数) async function processFileItems() { // 处理主文档 await processFileItemsInDocument(document); // 处理iframe内容 processIframes(); } // 监听页面变化 function observePageChanges() { const observer = new MutationObserver((mutations) => { let shouldProcess = false; mutations.forEach((mutation) => { if (mutation.type === 'childList' && mutation.addedNodes.length > 0) { // 检查是否有新的文件列表项添加 for (const node of mutation.addedNodes) { if (node.nodeType === Node.ELEMENT_NODE) { if (node.classList && (node.classList.contains('file-item') || node.classList.contains('list-item') || node.querySelector && node.querySelector('.file-name'))) { shouldProcess = true; break; } } } } }); if (shouldProcess) { debugLog('检测到页面变化,重新处理文件列表...'); setTimeout(() => { processFileItems().then(() => { // 处理完文件后,更新批量重命名按钮状态 createBatchRenameButton(); }); }, CONFIG.MUTATION_DELAY); } }); observer.observe(document.body, { childList: true, subtree: true }); debugLog('页面变化监听器已启动'); } // 主函数 async function main() { debugLog('115网盘文件完整性检查器已启动'); try { // 等待页面加载 await new Promise(resolve => { if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', resolve); } else { resolve(); } }); // 等待一段时间让页面完全加载 await new Promise(resolve => setTimeout(resolve, CONFIG.PROCESS_DELAY)); // 处理当前页面的文件列表 await processFileItems(); // 处理完文件后,创建批量重命名按钮(仅在有绿标文件时显示) createBatchRenameButton(); // 开始监听页面变化 observePageChanges(); debugLog('文件完整性检查器初始化完成'); } catch (error) { debugLog('初始化失败:', error); } } // 启动脚本 main(); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址