// ==UserScript== // @name 115网盘-全能视频清理(时长+VDI+持久化) // @namespace http://tampermonkey.net/ // @version 2.4 // @description 合并了视频时长清理与VDI清晰度清理功能。支持独立勾选、参数持久化保存、VDI对照表。 // @author edhnt4551 // @match https://115.com/* // @icon https://115.com/favicon.ico // @grant GM_xmlhttpRequest // @grant GM_addStyle // @grant GM_setValue // @grant GM_getValue // @license MIT // @run-at document-end // @downloadURL https://update.greasyfork.icu/scripts/565078/115%E7%BD%91%E7%9B%98-%E5%85%A8%E8%83%BD%E8%A7%86%E9%A2%91%E6%B8%85%E7%90%86%28%E6%97%B6%E9%95%BF%2BVDI%2B%E6%8C%81%E4%B9%85%E5%8C%96%29.user.js // @updateURL https://update.greasyfork.icu/scripts/565078/115%E7%BD%91%E7%9B%98-%E5%85%A8%E8%83%BD%E8%A7%86%E9%A2%91%E6%B8%85%E7%90%86%28%E6%97%B6%E9%95%BF%2BVDI%2B%E6%8C%81%E4%B9%85%E5%8C%96%29.meta.js // ==/UserScript== (function() { 'use strict'; if (window.self !== window.top) return; // === 样式配置 === GM_addStyle(` #ce-cleaner-btn { position: fixed; top: 120px; right: 20px; z-index: 2147483647; padding: 8px 15px; background: #2777F8; color: #fff; border-radius: 4px; cursor: pointer; font-size: 14px; box-shadow: 0 2px 8px rgba(0,0,0,0.3); transition: 0.3s; display: flex; align-items: center; gap: 5px; font-weight: 500; } #ce-cleaner-btn:hover { background: #1C66E6; transform: translateY(-1px); } #ce-cleaner-panel { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 850px; max-height: 85vh; background: #fff; z-index: 2147483647; border-radius: 8px; box-shadow: 0 10px 40px rgba(0,0,0,0.4); display: none; flex-direction: column; overflow: hidden; font-size: 14px; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; border: 1px solid #ddd; } .ce-header { padding: 15px 20px; border-bottom: 1px solid #eee; display: flex; justify-content: space-between; align-items: center; background: #f9f9f9; } .ce-header h3 { margin: 0; font-size: 16px; color: #333; } .ce-close { cursor: pointer; font-size: 24px; color: #999; line-height: 1; } .ce-close:hover { color: #333; } .ce-body { padding: 20px; overflow-y: auto; flex: 1; } /* 控制区样式 */ .ce-controls { margin-bottom: 10px; background: #f0f7ff; padding: 15px; border-radius: 6px; border: 1px solid #e1ecf9; display: flex; flex-direction: column; gap: 10px; } .ce-control-row { display: flex; align-items: center; gap: 15px; padding-bottom: 10px; border-bottom: 1px dashed #dae8f9; } .ce-control-row:last-of-type { border-bottom: none; padding-bottom: 0; } .ce-group-label { font-weight:bold; color:#2777F8; font-size:13px; width: 80px; display:flex; align-items:center; gap:4px; } /* 选项样式 */ .ce-opt-label { display: flex; align-items: center; gap: 5px; cursor: pointer; user-select: none; color: #444; } .ce-opt-label:hover { color: #2777F8; } .ce-opt-label input[type="checkbox"] { width: 15px; height: 15px; margin: 0; cursor: pointer; accent-color: #2777F8; } .ce-input { padding: 4px; border: 1px solid #ddd; border-radius: 4px; width: 50px; text-align: center; font-size: 13px; } .ce-input:disabled { background: #f0f0f0; color: #bbb; border-color: #eee; cursor: not-allowed; } .ce-btn { padding: 6px 20px; border: none; border-radius: 4px; cursor: pointer; color: #fff; transition: 0.2s; font-weight:bold; } .ce-btn-primary { background: #2777F8; } .ce-btn-primary:hover { background: #1C66E6; } .ce-btn-danger { background: #E64C4C; } .ce-btn-danger:hover { background: #D63C3C; } .ce-btn-disabled { background: #ccc !important; cursor: not-allowed; } /* 提示说明区 */ .ce-tips { margin-bottom: 15px; padding: 10px 15px; background: #fff8e1; border: 1px solid #ffe0b2; border-radius: 6px; color: #8d6e63; font-size: 12px; line-height: 1.6; } .ce-tips strong { color: #e65100; font-weight: 600; } .ce-vdi-table { margin-top: 5px; display: flex; flex-wrap: wrap; gap: 8px; } .ce-vdi-item { background: rgba(255,255,255,0.6); padding: 1px 6px; border-radius: 3px; border: 1px solid #ffe0b2; font-family: monospace; } /* 表格样式 */ .ce-list-table { width: 100%; border-collapse: collapse; margin-top: 10px; } .ce-list-table th, .ce-list-table td { text-align: left; padding: 10px; border-bottom: 1px solid #eee; font-size: 13px; } .ce-list-table th { background: #fff; position: sticky; top: 0; z-index: 1; border-bottom: 2px solid #eee; font-weight: 600; } .ce-footer { padding: 15px 20px; border-top: 1px solid #eee; text-align: right; background: #fff; } .ce-log { color: #666; font-size: 12px; margin-top: 10px; padding-left: 5px; } /* 标签样式 */ .ce-tag { padding: 2px 6px; border-radius: 3px; font-size: 11px; margin-left: 5px; white-space: nowrap; } .ce-tag-short { background: #ffebeb; color: #e64c4c; border:1px solid #ffcdd2; } .ce-tag-long { background: #e6f3ff; color: #2777f8; border:1px solid #bbdefb; } .ce-tag-vdi { background: #fff3e0; color: #ef6c00; border:1px solid #ffe0b2; } .ce-vdi-badge { display:inline-block; font-size:10px; background:#eee; color:#555; padding:1px 4px; border-radius:3px; margin-right:4px; } .ce-divider { height: 16px; width: 1px; background: #ccc; margin: 0 5px; } `); // === 设置管理 (持久化) === const settings = { save() { GM_setValue('cfg_min_on', document.getElementById('ce-chk-min-duration').checked); GM_setValue('cfg_min_val', document.getElementById('ce-duration-min').value); GM_setValue('cfg_max_on', document.getElementById('ce-chk-max-duration').checked); GM_setValue('cfg_max_val', document.getElementById('ce-duration-max').value); GM_setValue('cfg_vdi_on', document.getElementById('ce-chk-vdi').checked); GM_setValue('cfg_vdi_val', document.getElementById('ce-vdi-val').value); GM_setValue('cfg_vdi0_on', document.getElementById('ce-chk-vdi-zero').checked); }, load() { // 设置默认值 const setVal = (id, val) => { if(document.getElementById(id)) document.getElementById(id).value = val; }; const setChk = (id, val) => { const el = document.getElementById(id); if(el) { el.checked = val; // 触发change事件以更新关联输入框的disabled状态,或者手动处理 const inputId = el.getAttribute('data-target'); if(inputId) document.getElementById(inputId).disabled = !val; } }; setChk('ce-chk-min-duration', GM_getValue('cfg_min_on', false)); setVal('ce-duration-min', GM_getValue('cfg_min_val', 8)); setChk('ce-chk-max-duration', GM_getValue('cfg_max_on', false)); setVal('ce-duration-max', GM_getValue('cfg_max_val', 60)); setChk('ce-chk-vdi', GM_getValue('cfg_vdi_on', false)); setVal('ce-vdi-val', GM_getValue('cfg_vdi_val', 3)); setChk('ce-chk-vdi-zero', GM_getValue('cfg_vdi0_on', false)); } }; // === UI 构建 === const ui = { btn: null, panel: null, init() { if (document.getElementById('ce-cleaner-btn')) return; // 悬浮球 this.btn = document.createElement('div'); this.btn.id = 'ce-cleaner-btn'; this.btn.innerHTML = '🧹 视频清理助手'; this.btn.title = "点击打开筛选面板"; this.btn.onclick = (e) => { e.stopPropagation(); this.togglePanel(); }; document.body.appendChild(this.btn); // 主面板 this.panel = document.createElement('div'); this.panel.id = 'ce-cleaner-panel'; this.panel.innerHTML = `

🎥 视频清理助手 (持久化版)

×
⏱️ 时长
📺 清晰度
💡 提示: 您的设置(勾选状态和数值)会在点击“保存设置并扫描”时自动保存,下次无需重新输入。
📊 VDI 清晰度参考表:
1 = 低清 2 = 标清 3 = 高清 4 = 1080P 5 = 4K 100 = 原画
请勾选上方条件并点击扫描...
文件名 大小 VDI 删除原因
`; document.body.appendChild(this.panel); // 交互逻辑:复选框控制输入框禁用状态 this.bindCheckbox('ce-chk-min-duration', 'ce-duration-min'); this.bindCheckbox('ce-chk-max-duration', 'ce-duration-max'); this.bindCheckbox('ce-chk-vdi', 'ce-vdi-val'); // 恢复保存的设置 settings.load(); // 事件绑定 document.getElementById('ce-scan-btn').onclick = () => core.scan(); document.getElementById('ce-delete-btn').onclick = () => core.delete(); document.getElementById('ce-select-all').onchange = (e) => core.toggleAll(e.target.checked); }, bindCheckbox(chkId, inputId) { const chk = document.getElementById(chkId); const input = document.getElementById(inputId); chk.addEventListener('change', () => { input.disabled = !chk.checked; if(chk.checked) input.focus(); }); }, togglePanel() { this.panel.style.display = this.panel.style.display === 'flex' ? 'none' : 'flex'; }, log(msg) { const statusEl = document.getElementById('ce-status'); if(statusEl) statusEl.innerText = msg; }, renderList(files) { const tbody = document.getElementById('ce-file-list'); tbody.innerHTML = ''; if (files.length === 0) { tbody.innerHTML = '没有发现符合条件的文件'; this.updateSummary(0); return; } files.forEach(f => { const tr = document.createElement('tr'); // 构建原因标签 let reasonHtml = ''; if (f._reasons.includes('short')) reasonHtml += `时长<${f._limit_min}m`; if (f._reasons.includes('long')) reasonHtml += `时长>${f._limit_max}m`; if (f._reasons.includes('low_vdi')) reasonHtml += `VDI<${f._limit_vdi}`; if (f._reasons.includes('zero_vdi')) reasonHtml += `画质未知`; // 时长显示 const timeStr = core.formatTime(f.play_long); tr.innerHTML = `
${f.n}
${core.formatSize(f.s)} VDI: ${f.vdi || 0}
${timeStr}
${reasonHtml} `; tbody.appendChild(tr); }); this.updateSummary(files.length); }, updateSummary(count) { const btn = document.getElementById('ce-delete-btn'); document.getElementById('ce-summary').innerText = `共选中 ${count} 个文件`; if (count > 0) { btn.removeAttribute('disabled'); btn.classList.remove('ce-btn-disabled'); } else { btn.setAttribute('disabled', 'true'); btn.classList.add('ce-btn-disabled'); } } }; // === 核心逻辑 === const core = { cid: 0, getCid() { try { if (window.TOP && window.TOP.API && window.TOP.API.aid) return window.TOP.API.cid; } catch(e) {} const match = location.href.match(/[?&]cid=(\d+)/); if (match) return match[1]; return 0; }, formatTime(seconds) { if (!seconds) return '00:00'; const h = Math.floor(seconds / 3600); const m = Math.floor((seconds % 3600) / 60); const s = Math.floor(seconds % 60); if (h > 0) return `${h}:${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}`; return `${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}`; }, formatSize(bytes) { if (bytes === 0) return '0 B'; const k = 1024; const sizes = ['B', 'KB', 'MB', 'GB', 'TB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; }, async scan() { // 保存当前设置 settings.save(); this.cid = this.getCid(); if (!this.cid || this.cid == '0') { alert('⚠️ 未获取到文件夹ID。\n\n请进入具体的文件夹后再点击扫描。'); return; } // === 获取启用状态 === const useMin = document.getElementById('ce-chk-min-duration').checked; const useMax = document.getElementById('ce-chk-max-duration').checked; const useVdi = document.getElementById('ce-chk-vdi').checked; const useVdiZero = document.getElementById('ce-chk-vdi-zero').checked; if (!useMin && !useMax && !useVdi && !useVdiZero) { alert('⚠️ 请至少勾选一个筛选条件!'); return; } // === 获取数值 === const minVal = parseFloat(document.getElementById('ce-duration-min').value) || 0; const maxVal = parseFloat(document.getElementById('ce-duration-max').value) || 0; const minSec = minVal * 60; const maxSec = maxVal * 60; const vdiThreshold = parseInt(document.getElementById('ce-vdi-val').value) || 0; ui.log('正在获取文件列表...'); ui.renderList([]); try { const files = await this.fetchAllFiles(this.cid); const targets = files.filter(f => { if (!f.fid) return false; let reasons = []; const duration = parseFloat(f.play_long || 0); const vdi = isNaN(parseInt(f.vdi)) ? 0 : parseInt(f.vdi); // 1. 时长判断 if (duration > 0) { if (useMin && minSec > 0 && duration < minSec) { reasons.push('short'); f._limit_min = minVal; } if (useMax && maxSec > 0 && duration > maxSec) { reasons.push('long'); f._limit_max = maxVal; } } // 2. VDI(清晰度)判断 if (vdi === 0) { if (useVdiZero) reasons.push('zero_vdi'); } else { if (useVdi && vdiThreshold > 0 && vdi < vdiThreshold) { reasons.push('low_vdi'); f._limit_vdi = vdiThreshold; } } if (reasons.length > 0) { f._reasons = reasons; return true; } return false; }); ui.renderList(targets); ui.log(`✅ 扫描完成。共检索 ${files.length} 个视频,命中 ${targets.length} 个。`); } catch (e) { console.error(e); ui.log('❌ 扫描出错: ' + e.message); } }, async fetchAllFiles(cid) { let allData = []; let offset = 0; const limit = 1150; while (true) { ui.log(`正在加载第 ${Math.floor(offset/limit) + 1} 页数据...`); const url = `https://webapi.115.com/files?aid=1&cid=${cid}&o=user_ptime&asc=0&offset=${offset}&show_dir=0&limit=${limit}&code=&scid=&snap=0&natsort=1&type=4&format=json`; const data = await this.request(url); if (!data.state) throw new Error(data.error || 'API请求失败,请检查登录状态'); if (data.data && data.data.length > 0) allData = allData.concat(data.data); if (allData.length >= data.count || data.data.length < limit) break; offset += limit; await new Promise(r => setTimeout(r, 200)); } return allData; }, async delete() { const checkboxes = document.querySelectorAll('.ce-file-chk:checked'); if (checkboxes.length === 0) return; if (!confirm(`⚠️ 高能预警:\n\n即将删除选中的 ${checkboxes.length} 个文件。\n\n文件将移入【回收站】,确定继续吗?`)) return; const ids = Array.from(checkboxes).map(cb => cb.value); const btn = document.getElementById('ce-delete-btn'); const originalText = btn.innerText; btn.innerText = '正在执行删除...'; btn.setAttribute('disabled', 'true'); try { const batchSize = 500; for (let i = 0; i < ids.length; i += batchSize) { const chunk = ids.slice(i, i + batchSize); await this.deleteBatch(chunk); ui.log(`🗑️ 已删除 ${Math.min(i + batchSize, ids.length)} / ${ids.length} ...`); } ui.log('✅ 删除完成!请手动刷新网页查看结果。'); alert('清理完成!文件已移入回收站。'); ui.togglePanel(); setTimeout(()=> location.reload(), 1000); } catch (e) { alert('删除出错:' + e.message); btn.innerText = originalText; btn.removeAttribute('disabled'); } }, async deleteBatch(fids) { const fd = new FormData(); fd.append('pid', this.cid); fd.append('ignore_warn', '1'); fids.forEach((fid, index) => { fd.append(`fid[${index}]`, fid); }); return new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: "POST", url: "https://webapi.115.com/rb/delete", data: fd, headers: { "Origin": "https://115.com", "Referer": "https://115.com/", }, onload: (res) => { try { const json = JSON.parse(res.responseText); if (json.state) resolve(json); else reject(new Error(json.error)); } catch(e) { reject(new Error("返回数据解析失败")); } }, onerror: (e) => reject(e) }); }); }, request(url) { return new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: "GET", url: url, headers: { "User-Agent": navigator.userAgent, "Accept": "application/json" }, onload: (response) => { if (response.status === 200) { try { resolve(JSON.parse(response.responseText)); } catch (e) { reject(e); } } else { reject(new Error("HTTP " + response.status)); } }, onerror: (err) => reject(err) }); }); }, toggleAll(checked) { document.querySelectorAll('.ce-file-chk').forEach(cb => cb.checked = checked); } }; setTimeout(() => { ui.init(); }, 1000); })();