// ==UserScript==
// @name 访问码验证助手
// @author [email protected]
// @description 115网盘访问码验证助手,智能存储管理(115分享链接/ED2K/磁力链)、批量分享/接收/识别、元素屏蔽、附加功能、自动填写、数据导入导出等功能,大幅提升115网盘使用效率,让网盘管理更轻松高效!
// @description:en 115 Access Code Helper for 115 Netdisk: intelligent storage management (115 share links/ED2K/magnet links), batch share/receive/recognize, element blocking, extra utilities, auto-fill, data import/export, and more. Greatly improves efficiency on 115, making cloud drive management easier and more productive.
// @version 1.8.8.20250825
// @icon data:image/gif;base64,R0lGODlhgAGAAZECAPr7/ERNVv///wAAACH/C05FVFNDQVBFMi4wAwEAAAAh/wtYTVAgRGF0YVhNUDw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTQ1IDc5LjE2MzQ5OSwgMjAxOC8wOC8xMy0xNjo0MDoyMiAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDo5OTk5ODkxZS0wMWE2LTRlMTItYWM1Mi00YTIzMzI1MTViYjQiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MkU2QTk5NkYxM0UzMTFFQzg5RkRCRTMwRTcyQUZCRjIiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6MkU2QTk5NkUxM0UzMTFFQzg5RkRCRTMwRTcyQUZCRjIiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTkgKE1hY2ludG9zaCkiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDoxOTQyMDFlMi00OGRjLTQ3OTgtOTFlNi05ZjkyNzhjNTJlZTUiIHN0UmVmOmRvY3VtZW50SUQ9ImFkb2JlOmRvY2lkOnBob3Rvc2hvcDplMjk3YTUwZC00MTM4LWVmNDYtYTY3Ni1kZTkzNDU1ZjNmOTkiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRm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
// @match 115cdn.com/*
// @match 115.com/*
// @grant GM_xmlhttpRequest
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_deleteValue
// @grant GM_listValues
// @connect 115cdn.com
// @connect webapi.115.com
// @connect pmsg.115.com
// @license MIT
// @namespace https://gf.qytechs.cn/users/1453515
// ==/UserScript==
(function() {
'use strict';
const __GM_ORIG__ = {
get: typeof GM_getValue === 'function' ? GM_getValue : undefined,
set: typeof GM_setValue === 'function' ? GM_setValue : undefined,
del: typeof GM_deleteValue === 'function' ? GM_deleteValue : undefined,
keys: typeof GM_listValues === 'function' ? GM_listValues : undefined
};
const __KV_MEM__ = new Map();
const __IDB_CFG__ = {
name: 'VisitCodeHelperDB',
version: 1,
store: 'kv'
};
let __idbDbPromise = null;
function __openIDB__() {
if (__idbDbPromise) return __idbDbPromise;
__idbDbPromise = new Promise((resolve, reject) => {
try {
const req = indexedDB.open(__IDB_CFG__.name, __IDB_CFG__.version);
req.onupgradeneeded = function (e) {
const db = e.target.result;
if (!db.objectStoreNames.contains(__IDB_CFG__.store)) {
db.createObjectStore(__IDB_CFG__.store, { keyPath: 'key' });
}
};
req.onsuccess = () => resolve(req.result);
req.onerror = () => reject(req.error);
} catch (err) {
reject(err);
}
});
return __idbDbPromise;
}
async function __idbGetAll__() {
const db = await __openIDB__();
return new Promise((resolve, reject) => {
const tx = db.transaction(__IDB_CFG__.store, 'readonly');
const store = tx.objectStore(__IDB_CFG__.store);
const req = store.openCursor();
const out = [];
req.onsuccess = e => {
const cursor = e.target.result;
if (cursor) {
out.push(cursor.value);
cursor.continue();
} else {
resolve(out);
}
};
req.onerror = () => reject(req.error);
});
}
async function __idbSet__(key, value) {
const db = await __openIDB__();
return new Promise((resolve, reject) => {
const tx = db.transaction(__IDB_CFG__.store, 'readwrite');
const store = tx.objectStore(__IDB_CFG__.store);
const req = store.put({ key, value });
req.onsuccess = () => resolve(true);
req.onerror = () => reject(req.error);
});
}
async function __idbDel__(key) {
const db = await __openIDB__();
return new Promise((resolve, reject) => {
const tx = db.transaction(__IDB_CFG__.store, 'readwrite');
const store = tx.objectStore(__IDB_CFG__.store);
const req = store.delete(key);
req.onsuccess = () => resolve(true);
req.onerror = () => reject(req.error);
});
}
async function __idbBulkPut__(entries) {
if (!entries.length) return;
const db = await __openIDB__();
await new Promise((resolve, reject) => {
const tx = db.transaction(__IDB_CFG__.store, 'readwrite');
const store = tx.objectStore(__IDB_CFG__.store);
for (const [key, value] of entries) {
store.put({ key, value });
}
tx.oncomplete = () => resolve(true);
tx.onerror = () => reject(tx.error);
});
}
(async function __initKV__() {
try {
const all = await __idbGetAll__();
if (all && all.length) {
for (const row of all) {
__KV_MEM__.set(row.key, row.value);
}
} else if (__GM_ORIG__.keys && __GM_ORIG__.get) {
try {
const keys = __GM_ORIG__.keys();
for (const k of keys) {
try {
const v = __GM_ORIG__.get(k);
__KV_MEM__.set(k, v);
} catch (e) {}
}
__idbBulkPut__(Array.from(__KV_MEM__.entries())).catch(console.error);
} catch (e) {
console.warn('GM storage migration skipped:', e);
}
}
} catch (e) {
console.warn('IndexedDB init failed, fallback to memory only:', e);
}
})();
function GM_getValue(key, defaultValue) {
if (__KV_MEM__.has(key)) return __KV_MEM__.get(key);
if (__GM_ORIG__.get) {
try {
const v = __GM_ORIG__.get(key, defaultValue);
__KV_MEM__.set(key, v);
__idbSet__(key, v).catch(() => {});
return v;
} catch (_) {}
}
return defaultValue;
}
function GM_setValue(key, value) {
__KV_MEM__.set(key, value);
__idbSet__(key, value).catch(err => console.error('IDB set failed:', key, err));
}
function GM_deleteValue(key) {
__KV_MEM__.delete(key);
__idbDel__(key).catch(err => console.error('IDB delete failed:', key, err));
}
function GM_listValues() {
return Array.from(__KV_MEM__.keys());
}
const cssContent = `
.gear-icon {
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' width='24' height='24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill='%23666' d='M12 15.5A3.5 3.5 0 0 1 8.5 12A3.5 3.5 0 0 1 12 8.5A3.5 3.5 0 0 1 15.5 12A3.5 3.5 0 0 1 12 15.5M19.43 12.97C19.47 12.65 19.5 12.33 19.5 12C19.5 11.67 19.47 11.34 19.43 11L21.54 9.37C21.73 9.22 21.78 8.95 21.66 8.73L19.66 5.27C19.54 5.05 19.27 4.96 19.05 5.05L16.56 6.05C16.04 5.66 15.5 5.32 14.87 5.07L14.5 2.42C14.46 2.18 14.25 2 14 2H10C9.75 2 9.54 2.18 9.5 2.42L9.13 5.07C8.5 5.32 7.96 5.66 7.44 6.05L4.95 5.05C4.73 4.96 4.46 5.05 4.34 5.27L2.34 8.73C2.21 8.95 2.27 9.22 2.46 9.37L4.57 11C4.53 11.34 4.5 11.67 4.5 12C4.5 12.33 4.53 12.65 4.57 12.97L2.46 14.63C2.27 14.78 2.21 15.05 2.34 15.27L4.34 18.73C4.46 18.95 4.73 19.03 4.95 18.95L7.44 17.94C7.96 18.34 8.5 18.68 9.13 18.93L9.5 21.58C9.54 21.82 9.75 22 10 22H14C14.25 22 14.46 21.82 14.5 21.58L14.87 18.93C15.5 18.67 16.04 18.34 16.56 17.94L19.05 18.95C19.27 19.03 19.54 18.95 19.66 18.73L21.66 15.27C21.78 15.05 21.73 14.78 21.54 14.63L19.43 12.97Z'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-size: contain;
width: 24px;
height: 24px;
display: inline-block;
}
.gear-icon.active {
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' width='24' height='24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill='%234285f4' d='M12 15.5A3.5 3.5 0 0 1 8.5 12A3.5 3.5 0 0 1 12 8.5A3.5 3.5 0 0 1 15.5 12A3.5 3.5 0 0 1 12 15.5M19.43 12.97C19.47 12.65 19.5 12.33 19.5 12C19.5 11.67 19.47 11.34 19.43 11L21.54 9.37C21.73 9.22 21.78 8.95 21.66 8.73L19.66 5.27C19.54 5.05 19.27 4.96 19.05 5.05L16.56 6.05C16.04 5.66 15.5 5.32 14.87 5.07L14.5 2.42C14.46 2.18 14.25 2 14 2H10C9.75 2 9.54 2.18 9.5 2.42L9.13 5.07C8.5 5.32 7.96 5.66 7.44 6.05L4.95 5.05C4.73 4.96 4.46 5.05 4.34 5.27L2.34 8.73C2.21 8.95 2.27 9.22 2.46 9.37L4.57 11C4.53 11.34 4.5 11.67 4.5 12C4.5 12.33 4.53 12.65 4.57 12.97L2.46 14.63C2.27 14.78 2.21 15.05 2.34 15.27L4.34 18.73C4.46 18.95 4.73 19.03 4.95 18.95L7.44 17.94C7.96 18.34 8.5 18.68 9.13 18.93L9.5 21.58C9.54 21.82 9.75 22 10 22H14C14.25 22 14.46 21.82 14.5 21.58L14.87 18.93C15.5 18.67 16.04 18.34 16.56 17.94L19.05 18.95C19.27 19.03 19.54 18.95 19.66 18.73L21.66 15.27C21.78 15.05 21.73 14.78 21.54 14.63L19.43 12.97Z'/%3E%3C/svg%3E");
}
.refresh-icon {
background-image: url("data:image/svg+xml,%3Csvg width='14' height='14' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M23 4v6h-6M1 20v-6h6'/%3E%3Cpath d='M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-size: contain;
width: 14px;
height: 14px;
display: inline-block;
transition: all 0.2s ease;
}
.api-refresh-btn:hover .refresh-icon,
.refresh-btn:hover .refresh-icon {
background-image: url("data:image/svg+xml,%3Csvg width='14' height='14' viewBox='0 0 24 24' fill='none' stroke='%234285f4' stroke-width='2' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M23 4v6h-6M1 20v-6h6'/%3E%3Cpath d='M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15'/%3E%3C/svg%3E");
}
.internet-icon {
-webkit-mask-image: url("data:image/svg+xml,%3Csvg width='14' height='14' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' xmlns='http://www.w3.org/2000/svg'%3E%3Ccircle cx='12' cy='12' r='10'/%3E%3Cpath d='M2 12h20'/%3E%3Cpath d='M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z'/%3E%3C/svg%3E");
mask-image: url("data:image/svg+xml,%3Csvg width='14' height='14' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' xmlns='http://www.w3.org/2000/svg'%3E%3Ccircle cx='12' cy='12' r='10'/%3E%3Cpath d='M2 12h20'/%3E%3Cpath d='M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z'/%3E%3C/svg%3E");
-webkit-mask-repeat: no-repeat;
mask-repeat: no-repeat;
-webkit-mask-size: contain;
mask-size: contain;
background-color: currentColor;
width: 14px;
height: 14px;
display: inline-block;
transition: all 0.2s ease;
}
.extract-btn:hover .internet-icon {
background-color: #4caf50;
}
.success-icon {
background-image: url("data:image/svg+xml,%3Csvg width='16' height='16' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z' fill='%234caf50'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-size: contain;
width: 16px;
height: 16px;
display: inline-block;
}
.error-icon {
background-image: url("data:image/svg+xml,%3Csvg width='16' height='16' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Ccircle cx='12' cy='12' r='10' fill='%23f44336'/%3E%3Cpath d='M15 9L9 15M9 9l6 6' stroke='%23fff' stroke-width='2' stroke-linecap='round'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-size: contain;
width: 16px;
height: 16px;
display: inline-block;
}
.processing-icon {
background-image: url("data:image/svg+xml,%3Csvg width='16' height='16' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z' fill='currentColor'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-size: contain;
width: 16px;
height: 16px;
display: inline-block;
animation: spin 1s linear infinite;
}
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.batch-share-file-icon {
width: 16px;
height: 16px;
display: inline-block;
vertical-align: middle;
margin-right: 6px;
object-fit: contain;
object-position: center;
}
.batch-share-success-icon {
background-image: url("data:image/svg+xml,%3Csvg width='16' height='16' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z' fill='%234caf50'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-size: contain;
width: 16px;
height: 16px;
display: inline-block;
}
.batch-share-error-icon {
background-image: url("data:image/svg+xml,%3Csvg width='16' height='16' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Ccircle cx='12' cy='12' r='10' fill='%23f44336'/%3E%3Cpath d='M15 9L9 15M9 9l6 6' stroke='%23fff' stroke-width='2' stroke-linecap='round'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-size: contain;
width: 16px;
height: 16px;
display: inline-block;
}
.batch-share-processing-icon {
background-image: url("data:image/svg+xml,%3Csvg width='16' height='16' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Ccircle cx='12' cy='12' r='10' fill='%23ff9800'/%3E%3Cpath d='M12 6v6l4 2' stroke='%23fff' stroke-width='2' stroke-linecap='round'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-size: contain;
width: 16px;
height: 16px;
display: inline-block;
animation: spin 1s linear infinite;
}
.batch-share-status-icon {
background-image: url("data:image/svg+xml,%3Csvg width='16' height='16' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Ccircle cx='12' cy='12' r='10' fill='%23f44336'/%3E%3Cpath d='M15 9L9 15M9 9l6 6' stroke='%23fff' stroke-width='2' stroke-linecap='round'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-size: contain;
width: 16px;
height: 16px;
display: inline-block;
color: #f44336;
}
.batch-share-status-icon.warning {
background-image: url("data:image/svg+xml,%3Csvg width='16' height='16' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M12 2L2 20h20L12 2z' fill='%23ff9800'/%3E%3Cpath d='M12 8v6M12 16h.01' stroke='%23fff' stroke-width='2' stroke-linecap='round'/%3E%3C/svg%3E");
color: #ff9800;
}`;
const style = document.createElement('style');
style.textContent = cssContent;
style.id = 'svg-icons-styles';
document.head.appendChild(style);
})();
const style = document.createElement('style');
style.textContent = `
:root {
--color-white: white;
--color-text: #333;
--color-primary: #4285f4;
--color-success: #4caf50;
--color-error: #f44336;
--color-warning: #ff9800;
--bg-overlay-light: rgba(0,0,0,0.05);
--bg-overlay-medium: rgba(0,0,0,0.1);
--bg-overlay-dark: rgba(0,0,0,0.15);
--bg-overlay-darker: rgba(0,0,0,0.2);
--bg-primary: rgba(66, 133, 244, 0.9);
}
.btn,
.window {
position: fixed;
z-index: 9998;
transition: all 0.3s ease;
}
.flex-between {
display: flex;
justify-content: space-between;
align-items: center;
}
.flex-gap {
display: flex;
gap: 8px;
}
.clear-text-btn {
position: absolute;
top: 6px;
right: 6px;
width: 24px;
height: 24px;
border-radius: 50%;
border: 1px solid #ddd;
background: #fff;
color: #888;
display: inline-flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: background 0.2s ease, color 0.2s ease, border-color 0.2s ease;
z-index: 1;
}
.clear-text-btn:hover {
background: #f5f5f5;
color: #555;
border-color: #ccc;
}
.clear-text-btn .clear-brush-svg {
pointer-events: none;
}
.btn-small {
font-size: 12px;
padding: 4px 8px;
}
.transition-all {
transition: all 0.3s ease;
}
.transition-transform {
transition: transform 0.3s ease;
}
.transition-bg {
transition: background 0.3s ease, color 0.3s ease;
}
.rounded-sm {
border-radius: 4px;
}
.rounded-lg {
border-radius: 12px;
}
.shadow-sm {
box-shadow: 0 1px 3px var(--bg-overlay-medium);
}
.shadow-md {
box-shadow: 0 2px 8px var(--bg-overlay-medium);
}
.shadow-lg {
box-shadow: 0 4px 15px var(--bg-overlay-medium);
}
.shadow-xl {
box-shadow: 0 12px 40px var(--bg-overlay-dark);
}
.batch-share-progress {
position: relative;
}
.batch-share-copy-all-btn,
.batch-share-export-btn {
display: none;
}
.batch-share-status-display {
display: none;
margin-top: 8px;
padding: 6px 12px;
background: var(--bg-primary);
color: var(--color-white);
border-radius: 4px;
font-size: 13px;
font-weight: 500;
text-align: center;
box-shadow: 0 2px 8px var(--bg-overlay-darker);
}
.progress-bar,
.progress-bar-dynamic,
#batch-receive-progress-bar,
#batch-share-progress-bar {
--progress-width: 0%;
}
.search-clear-btn {
display: none;
}
.extract-btn-progress {
background: linear-gradient(90deg, var(--color-success) var(--progress-percent, 0%), var(--bg-overlay-light) var(--progress-percent, 0%)) !important;
transition: background 0.3s ease, color 0.3s ease !important;
}
.extract-btn-progress.progress-high {
color: var(--color-white) !important;
}
.extract-btn-progress.progress-low {
color: var(--color-text) !important;
}
.status-display-text {
color: var(--color-white);
}
.batch-receive-ready {
text-align: center;
margin-top: 60px;
}
.btn {
left: 10px;
bottom: 15px;
width: 40px;
height: 40px;
border-radius: 50%;
background: #fff;
border: 1px solid #ddd;
box-shadow: 0 4px 15px rgba(0,0,0,0.1);
display: flex;
justify-content: center;
align-items: center;
cursor: move;
user-select: none;
}
.btn:hover {
box-shadow: 0 6px 20px rgba(0,0,0,0.15);
}
.window {
width: 680px;
max-width: 90vw;
background: #fff;
border-radius: 12px;
border: 1px solid #ddd;
box-shadow: 0 12px 40px rgba(0,0,0,0.15);
padding: 20px;
display: none;
overflow: hidden;
color: #333;
cursor: move;
left: 20px;
top: 20px;
z-index: 9999;
}
.window.maximized {
width: 100% !important;
height: 100vh !important;
max-width: 100% !important;
max-height: 100vh !important;
top: 0 !important;
left: 0 !important;
border-radius: 0 !important;
box-sizing: border-box !important;
overflow: hidden !important;
}
.window.maximized .modal-textarea,
.window.maximized #batch-receive-textarea {
height: calc(100vh - 320px) !important;
min-height: 200px !important;
max-height: calc(100vh - 320px) !important;
}
.window.maximized .batch-results-container {
height: calc(100vh - 320px) !important;
min-height: 450px !important;
max-height: calc(100vh - 320px) !important;
}
.window.maximized .storage-container {
height: calc(100vh - 370px) !important;
min-height: 396px !important;
max-height: calc(100vh - 370px) !important;
}
.window.maximized #batch-share-file-list-container {
height: calc(100vh - 420px) !important;
min-height: 290px !important;
max-height: calc(100vh - 420px) !important;
}
.window-header,
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
padding-bottom: 12px;
border-bottom: 1px solid #eee;
}
.window-title,
.modal-title {
margin: 0;
color: #333;
font-size: 18px;
font-weight: 600;
flex: 1;
}
.status-tag-container {
margin-right: auto;
padding: 0 10px;
}
.gear-icon {
width: 30px;
height: 30px;
color: #555;
transition: transform 0.3s ease;
}
.btn:hover .gear-icon {
transform: rotate(30deg);
color: #4285f4;
}
.form-group,
.modal-form-group {
margin-bottom: 18px;
}
.label,
.modal-label,
.concurrent-label {
display: block;
color: #666;
margin-bottom: 8px;
font-size: 12px;
font-weight: 500;
height: 16px;
line-height: 16px;
}
.modal-label {
text-align: left;
margin-bottom: 5px;
}
.input,
.select,
.modal-input,
.concurrent-input {
width: 100%;
padding: 10px 12px;
background: #fff;
border: 1px solid #ddd;
border-radius: 8px;
color: #333;
font-size: 14px;
transition: all 0.3s;
box-sizing: border-box;
box-shadow: 0 1px 2px rgba(0,0,0,0.05);
}
.modal-input {
padding: 8px 12px;
margin: 0;
}
.input:focus,
.select:focus,
.modal-input:focus {
outline: none;
border-color: #bbb;
background: #f9f9f9;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
.select {
appearance: none;
background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23555'%3e%3cpath d='M7 10l5 5 5-5z'/%3e%3c/svg%3e");
background-repeat: no-repeat;
background-position: right 12px center;
background-size: 14px;
}
.btn-primary,
.modal-btn {
width: 100%;
padding: 12px;
border: none;
border-radius: 8px;
color: white;
font-size: 15px;
font-weight: 500;
cursor: pointer;
transition: all 0.3s;
margin-bottom: 15px;
box-shadow: 0 2px 6px rgba(0,0,0,0.1);
background: #42a5f5;
}
.btn-primary:hover,
.modal-btn:hover {
background-color: #3367d6;
}
.btn-primary.stop {
background: #ff5252;
}
.storage-tab-content:not([data-tab-content="storage"]) .btn-primary,
.storage-tab-content:not([data-tab-content="storage"]) .batch-result-item-btn,
.storage-tab-content:not([data-tab-content="storage"]) .modal-btn {
background-color: #4285f4 !important;
color: white !important;
border: none;
border-radius: 4px;
padding: 8px 16px;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 80px;
height: 36px;
width: auto;
margin-bottom: 0;
box-shadow: none;
}
.storage-tab-content:not([data-tab-content="storage"]) .btn-primary:hover,
.storage-tab-content:not([data-tab-content="storage"]) .batch-result-item-btn:hover,
.storage-tab-content:not([data-tab-content="storage"]) .modal-btn:hover {
background-color: #3367d6 !important;
}
.btn-danger,
.storage-tab-content:not([data-tab-content="storage"]) .delete-btn,
.storage-tab-content:not([data-tab-content="storage"]) .stop-btn,
.storage-tab-content:not([data-tab-content="storage"]) .cancel-btn,
.storage-tab-content:not([data-tab-content="storage"]) .batch-share-cancel-btn {
background-color: #f44336 !important;
color: white !important;
border: none;
border-radius: 4px;
padding: 8px 16px;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 80px;
height: 36px;
}
.btn-danger:hover,
.storage-tab-content:not([data-tab-content="storage"]) .delete-btn:hover,
.storage-tab-content:not([data-tab-content="storage"]) .stop-btn:hover,
.storage-tab-content:not([data-tab-content="storage"]) .cancel-btn:hover,
.storage-tab-content:not([data-tab-content="storage"]) .batch-share-cancel-btn:hover {
background-color: #d32f2f !important;
}
.btn-warning,
.storage-tab-content:not([data-tab-content="storage"]) .pause-resume-btn.pause {
background-color: #ff9800 !important;
color: white !important;
border: none;
border-radius: 4px;
padding: 8px 16px;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 80px;
height: 36px;
}
.btn-warning:hover,
.storage-tab-content:not([data-tab-content="storage"]) .pause-resume-btn.pause:hover {
background-color: #f57c00 !important;
}
.btn-success,
.storage-tab-content:not([data-tab-content="storage"]) .pause-resume-btn:not(.pause),
.storage-tab-content:not([data-tab-content="storage"]) .batch-result-btn-exists,
.storage-tab-content:not([data-tab-content="storage"]) .batch-result-btn-imported {
background-color: #4caf50 !important;
color: white !important;
border: none;
border-radius: 4px;
padding: 8px 16px;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 80px;
height: 36px;
}
.btn-success:hover,
.storage-tab-content:not([data-tab-content="storage"]) .pause-resume-btn:not(.pause):hover,
.storage-tab-content:not([data-tab-content="storage"]) .batch-result-btn-exists:hover,
.storage-tab-content:not([data-tab-content="storage"]) .batch-result-btn-imported:hover {
background-color: #388e3c !important;
}
.storage-tab-content:not([data-tab-content="storage"]) .batch-result-btn-no-password {
background-color: #fbbc05 !important;
color: white !important;
border: none;
border-radius: 4px;
padding: 8px 16px;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 80px;
height: 36px;
}
.storage-tab-content:not([data-tab-content="storage"]) .batch-result-btn-no-password:hover {
background-color: #f4b400 !important;
}
.modal-btn {
padding: 8px 16px;
font-size: 14px;
margin-bottom: 0;
}
.modal-btn-primary {
background: #42a5f5;
}
.modal-btn-secondary {
background: #f5f5f5;
color: #333;
}
.status,
.batch-results-container,
.failed-items-container {
font-size: 12px;
line-height: 1.6;
max-height: 360px;
overflow-y: auto;
padding: 12px;
background: #f5f5f5;
border-radius: 8px;
border: 1px solid #eee;
margin-top: 15px;
color: #666;
display: none;
}
.batch-results-container,
.failed-items-container {
height: 360px;
margin-top: 0;
padding: 5px;
}
.status.active,
.failed-items-container.active {
display: block;
}
.status.flex {
display: flex !important;
}
.error {
color: #ea4335;
font-size: 12px;
margin-top: 6px;
padding-left: 5px;
}
.stats,
.share-info {
color: #666;
font-size: 12px;
margin-bottom: 18px;
padding: 10px;
background: #f5f5f5;
border-radius: 8px;
border: 1px solid #eee;
}
.btn-group,
.modal-footer {
display: flex;
gap: 10px;
}
.btn-group {
height: 35px;
margin-bottom: 15px;
justify-content: center;
width: 100%;
}
.btn-group .btn-primary {
flex: 1;
max-width: none;
padding: 12px;
height: auto;
line-height: normal;
min-height: 40px;
}
.modal-footer {
justify-content: flex-end;
margin-top: 20px;
}
.pause-resume-btn {
background-color: #4285f4 !important;
color: white !important;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
border: none;
border-radius: 4px;
padding: 8px 16px;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
min-width: 120px;
}
.pause-resume-btn:hover {
background-color: #3367d6 !important;
}
.pause-resume-btn.pause {
background-color: #f44336 !important;
}
.pause-resume-btn.pause:hover {
background-color: #d32f2f !important;
}
.stop-btn {
height: 40px;
display: flex;
align-items: center;
justify-content: center;
background-color: #f44336;
color: white;
border: none;
border-radius: 4px;
padding: 8px 16px;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
min-width: 120px;
}
.stop-btn:hover {
background-color: #d32f2f;
}
.status::-webkit-scrollbar,
.batch-results-container::-webkit-scrollbar,
.failed-items-container::-webkit-scrollbar {
width: 6px;
}
.status::-webkit-scrollbar-track,
.batch-results-container::-webkit-scrollbar-track,
.failed-items-container::-webkit-scrollbar-track {
background: rgba(0,0,0,0.05);
border-radius: 3px;
}
.status::-webkit-scrollbar-thumb,
.batch-results-container::-webkit-scrollbar-thumb,
.failed-items-container::-webkit-scrollbar-thumb {
background: rgba(0,0,0,0.15);
border-radius: 3px;
}
.status::-webkit-scrollbar-thumb:hover,
.batch-results-container::-webkit-scrollbar-thumb:hover,
.failed-items-container::-webkit-scrollbar-thumb:hover {
background: rgba(0,0,0,0.2);
}
.highlight {
color: #4285f4;
font-weight: 500;
}
.highlight-success {
color: #34a853;
font-weight: 500;
}
.highlight-warning {
color: #fbbc05;
font-weight: 500;
}
.disabled-input {
opacity: 0.7;
pointer-events: none;
background: #f5f5f5 !important;
}
.chars-input.disabled-digits {
background: #f5f5f5 !important;
}
.share-avatar {
flex-shrink: 0;
width: 50px;
height: 50px;
border-radius: 50%;
object-fit: cover;
border: 1px solid #ddd;
}
.form-row {
display: flex;
gap: 15px;
justify-content: center;
align-items: flex-start;
width: 100%;
}
.form-row .form-group {
flex: 1;
margin-bottom: 0;
max-width: none;
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
justify-content: flex-start;
}
.concurrent-group {
flex: 1;
display: flex;
flex-direction: column;
max-width: none;
align-items: center;
text-align: center;
justify-content: flex-start;
}
.window.maximized .form-row .form-group {
max-width: none;
min-width: 200px;
}
.window.maximized .concurrent-group {
max-width: none;
min-width: 200px;
}
.window.maximized .btn-group .btn-primary {
min-width: 200px;
}
.window.maximized .pause-resume-btn,
.window.maximized .stop-btn {
min-width: 200px;
}
.settings-section {
margin-bottom: 15px;
}
.settings-section-title {
font-size: 14px;
font-weight: 600;
color: #333;
margin-bottom: 6px;
padding: 6px 10px;
background: #f8f9fa;
border-left: 3px solid #4285f4;
border-radius: 4px;
display: flex;
justify-content: space-between;
align-items: center;
}
.category-toggle-container {
display: flex;
align-items: center;
}
.status-info-grid {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-bottom: 10px;
}
.status-info-column {
flex: 1;
min-width: 45%;
}
.status-info-item {
margin-bottom: 5px;
font-size: 13px;
}
.progress-container,
.compact-progress-bar {
width: 100%;
background: #f5f7fa;
border-radius: 6px;
margin-bottom: 8px;
overflow: hidden;
display: none;
padding: 8px;
border: 1px solid #e1e8ed;
box-sizing: border-box;
}
.progress-container .progress-bar {
height: 8px;
background: #42a5f5;
border-radius: 4px;
overflow: hidden;
border: none;
}
#batch-share-progress {
display: none;
margin-bottom: 12px;
}
#batch-share-progress.show {
display: block;
}
#batch-receive-progress {
display: none;
margin-bottom: 12px;
}
#batch-receive-progress.show {
display: block;
}
#batch-receive-result {
margin-bottom: 12px;
}
.batch-result-item-title {
display: flex;
align-items: center;
gap: 8px;
flex-wrap: nowrap;
overflow: hidden;
}
.batch-result-item-actions {
display: flex;
gap: 4px;
margin-left: auto;
flex-shrink: 0;
}
.batch-result-item-title .storage-item-btn {
width: auto;
min-width: fit-content;
padding: 4px 8px;
white-space: nowrap;
}
.batch-result-item-status {
margin-top: 4px;
}
.progress-bar,
.compact-progress-fill {
height: 100%;
background: #3b82f6;
border-radius: 4px;
transition: width 0.3s ease;
position: relative;
overflow: hidden;
}
.progress-bar::before,
.compact-progress-fill::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
animation: shimmer 1.5s infinite;
}
@keyframes shimmer {
0% {
left: -100%;
}
100% {
left: 100%;
}
}
.status-tag,
.expired-tag,
.valid-tag,
.time-limited-tag,
.auto-renewal-tag,
.error-tag {
display: inline-block;
padding: 2px 6px;
border-radius: 4px;
font-size: 12px;
font-weight: normal;
margin-left: 5px;
color: white;
text-align: center;
}
.error-tag,
.expired-tag {
background: #f44336;
}
.status-tag {
margin-bottom: 5px;
}
.status-stopped {
background: #ea4335;
}
.valid-tag,
.auto-renewal-tag {
background: #4caf50;
}
.time-limited-tag {
background: #2196f3;
}
.storage-tab-content .file-size,
.batch-receive-file-size,
.batch-recognize-file-size {
display: inline-block;
padding: 2px 6px;
background: #333333 !important;
color: white !important;
border-radius: 4px;
font-size: 12px;
margin-right: 5px;
font-family: normal;
width: auto;
min-width: fit-content;
white-space: nowrap;
}
.storage-tab-content .ed2k-tag {
display: inline-block;
padding: 2px 6px;
background: #9c27b0;
color: white;
border-radius: 4px;
font-size: 12px;
margin-right: 5px;
font-family: normal;
}
.storage-tab-content .magnet-tag {
display: inline-block;
padding: 2px 6px;
background: #349871fa;
color: white;
border-radius: 4px;
font-size: 12px;
margin-right: 5px;
font-family: normal;
}
.correct-code {
background-color: #e6f4ea;
padding: 8px 12px;
border-radius: 6px;
border: 1px solid #34a853;
margin-top: 10px;
font-weight: bold;
color: #34a853;
display: flex;
justify-content: space-between;
align-items: center;
}
.correct-code-text {
flex: 1;
}
.correct-code-actions {
display: flex;
gap: 5px;
}
.correct-code-btn {
padding: 2px 8px;
background: rgba(0,0,0,0.05);
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 12px;
}
.correct-code-btn:hover {
background: rgba(0,0,0,0.1);
}
.storage-container {
max-height: 396px;
overflow-y: auto;
margin-top: 15px;
background: #f5f5f5;
border-radius: 8px;
border: 1px solid #eee;
}
.storage-scroll-content {
position: relative;
width: 100%;
}
.storage-item {
position: absolute;
width: 100%;
box-sizing: border-box;
display: flex;
flex-direction: column;
padding: 10px;
background: #fff;
border-radius: 6px;
border: 1px solid #eee;
margin-bottom: 10px;
cursor: pointer;
transition: background-color 0.2s ease;
height: 99px;
min-height: 99px;
max-height: 99px;
overflow: hidden;
}
.batch-result-item {
position: relative;
margin-bottom: 4px;
padding: 8px;
background: #fff;
border-radius: 6px;
border: 1px solid #eee;
cursor: pointer;
transition: background-color 0.2s ease;
}
.storage-item:hover {
background: #f8f9fa;
}
.storage-item.selected {
background: #e3f2fd;
border-color: #2196f3;
}
.storage-item-header,
.batch-result-item-title {
display: flex;
justify-content: flex-start;
margin-bottom: 5px;
align-items: center;
}
.batch-result-item-title {
font-weight: bold;
color: #4285f4;
word-break: break-word;
flex: 1;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
margin-right: 10px;
cursor: pointer;
display: flex;
align-items: center;
margin-bottom: 2px;
}
.storage-item-title {
font-weight: bold;
color: #4285f4;
word-break: break-word;
flex: 1;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
margin-right: 10px;
cursor: pointer;
display: flex;
align-items: center;
}
.storage-item-actions,
.batch-result-item-actions {
display: flex;
gap: 5px;
flex-shrink: 0;
}
.batch-result-item-actions {
margin-top: 4px;
}
.btn-small,
.storage-item-btn,
.batch-result-item-btn {
padding: 4px 8px;
background: rgba(0,0,0,0.05);
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 12px;
white-space: nowrap;
height: 24px;
line-height: 16px;
transition: all 0.2s ease;
}
.btn-small:hover,
.storage-item-btn:hover,
.batch-result-item-btn:hover {
background: rgba(0,0,0,0.1);
}
.btn-small.active,
.storage-item-btn.active {
background: rgba(66,165,245,0.2);
color: #4285f4;
}
.storage-item-btn.ed2k-btn.active {
background: rgba(156,39,176,0.2);
color: #9c27b0;
}
.storage-item-btn.magnet-btn.active {
background: rgba(52,152,113,0.2);
color: #349871fa;
}
.storage-item-content {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 5px;
font-size: 13px;
word-break: break-all;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
flex: 1;
min-height: 0;
}
.batch-result-item-details {
font-size: 12px;
color: #666;
margin-bottom: 0;
}
.storage-item-note {
font-size: 12px;
color: #666;
margin-top: 3px;
display: flex;
align-items: center;
gap: 5px;
flex-shrink: 0;
min-height: 0;
}
.note-display {
display: inline-block;
flex: 1;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
vertical-align: middle;
cursor: text;
}
.storage-item-title-input,
.storage-item-note-input {
display: none;
padding: 2px 4px;
border: 1px solid #ddd;
border-radius: 3px;
font-size: 12px;
vertical-align: baseline;
}
.storage-item-title-input {
width: 300px;
font-weight: bold;
color: #4285f4;
font-size: 13px;
}
.storage-item-note-input {
flex: 1;
min-width: 0;
}
.storage-item-password-input {
display: none;
width: 40px;
padding: 2px 4px;
text-align: center;
border: 1px solid #ddd;
border-radius: 3px;
font-size: 12px;
vertical-align: baseline;
}
.storage-item-info {
display: flex;
align-items: center;
gap: 10px;
flex: 1;
min-width: 0;
white-space: nowrap;
overflow: hidden;
}
.storage-item-password {
display: flex;
align-items: center;
gap: 10px;
margin-top: 3px;
}
.storage-item-sharer {
font-size: 12px;
color: #666;
margin-right: 10px;
}
.password-display {
font-size: 12px;
color: #666;
margin-right: 10px;
}
.password-value {
font-size: 12px;
color: #666;
}
.title-value {
font-size: 13px;
color: #4285f4;
font-weight: bold;
}
.fill-error {
color: #f44336;
font-size: 12px;
margin-top: 5px;
display: none;
}
.storage-item-share-title {
font-weight: bold;
margin-bottom: 5px;
color: #333;
}
.storage-item-expire-time {
font-size: 12px;
color: #666;
margin-left: 5px;
}
.storage-item-input-hidden {
display: none !important;
}
.storage-item-input-visible {
display: inline-block !important;
}
.btn-hidden {
display: none !important;
}
.btn-visible {
display: inline-block !important;
}
.batch-recognize-btn-visible {
display: inline-block !important;
}
.batch-recognize-btn-hidden {
display: none !important;
}
.batch-recognize-container-visible {
display: block !important;
}
.batch-recognize-container-hidden {
display: none !important;
}
.batch-recognize-flex-visible {
display: flex !important;
}
.batch-recognize-flex-hidden {
display: none !important;
}
.batch-recognize-result-maximized {
height: calc(100vh - 360px) !important;
min-height: 200px !important;
}
.batch-recognize-result-dynamic {
height: 360px !important;
min-height: 200px !important;
}
.batch-receive-btn-visible {
display: inline-block !important;
}
.batch-receive-btn-hidden {
display: none !important;
}
.batch-receive-container-visible {
display: block !important;
}
.batch-receive-container-hidden {
display: none !important;
}
.batch-receive-flex-visible {
display: flex !important;
}
.batch-receive-flex-hidden {
display: none !important;
}
.batch-receive-result-maximized {
height: calc(100vh - 360px) !important;
min-height: 200px !important;
}
.batch-receive-result-dynamic {
height: 360px !important;
min-height: 200px !important;
}
.text-color-gray {
color: #666 !important;
font-size: 12px;
}
.text-color-primary {
color: #4285f4 !important;
font-size: 12px;
}
.text-color-custom {
color: var(--text-color, #4285f4) !important;
font-size: 12px;
}
.input-width-small {
width: 80px !important;
}
.input-width-medium {
width: 300px !important;
}
.input-width-large {
width: 100% !important;
max-width: 600px !important;
}
.storage-tabs {
display: flex;
border-bottom: 1px solid #eee;
margin-bottom: 15px;
}
.storage-tab {
padding: 8px 15px;
cursor: pointer;
border-bottom: 2px solid transparent;
position: relative;
display: inline-block;
}
.storage-tab.active {
border-bottom: 2px solid #4285f5;
color: #4285f4;
font-weight: bold;
}
.storage-tab-content {
display: none;
}
.storage-tab-content.active {
display: block;
}
.storage-import-export {
display: flex;
gap: 10px;
margin-top: 15px;
}
.btn-secondary,
.storage-import-export-btn {
padding: 8px 12px;
background: rgba(0,0,0,0.05);
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 13px;
text-align: center;
transition: all 0.2s ease;
display: flex;
align-items: center;
justify-content: center;
gap: 5px;
position: relative;
min-width: fit-content;
}
.storage-import-export-btn[data-text="导出结果"] {
flex: none;
width: auto;
min-width: fit-content;
padding: 8px 12px;
}
.btn-secondary:hover,
.storage-import-export-btn:hover {
background: rgba(0,0,0,0.1);
transform: translateY(-1px);
}
.storage-batch-actions {
display: none;
gap: 10px;
margin-top: 15px;
padding: 10px;
background: #f5f5f5;
border-radius: 6px;
border: 1px solid #ddd;
}
.storage-batch-actions.show {
display: flex;
}
.storage-batch-actions button {
padding: 8px 12px;
background: rgba(0,0,0,0.05);
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 13px;
text-align: center;
transition: all 0.2s ease;
display: flex;
align-items: center;
justify-content: center;
gap: 5px;
position: relative;
min-width: fit-content;
}
.storage-batch-actions button:hover {
background: rgba(0,0,0,0.1);
transform: translateY(-1px);
}
.storage-batch-actions button.delete-btn {
background: #f44336;
color: white;
}
.storage-batch-actions button.delete-btn:hover {
background: #d32f2f;
}
.storage-batch-actions .selected-count {
font-weight: bold;
color: #333;
margin-right: auto;
}
.storage-empty {
text-align: center;
padding: 20px;
color: #666;
font-size: 14px;
}
.storage-search {
display: flex;
gap: 8px;
margin-bottom: 10px;
align-items: center;
}
.storage-search-input {
flex: 1;
padding: 6px 10px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 13px;
}
.close-icon {
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' width='24' height='24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill='%23666' d='M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-size: contain;
width: 16px;
height: 16px;
display: inline-block;
transition: all 0.2s ease;
}
.search-input-container {
position: relative;
flex: 1;
display: flex;
align-items: center;
}
.search-clear-btn {
position: absolute;
right: 6px;
top: 50%;
transform: translateY(-50%);
background: none;
border: none;
cursor: pointer;
padding: 2px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s ease;
}
.search-clear-btn:hover {
background-color: rgba(0, 0, 0, 0.1);
}
.search-clear-btn:hover .close-icon {
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' width='24' height='24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill='%23333' d='M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z'/%3E%3C/svg%3E");
}
.storage-search-select {
padding: 6px 8px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 13px;
background: white;
}
.element-block-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 4px;
}
.element-block-item {
display: flex;
flex-direction: column;
padding: 4px;
background: #fff;
border-radius: 6px;
border: 1px solid #eee;
}
.element-block-item-header {
display: flex;
align-items: center;
margin-bottom: 1px;
}
.element-block-switch {
position: relative;
display: inline-block;
width: 50px;
height: 24px;
margin-right: 10px;
}
.element-block-switch input {
opacity: 0;
width: 0;
height: 0;
}
.element-block-slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #ccc;
transition: .4s;
border-radius: 24px;
}
.element-block-slider:before {
position: absolute;
content: "";
height: 16px;
width: 16px;
left: 4px;
bottom: 4px;
background-color: white;
transition: .4s;
border-radius: 50%;
}
input:checked + .element-block-slider {
background-color: #42a5f5;
}
input:checked + .element-block-slider:before {
transform: translateX(26px);
}
.element-block-name {
flex: 1;
font-size: 13px;
color: #333;
font-weight: bold;
}
.element-block-selector {
font-size: 11px;
color: #666;
word-break: break-all;
}
.modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0,0,0,0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 10000;
}
.modal-content {
background: white;
border-radius: 8px;
padding: 20px;
width: 400px;
max-width: 90%;
box-shadow: 0 5px 20px rgba(0,0,0,0.2);
box-sizing: border-box;
}
.modal-textarea {
width: 100%;
height: 150px;
padding: 8px 12px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
box-sizing: border-box;
resize: vertical;
}
.batch-result-item.success {
border-left: 4px solid #4caf50;
}
.batch-result-item.error {
border-left: 4px solid #f44336;
}
.batch-result-item.warning {
border-left: 4px solid #ff9800;
}
.failed-item {
padding: 5px;
margin-bottom: 5px;
border-bottom: 1px solid #eee;
font-size: 12px;
word-break: break-all;
}
.compact-layout {
font-size: 12px;
line-height: 1.4;
}
.compact-layout .batch-result-item-title {
margin-bottom: 1px;
font-size: 12px;
}
.compact-layout .batch-result-item-details {
font-size: 11px;
margin-bottom: 1px;
}
.compact-layout .batch-result-item-actions {
margin-top: 1px;
}
.compact-layout .batch-result-item {
margin-bottom: 2px;
padding: 6px;
}
.compact-layout .batch-result-item-btn {
padding: 1px 4px;
font-size: 11px;
}
.compact-progress {
display: flex;
align-items: center;
margin-top: 5px;
font-size: 11px;
}
.api-refresh-btn,
.delete-expired-btn,
.delete-invalid-btn {
position: relative;
}
.api-refresh-badge,
.expired-count-badge,
.invalid-count-badge,
.error-count-badge,
.import-badge,
.extract-badge {
position: absolute;
top: -6px;
right: -6px;
color: white;
border-radius: 12px;
min-width: 16px;
height: 16px;
padding: 0 4px;
font-size: 10px;
display: flex;
align-items: center;
justify-content: center;
box-sizing: border-box;
white-space: nowrap;
}
.api-refresh-badge {
background-color: #4285f4;
}
.expired-count-badge,
.invalid-count-badge,
.error-count-badge {
background-color: #f44336;
}
.import-badge {
background-color: #4caf50;
display: none;
}
.import-badge.show {
display: flex;
}
.extract-badge {
background-color: #ff9800;
display: none;
}
.extract-badge.show {
display: flex;
}
.extract-btn-progress {
transition: all 0.3s ease;
position: relative;
overflow: hidden;
}
.extract-btn-progress::before {
content: '';
position: absolute;
top: 0;
left: 0;
height: 100%;
background: linear-gradient(90deg, #4caf50, #45a049);
transition: width 0.3s ease;
z-index: 0;
}
.extract-btn-progress > * {
position: relative;
z-index: 1;
}
.expired-count-badge.large-count,
.invalid-count-badge.large-count,
.error-count-badge.large-count {
right: -12px;
padding: 0 6px;
}
.fetch-btn {
background-color: rgba(66,133,244,0.1);
color: #4285f4;
}
.fetch-btn:hover {
background-color: rgba(66,133,244,0.2);
}
.fetch-btn:active {
background-color: rgba(66,133,244,0.3);
}
.filter-buttons {
display: flex;
justify-content: space-between;
margin-bottom: 6px;
align-items: center;
}
.filter-group,
.action-group {
display: flex;
gap: 8px;
}
.sort-buttons {
display: flex;
gap: 8px;
margin-bottom: 4px;
align-items: center;
justify-content: center;
height: 36px;
min-height: 36px;
max-height: 36px;
}
.sort-buttons .batch-actions-container {
margin-left: auto;
display: flex;
align-items: center;
gap: 8px;
height: 32px;
min-height: 32px;
max-height: 32px;
overflow: visible;
flex-shrink: 0;
position: relative;
}
.sort-buttons .batch-actions-container.has-selection {
padding: 4px 8px;
background: #f5f5f5;
border-radius: 4px;
border: 1px solid #ddd;
}
.sort-buttons .selected-count {
font-size: 12px;
color: #333;
font-weight: bold;
}
.sort-buttons .selected-help-btn {
display: inline-flex;
align-items: center;
justify-content: center;
width: 20px;
height: 20px;
border-radius: 50%;
border: 1px solid #d0d0d0;
background: #fff;
cursor: pointer;
padding: 0;
color: #bdbdbd;
}
.sort-buttons .selected-help-btn:hover {
background: #f4f4f4;
color: #8a8a8a;
}
.sort-buttons .multi-select-hint {
position: absolute;
right: 8px;
top: calc(100% + 6px);
background: #fffef7;
border: 1px solid #f0e6c8;
box-shadow: 0 2px 8px rgba(0,0,0,0.12);
border-radius: 6px;
padding: 8px 10px;
color: #5c4b00;
font-size: 12px;
line-height: 1.5;
z-index: 1000;
display: none;
min-width: 360px;
max-width: 560px;
white-space: normal;
pointer-events: auto;
}
.sort-buttons .batch-actions-container .batch-result-item-btn,
.sort-buttons .batch-actions-container .storage-item-btn {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 4px 8px;
border: none;
border-radius: 3px;
cursor: pointer;
font-size: 12px;
font-weight: 500;
transition: all 0.2s ease;
height: 24px;
width: auto;
}
.sort-buttons .batch-actions-container .copy-btn {
background-color: #4285f4;
color: white;
}
.sort-buttons .batch-actions-container .copy-btn:hover {
background-color: #3367d6;
}
.sort-buttons .batch-actions-container .delete-btn {
background-color: #f44336;
color: white;
}
.sort-buttons .batch-actions-container .delete-btn:hover {
background-color: #d32f2f;
}
.sort-buttons .batch-actions-container .cancel-btn {
background-color: #ff9800;
color: white;
}
.sort-buttons .batch-actions-container .cancel-btn:hover {
background-color: #f57c00;
}
.btn-icon {
background: none;
border: none;
color: #666;
cursor: pointer;
opacity: 0.7;
transition: all 0.2s ease;
padding: 0;
width: 30px;
height: 30px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
position: relative;
}
.btn-icon:hover {
opacity: 1;
background-color: rgba(0,0,0,0.05);
}
.ed2k-btn {
background-color: rgba(156,39,176,0.1);
color: #9c27b0;
}
.ed2k-btn:hover {
background-color: rgba(156,39,176,0.2);
}
.ed2k-btn:active {
background-color: rgba(156,39,176,0.3);
}
.magnet-btn {
background-color: rgba(52,152,113,0.1);
color: #349871fa;
}
.magnet-btn:hover {
background-color: rgba(52,152,113,0.2);
}
.magnet-btn:active {
background-color: rgba(52,152,113,0.3);
}
.storage-item-ed2k-input,
.storage-item-magnet-input {
display: none;
width: 100%;
max-width: 600px;
padding: 2px 5px;
margin-left: 5px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 12px;
box-sizing: border-box;
}
.window-close,
.window-maximize {
background: none;
border: none;
color: #666;
cursor: pointer;
opacity: 0.7;
transition: all 0.2s ease;
padding: 0;
width: 30px;
height: 30px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
position: relative;
}
.window-close:hover,
.window-maximize:hover {
opacity: 1;
background-color: rgba(0,0,0,0.05);
}
.window-close::before,
.window-maximize::before {
content: "";
display: block;
width: 16px;
height: 16px;
background-repeat: no-repeat;
background-position: center;
background-size: contain;
position: absolute;
transition: transform 0.3s cubic-bezier(.4,2,.6,1);
}
.window-close::before {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='18' y1='6' x2='6' y2='18'%3E%3C/line%3E%3Cline x1='6' y1='6' x2='18' y2='18'%3E%3C/line%3E%3C/svg%3E");
}
.window-maximize::before {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='4' y='4' width='16' height='16' rx='2' ry='2'%3E%3C/rect%3E%3C/svg%3E");
}
.window.maximized .window-maximize::before {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M8 3v3a2 2 0 0 1-2 2H3m18 0h-3a2 2 0 0 1-2-2V3m0 18v-3a2 2 0 0 1 2-2h3M3 16h3a2 2 0 0 1 2 2v3'%3E%3C/path%3E%3C/svg%3E");
}
.window-close:hover::before,
.window-maximize:hover::before {
transform: rotate(90deg);
}
.window-title {
margin: 0;
color: #333;
font-size: 16px;
font-weight: 600;
flex: 1;
display: flex;
align-items: center;
gap: 6px;
}
.pro-tag {
background: linear-gradient(135deg, #f0f0f0 0%, #e0e0e0 50%, #c9c9c9 100%);
color: #5d5d5d;
border: 1px solid #c9c9c9;
box-shadow: inset 0 1px 1px rgba(255,255,255,0.2);
font-size: 9px;
font-weight: bold;
padding: 1px 6px;
border-radius: 3px;
text-transform: uppercase;
cursor: pointer;
transition: all 0.2s ease;
}
.pro-tag.golden {
background: linear-gradient(135deg, #fff4c1 0%, #ffd56b 50%, #ffb84c 100%);
color: #8b4513;
border: 1px solid #daa520;
box-shadow: inset 0 1px 1px rgba(255,255,255,0.3), 0 1px 3px rgba(0,0,0,0.2);
}
.pro-tag:hover {
background: linear-gradient(135deg, #e8e8e8 0%, #d8d8d8 50%, #c1c1c1 100%);
box-shadow: inset 0 1px 1px rgba(255,255,255,0.3);
}
.pro-tag.golden:hover {
background: linear-gradient(135deg, #ffe89a 0%, #ffc94c 50%, #ffa629 100%);
box-shadow: inset 0 1px 1px rgba(255,255,255,0.4), 0 2px 4px rgba(0,0,0,0.3);
}
.pro-tag-input {
display: none;
background: linear-gradient(135deg, #f0f0f0 0%, #e0e0e0 50%, #c9c9c9 100%);
color: #5d5d5d;
border: 1px solid #c9c9c9;
box-shadow: inset 0 1px 1px rgba(255,255,255,0.2);
font-size: 9px;
font-weight: bold;
padding: 1px 6px;
border-radius: 3px;
text-transform: uppercase;
width: 120px;
min-width: 80px;
max-width: 200px;
box-sizing: border-box;
}
.pro-tag-input:focus {
outline: none;
border-color: #4285f4;
box-shadow: 0 0 0 2px rgba(66, 133, 244, 0.2);
}
.open-tab-icon {
display: inline-block;
width: 14px;
height: 14px;
position: absolute;
right: 0;
top: 2px;
cursor: pointer;
background-color: transparent;
vertical-align: middle;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6'/%3E%3Cpath d='M15 3h6v6'/%3E%3Cpath d='M10 14L21 3'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: center;
background-size: contain;
}
.copy-btn.copied {
background: #e6f4ea !important;
color: #34a853 !important;
}
.delete-error-btn,
.delete-expired-btn,
.delete-invalid-btn {
display: none;
position: relative;
}
.storage-item-title-input,
.storage-item-ed2k-input {
display: none;
}
.share-info {
display: flex;
align-items: center;
gap: 15px;
}
.share-avatar {
flex-shrink: 0;
}
.share-text {
flex: 1;
}
.network-status {
margin-bottom: 12px;
}
.network-warning {
color: red;
margin-top: 5px;
}
.progress-bar-dynamic {
width: var(--progress-width, 0%);
}
.status-info-grid {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.status-info-column {
flex: 1;
min-width: 45%;
}
.batch-complete-message {
color: #fbbc05;
margin-top: 10px;
border-top: 1px solid rgba(0,0,0,0.08);
padding-top: 10px;
}
.modal-content-wide {
width: 700px;
max-height: 90vh;
display: flex;
flex-direction: column;
}
.modal-content-extra-wide {
width: 800px;
}
.modal-scrollable {
overflow-y: auto;
flex: 1;
padding: 15px;
}
.modal-form-row {
display: flex;
gap: 10px;
}
.modal-form-column {
flex: 1;
}
.progress-container-margin {
margin-top: 15px;
margin-bottom: 4px;
display: none;
}
.progress-header {
display: flex;
justify-content: space-between;
margin-bottom: 8px;
align-items: center;
}
.progress-info {
font-size: 13px;
color: #3b82f6;
font-weight: 500;
}
.progress-bar-container {
display: flex;
justify-content: space-between;
margin-bottom: 8px;
align-items: center;
}
.progress-footer {
display: flex;
justify-content: space-between;
margin-top: 5px;
}
.results-container-margin {
margin-top: 15px;
display: none;
}
.summary-container-margin {
margin-top: 20px;
display: none;
}
.summary-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
}
.summary-spacer {
flex: 1;
}
.export-results-btn {
background-color: #2196F3;
color: white;
}
.summary-content {
padding: 10px;
background-color: #f5f5f5;
border-radius: 4px;
}
.batch-result-btn-success {
background-color: #4caf50;
color: white;
}
.batch-result-btn-error {
background-color: #f44336;
color: white;
}
.batch-result-btn-warning {
background-color: #ff9800;
color: white;
}
.batch-result-btn-network {
background-color: #2196F3;
color: white;
}
.batch-result-btn-skipped {
background-color: #ff9800;
color: white;
}
.batch-result-btn-verified {
background-color: #4caf50;
color: white;
}
.batch-result-btn-unverified {
background-color: #ff9800;
color: white;
}
.no-recognition-message {
padding: 10px;
text-align: center;
color: #666;
}
.import-content {
padding: 20px;
}
.import-info {
padding: 15px;
margin-bottom: 15px;
background: #f5f5f5;
border-radius: 4px;
}
.import-info-header {
margin-bottom: 10px;
}
.import-info-list {
margin: 0;
padding-left: 20px;
}
.import-warning {
margin-top: 10px;
color: #ff5722;
}
.import-progress-container {
margin-top: 10px;
margin-bottom: 4px;
background: #f5f7fa;
border-radius: 6px;
padding: 8px;
border: 1px solid #e1e8ed;
}
.import-remaining-time {
margin-top: 5px;
font-size: 12px;
color: #666;
}
.import-failed-items {
display: none;
margin-top: 10px;
}
.import-complete-btn {
display: none;
}
.batch-result-btn-exists {
background-color: #ff9800;
color: white;
}
.batch-result-btn-no-password {
background-color: #f44336;
color: white;
}
.batch-result-btn-imported {
background-color: #4caf50;
color: white;
}
.element-block-item-header-relative {
position: relative;
}
.status-text {
color: var(--status-color, #4285f4);
}
.batch-recognize-input-container {
margin-bottom: 12px;
position: relative;
}
.batch-recognize-textarea,
#batch-receive-textarea {
height: 350px;
width: 100%;
}
#batch-receive-input-container #batch-receive-textarea {
padding-right: 40px;
}
#batch-recognize-input-container #batch-recognize-textarea {
padding-right: 40px;
}
.batch-recognize-controls {
display: flex;
gap: 16px;
align-items: center;
margin-bottom: 12px;
}
.batch-recognize-controls-main {
flex: 1;
}
.batch-recognize-controls-row {
display: flex;
align-items: center;
gap: 16px;
}
.batch-recognize-setting-item,
.batch-receive-setting-item,
.batch-share-switch-group {
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
}
.batch-recognize-setting-item .label,
.batch-receive-setting-item .label,
.batch-share-switch-group .label {
font-size: 12px;
color: #666;
text-align: center;
margin: 0;
display: block;
}
.batch-size-select,
.verify-method-select {
width: 140px;
}
.batch-recognize-start-btn,
.batch-receive-start-btn {
width: 100px;
min-width: 100px;
margin-top: 22px;
margin-left: auto;
}
.batch-recognize-progress {
margin-bottom: 12px;
background: #f5f7fa;
border-radius: 6px;
padding: 8px;
border: 1px solid #e1e8ed;
}
.batch-recognize-ready-message {
text-align: center;
color: #666;
padding: 20px;
font-size: 14px;
}
.batch-recognize-file-size {
font-size: 12px;
color: #666;
margin-right: 8px;
flex-shrink: 0;
}
.batch-recognize-file-name,
.batch-receive-file-name {
font-weight: 500;
color: #333;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
flex: 1;
min-width: 0;
}
.batch-size-input {
width: 80px;
text-align: center;
}
.verify-method-select {
text-align: center;
}
.batch-recognize-back-btn {
display: none;
}
.batch-recognize-progress-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.batch-recognize-progress-btn {
display: none;
margin: 0;
width: 100px;
min-width: 100px;
}
.batch-recognize-progress-bar-container {
display: flex;
justify-content: space-between;
align-items: center;
}
.batch-recognize-export-btn {
display: none;
}
.batch-result-item-title {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
}
.batch-result-item-title > div {
display: flex;
align-items: center;
min-width: 0;
}
.batch-result-item-actions {
display: flex;
gap: 4px;
flex-shrink: 0;
}
.batch-result-item-actions .storage-item-btn {
width: auto;
min-width: fit-content;
}
.batch-result-item-details {
display: flex;
align-items: center;
justify-content: space-between;
}
.batch-result-link {
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
color: #2196f3;
cursor: pointer;
transition: color 0.2s ease;
}
.batch-result-link:hover {
color: #4caf50;
}
.batch-result-status {
font-size: 12px;
flex-shrink: 0;
margin-left: 8px;
padding: 2px 6px;
border-radius: 3px;
}
.batch-result-status.success {
background: #e8f5e8;
color: #2e7d32;
}
.batch-result-status.warning {
background: #fff3e0;
color: #ef6c00;
}
.batch-result-status.error {
background: #ffebee;
color: #c62828;
}
.batch-recognize-file-name,
.batch-receive-file-name {
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.batch-recognize-progress-status {
display: inline-flex;
align-items: center;
font-size: 12px;
margin-right: 16px;
background: #f8f9fa;
border-radius: 6px;
padding: 4px 10px;
border: 1px solid #e9ecef;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
}
.batch-recognize-progress-status .status-label {
color: #6c757d;
font-weight: 500;
}
.batch-recognize-progress-status .status-separator {
color: #dee2e6;
margin: 0 6px;
font-weight: bold;
}
.batch-recognize-progress-status .status-progress {
color: #007bff;
font-weight: 500;
}
.batch-recognize-progress-status .status-success {
color: #28a745;
font-weight: 500;
}
.batch-recognize-progress-status .status-failed {
color: #dc3545;
font-weight: 500;
}
.batch-recognize-progress-status .status-skipped {
color: #ffc107;
font-weight: 500;
}
.batch-recognize-file-size,
.batch-receive-file-size {
background: #e3f2fd;
color: #1976d2;
padding: 2px 6px;
border-radius: 3px;
font-size: 11px;
margin-right: 8px;
}
.batch-recognize-ready {
text-align: center;
color: #666;
margin-top: 60px;
}
.batch-recognize-status-display {
display: inline-flex;
align-items: center;
font-size: 12px;
background: #f8f9fa;
border-radius: 6px;
padding: 4px 10px;
border: 1px solid #e9ecef;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
}
.batch-recognize-status-display .status-label {
color: #6c757d;
font-weight: 500;
}
.batch-recognize-status-display .status-separator {
color: #dee2e6;
margin: 0 6px;
font-weight: bold;
}
.batch-recognize-status-display .status-success {
color: #28a745;
font-weight: 500;
}
.batch-recognize-status-display .status-warning {
color: #ffc107;
font-weight: 500;
}
.batch-recognize-status-display .status-error {
color: #dc3545;
font-weight: 500;
}
.batch-mode-switch {
display: flex;
gap: 2px;
margin-bottom: 16px;
background: #f5f5f5;
border-radius: 6px;
padding: 2px;
}
.mode-switch-btn {
flex: 1;
padding: 8px 16px;
border: none;
background: transparent;
color: #666;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
font-weight: 500;
transition: all 0.2s ease;
}
.mode-switch-btn.active {
background: #fff;
color: #333;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.mode-switch-btn:hover:not(.active) {
background: rgba(255, 255, 255, 0.5);
color: #333;
}
.batch-mode-content {
display: none;
}
.batch-mode-content.active {
display: block;
}
.batch-recognize-status {
margin-top: 12px;
}
.batch-recognize-status-header {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
}
.batch-recognize-export-btn {
display: none;
width: 80px;
text-align: center;
flex: none;
}
.batch-result-link {
color: #4285f4;
cursor: pointer;
margin-left: 8px;
}
.batch-result-link:hover {
color: #4caf50;
}
.batch-result-link.copied {
color: #4caf50;
}
.batch-receive-file-name {
margin-right: 8px;
}
.batch-receive-ready-message {
text-align: center;
color: #666;
margin-top: 60px;
}
.batch-share-flex-row {
display: flex;
flex-direction: row;
gap: 4px;
align-items: center;
justify-content: center;
margin-bottom: 4px;
flex-wrap: nowrap;
width: 100%;
}
.batch-share-flex-column {
display: flex;
flex-direction: column;
align-items: center;
min-width: 70px;
flex: 1;
}
.batch-share-label {
margin-bottom: 1px;
cursor: pointer;
text-align: center;
}
.batch-share-input {
width: 100%;
min-width: 60px;
text-align: center;
}
.batch-share-checkbox {
width: 100%;
height: 100%;
opacity: 0.01;
position: absolute;
left: 0;
top: 0;
z-index: 2;
pointer-events: auto;
}
.batch-share-btn {
width: auto;
white-space: nowrap;
}
.batch-share-cancel-btn {
display: none;
}
.batch-share-file-list {
margin: 8px 0;
display: none;
}
.batch-share-file-count {
color: #666;
font-size: 12px;
}
.batch-share-file-list-container {
max-height: 290px;
overflow-y: auto;
transition: max-height 0.3s ease;
}
#batch-receive-input-container {
margin-bottom: 12px;
position: relative;
}
.batch-receive-controls {
display: flex;
gap: 16px;
align-items: center;
margin-bottom: 12px;
}
.batch-receive-controls-main {
flex: 1;
}
.batch-receive-controls-row {
display: flex;
align-items: center;
gap: 16px;
}
.batch-receive-cid-controls {
display: flex;
gap: 8px;
align-items: center;
}
#batch-receive-cid {
width: 120px;
text-align: center;
}
#batch-receive-cid-select {
width: auto;
}
#batch-receive-progress {
display: none;
margin-bottom: 8px;
}
#batch-receive-progress-bar {
width: 0%;
}
#batch-receive-status {
display: none;
}
.batch-receive-status-header {
display: flex;
align-items: center;
}
#batch-receive-export-btn {
display: none;
margin-left: auto;
}
#batch-receive-back-btn {
display: none;
}
.batch-result-item-btn {
flex-shrink: 0;
white-space: nowrap;
}
.batch-share-file-item {
display: flex;
align-items: center;
padding: 8px;
border-bottom: 1px solid #eee;
background: #fff;
}
.batch-share-file-item:last-child {
border-bottom: none;
}
.batch-share-file-icon {
width: 24px;
height: 24px;
margin-right: 8px;
flex-shrink: 0;
white-space: nowrap;
object-fit: contain;
object-position: center;
}
.batch-share-file-info {
flex: 1;
min-width: 0;
}
.batch-share-file-name {
font-weight: bold;
color: #4285f4;
word-break: break-word;
flex: 1;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
margin-right: 10px;
cursor: pointer;
display: flex;
align-items: center;
}
.batch-share-actions-container {
display: flex;
gap: 4px;
margin-left: auto;
}
.batch-share-action-btn {
width: auto;
min-width: fit-content;
}
.batch-share-status-success {
margin-left: auto;
font-size: 12px;
}
.batch-share-status-text {
--status-color: #4285f4;
color: var(--status-color);
}
.batch-share-details-container {
display: flex;
align-items: center;
}
.batch-share-export-btn {
margin-left: auto;
}
.batch-share-copy-all-btn {
background: #4caf50;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
transition: background-color 0.2s;
}
.batch-share-copy-all-btn:hover {
background: #45a049;
}
.batch-share-copy-all-btn.copied {
background: #2196f3;
}
.batch-share-progress-actions {
z-index: 10;
}
.batch-share-btn-disabled {
opacity: 0.5 !important;
cursor: not-allowed !important;
}
.batch-share-btn-enabled {
opacity: 1 !important;
cursor: text !important;
}
.batch-share-progress-hidden {
display: none !important;
}
.batch-share-progress-visible {
display: block !important;
}
.batch-share-btn-hidden {
display: none !important;
}
.batch-share-btn-visible {
display: inline-block !important;
}
.batch-share-flex-row-maximized {
width: 100% !important;
}
.batch-share-file-list-maximized {
max-height: calc(100vh - 400px) !important;
height: calc(100vh - 400px) !important;
}
.batch-share-file-list-normal {
max-height: 290px !important;
height: auto !important;
}
.batch-share-file-status {
margin-left: 8px;
padding: 2px 6px;
border-radius: 3px;
font-size: 11px;
font-weight: 500;
}
.batch-share-file-status.pending {
background: #f5f5f5;
color: #666;
}
.batch-share-file-status.processing {
background: #e3f2fd;
color: #1976d2;
}
.batch-share-file-status.success {
background: #e8f5e8;
color: #2e7d32;
}
.batch-share-file-status.error {
background: #ffebee;
color: #c62828;
}
.batch-share-file-status.skipped {
background: #fff3e0;
color: #ef6c00;
}
.batch-share-result-item {
display: flex;
align-items: center;
padding: 8px;
border-bottom: 1px solid #eee;
background: #fff;
}
.batch-share-result-item:last-child {
border-bottom: none;
}
.batch-share-result-icon {
width: 24px;
height: 24px;
margin-right: 8px;
flex-shrink: 0;
}
.batch-share-result-info {
flex: 1;
min-width: 0;
}
.batch-share-result-name {
font-weight: 500;
color: #333;
margin-bottom: 2px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.batch-share-result-details {
font-size: 12px;
color: #666;
margin-bottom: 2px;
}
.batch-share-result-link {
font-size: 12px;
color: #4285f4;
word-break: break-all;
cursor: pointer;
}
.batch-share-result-link:hover {
color: #4caf50;
}
.batch-share-result-actions {
margin-left: 8px;
display: flex;
gap: 4px;
}
.batch-share-result-btn {
padding: 2px 6px;
background: rgba(0,0,0,0.05);
border: none;
border-radius: 3px;
cursor: pointer;
font-size: 11px;
white-space: nowrap;
}
.batch-share-result-btn:hover {
background: rgba(0,0,0,0.1);
}
.batch-share-result-btn.copy-btn {
background-color: rgba(66,133,244,0.1);
color: #4285f4;
}
.batch-share-result-btn.copy-btn:hover {
background-color: rgba(66,133,244,0.2);
}
.batch-share-result-btn.open-btn {
background-color: rgba(76,175,80,0.1);
color: #4caf50;
}
.batch-share-result-btn.open-btn:hover {
background-color: rgba(76,175,80,0.2);
}
.batch-share-switches-container {
display: flex;
gap: 16px;
align-items: center;
}
.batch-share-buttons-container {
display: flex;
gap: 4px;
align-items: center;
margin-top: 12px;
}
.batch-share-file-list-header {
display: flex;
justify-content: flex-start;
align-items: center;
margin-bottom: 4px;
gap: 8px;
}
.storage-item-title,
.batch-share-file-list-title {
font-weight: bold;
color: #4285f4;
}
.batch-share-status-header {
display: flex;
justify-content: space-between;
align-items: center;
height: 10px;
line-height: 10px;
}
.network-disconnect-warning {
color: #F44336;
margin-top: 5px;
}
.virtual-scroll-item {
position: absolute;
width: 100%;
height: 99px;
box-sizing: border-box;
}
.batch-share-error-icon {
color: #f44336;
width: 16px;
height: 16px;
display: inline-block;
}
.batch-share-error-msg {
color: #f44336;
font-size: 11px;
}
.batch-share-file-type {
display: inline-block;
padding: 2px 6px;
background: #333333;
color: white;
border-radius: 4px;
font-size: 12px;
margin-right: 8px;
font-family: normal;
white-space: nowrap;
}
.batch-share-processing-icon {
color: #ff9800;
width: 16px;
height: 16px;
display: inline-block;
}
.element-block-switch-clickable {
cursor: pointer;
}
.element-block-switch input {
opacity: 0;
width: 0;
height: 0;
position: absolute;
z-index: 1;
}
.element-block-switch .element-block-slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #ccc;
transition: .4s;
border-radius: 24px;
z-index: 0;
}
.network-reconnect-message {
color: #4CAF50;
margin-top: 5px;
}
.batch-input-textarea {
height: 200px;
}
.batch-share-layout-container {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 4px;
}
.batch-share-status-preparing {
color: #ffa726;
font-size: 12px;
background-image: url("data:image/svg+xml,%3Csvg width='12' height='12' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M10 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2h-8l-2-2z' fill='%23ffa726'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: left center;
background-size: 12px;
padding-left: 16px;
display: inline-block;
font-weight: bold;
text-align: center;
}
`;
document.head.appendChild(style);
(function() {
'use strict';
window.addEventListener('online', () => {
if (statusDiv) {
statusDiv.innerHTML += '<div class="network-reconnect-message">网络连接已恢复</div>';
}
});
window.addEventListener('offline', () => {
if (statusDiv) {
statusDiv.innerHTML += '<div class="network-disconnect-warning">网络连接已断开,验证可能失败</div>';
}
if (isRunning && !isPaused) {
togglePauseResume();
}
});
function shouldInjectButton() {
const html115 = document.querySelector('html.layout-frame.bd-core.layout-allscreen');
if (html115 && html115.querySelector('head meta[charset="UTF-8"]')) {
return true;
}
const html115cdn = document.querySelector('html.layout-frame');
if (html115cdn && html115cdn.querySelector('head meta[charset="UTF-8"]')) {
return true;
}
const html115cdnAlt = document.querySelector('html');
if (html115cdnAlt && html115cdnAlt.querySelector('head meta[charset="UTF-8"]') &&
html115cdnAlt.querySelector('head meta[name="viewport"][content*="viewport-fit=cover"]')) {
return true;
}
return false;
}
if (!shouldInjectButton()) {
return;
}
const DEFAULT_CHARS = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
let allChars = DEFAULT_CHARS;
let shareInfo = {
shareCode: '',
userId: '',
face: '',
isAccessible: false,
shareTitle: '',
expireTime: -1,
fileSize: 0,
autoRenewal: '0'
};
let selectedItems = new Set();
let lastSelectedIndex = -1;
let isMultiSelectMode = false;
const elementBlockItems = [
{ selector: '.ceiling-feature#js_common_act-enter', name: '顶部活动广告', enabled: true, category: '通用' },
{ selector: '.ceiling-link-temp', name: '顶部净网2025', enabled: true, category: '通用' },
{ selector: '.feature-float#js_common_mini-dialog', name: '右下角浮动广告', enabled: true, category: '通用' },
{ selector: '.sharing-banner#js_common_sharing_banner2', name: '分享页横幅广告', enabled: true, category: '分享页' },
{ selector: '.sharing-banner#js_common_sharing_banner', name: '横幅广告', enabled: true, category: '分享页' },
{ selector: '.promptbar-caution', name: '底部警告提示栏', enabled: true, category: '分享页' },
{ selector: 'li:has(.ibc-notice)', name: '我聊', enabled: false, category: '导航' },
{ selector: 'li:has(.ibc-service)', name: '帮助', enabled: true, category: '导航' },
{ selector: 'li:has(.ibc-app)', name: '客户端下载', enabled: true, category: '导航' },
{ selector: '.ceiling-vip-door', name: '购买VIP', enabled: true, category: '导航' }
];
function initElementBlockSettings() {
const savedSettings = GM_getValue('elementBlockSettings');
if (savedSettings) {
try {
const parsedSettings = JSON.parse(savedSettings);
parsedSettings.forEach(savedItem => {
const item = elementBlockItems.find(i => i.selector === savedItem.selector);
if (item) item.enabled = savedItem.enabled;
});
} catch (e) {
console.error('解析元素屏蔽设置失败:', e);
}
}
}
function saveElementBlockSettings() {
GM_setValue('elementBlockSettings', JSON.stringify(elementBlockItems));
}
function executeElementBlock() {
elementBlockItems.forEach(item => {
if (item.enabled) {
document.querySelectorAll(item.selector).forEach(el => el.remove());
}
});
}
function formatFileSize(bytes) {
if (bytes === 0 || bytes === undefined) return '0B';
const units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'];
const digitGroups = Math.floor(Math.log10(bytes) / Math.log10(1024));
const size = bytes / Math.pow(1024, digitGroups);
if (digitGroups >= 4) {
return size.toFixed(2) + units[digitGroups];
} else if (digitGroups >= 2) {
return size.toFixed(2) + units[digitGroups];
} else {
return Math.round(size) + units[digitGroups];
}
}
const floatingBtn = document.createElement('div');
floatingBtn.className = 'btn';
floatingBtn.innerHTML = `<div class="gear-icon"></div>`;
document.body.appendChild(floatingBtn);
const badge = document.createElement('span');
badge.className = 'floating-badge';
badge.style.cssText = 'position:absolute;top:-6px;right:-6px;min-width:18px;height:18px;background:#4285f4;color:#fff;border-radius:9px;font-size:12px;line-height:18px;text-align:center;display:none;z-index:10001;padding:0 4px;pointer-events:none;box-shadow:0 1px 4px rgba(0,0,0,0.15);font-weight:bold;';
floatingBtn.style.position = 'fixed';
floatingBtn.appendChild(badge);
function updateFloatingBadgeAndGear() {
let count = 0;
try {
const iframe = document.querySelector('iframe');
const iframeWindow = iframe?.contentWindow || unsafeWindow;
const selectDOM = iframeWindow?.document?.querySelectorAll('div.list-contents > ul li.selected');
count = selectDOM ? selectDOM.length : 0;
} catch(e) { count = 0; }
if (count > 0) {
badge.textContent = count;
badge.style.display = 'block';
floatingBtn.querySelector('.gear-icon').classList.add('active');
} else {
badge.style.display = 'none';
floatingBtn.querySelector('.gear-icon').classList.remove('active');
}
}
setInterval(updateFloatingBadgeAndGear, 500);
floatingBtn.addEventListener('click', (e) => {
if (e.defaultPrevented || e.target !== floatingBtn) return;
let count = 0;
try {
const iframe = document.querySelector('iframe');
const iframeWindow = iframe?.contentWindow || unsafeWindow;
const selectDOM = iframeWindow?.document?.querySelectorAll('div.list-contents > ul li.selected');
count = selectDOM ? selectDOM.length : 0;
} catch(e) { count = 0; }
if (count > 0) {
windowElement.style.display = 'block';
document.querySelectorAll('.storage-tab').forEach(tab => tab.classList.remove('active'));
document.querySelectorAll('.storage-tab-content').forEach(tab => tab.classList.remove('active'));
const batchTab = document.querySelector('.storage-tab[data-tab="batchshare"]');
const batchContent = document.querySelector('.storage-tab-content[data-tab-content="batchshare"]');
if (batchTab && batchContent) {
batchTab.classList.add('active');
batchContent.classList.add('active');
if (typeof renderBatchSharePage === 'function') renderBatchSharePage();
}
} else {
windowElement.style.display = 'block';
document.querySelectorAll('.storage-tab').forEach(tab => tab.classList.remove('active'));
document.querySelectorAll('.storage-tab-content').forEach(tab => tab.classList.remove('active'));
const storageTab = document.querySelector('.storage-tab[data-tab="storage"]');
const storageContent = document.querySelector('.storage-tab-content[data-tab-content="storage"]');
if (storageTab && storageContent) {
storageTab.classList.add('active');
storageContent.classList.add('active');
}
}
});
const windowElement = document.createElement('div');
windowElement.className = 'window';
windowElement.style.position = 'fixed';
windowElement.style.left = '0px';
windowElement.style.top = '0px';
windowElement.style.transform = 'none';
windowElement.innerHTML = `
<div class="window-header">
<h3 class="window-title">访问码验证助手<span class="pro-tag" title="双击编辑">GreasyFork:[email protected]</span><input type="text" class="pro-tag-input" value="GreasyFork:[email protected]" maxlength="50"></h3>
<div class="status-tag-container"></div>
<button class="window-maximize"></button>
<button class="window-close" title="关闭"></button>
</div>
<div class="storage-tabs">
<div class="storage-tab active" data-tab="storage">存储管理</div>
<div class="storage-tab" data-tab="batchreceive">批量接收</div>
<div class="storage-tab" data-tab="batchrecognize">批量识别</div>
<div class="storage-tab" data-tab="batchshare">批量分享</div>
<div class="storage-tab" data-tab="verify">验证访问码</div>
<div class="storage-tab" data-tab="elementblock">元素屏蔽</div>
<div class="storage-tab" data-tab="settings">附加功能</div>
</div>
<div class="storage-tab-content active" data-tab-content="storage">
<div class="storage-search">
<select class="storage-search-select" id="search-type">
<option value="all">全部</option>
<option value="title">标题</option>
<option value="ed2k">ED2K</option>
<option value="magnet">磁力链</option>
<option value="shareCode">分享码</option>
<option value="password">访问码</option>
<option value="note">备注</option>
</select>
<div class="search-input-container">
<input type="text" class="storage-search-input" id="search-input" placeholder="搜索...">
<button class="search-clear-btn" id="search-clear-btn">
<div class="close-icon"></div>
</button>
</div>
</div>
<div class="filter-buttons">
<div class="filter-group">
<button class="storage-item-btn active" data-filter="all">全部</button>
<button class="storage-item-btn" data-filter="valid">有效</button>
<button class="storage-item-btn" data-filter="longterm">长期</button>
<button class="storage-item-btn" data-filter="renewal">续期</button>
<button class="storage-item-btn" data-filter="timelimited">限时</button>
<button class="storage-item-btn" data-filter="error">错误</button>
<button class="storage-item-btn" data-filter="expired">已过期</button>
<button class="storage-item-btn" data-filter="cancelled">已取消</button>
<button class="storage-item-btn ed2k-btn" data-filter="ed2k">ED2K</button>
<button class="storage-item-btn magnet-btn" data-filter="magnet">磁力链</button>
</div>
<div class="action-group">
<button class="storage-item-btn delete-error-btn" id="delete-error-btn">删除错误</button>
<button class="storage-item-btn delete-expired-btn" id="delete-expired-btn">删除过期</button>
<button class="storage-item-btn delete-invalid-btn" id="delete-invalid-btn">删除无效</button>
<button class="storage-item-btn api-refresh-btn fetch-btn" id="api-refresh-btn" title="API刷新">
<div class="refresh-icon"></div>
<span class="api-refresh-badge">0</span>
</button>
<button class="storage-item-btn refresh-btn" title="刷新所有时间和信息">
<div class="refresh-icon"></div>
</button>
</div>
</div>
<div class="sort-buttons">
<button class="storage-item-btn" data-sort="time-desc">最近添加</button>
<button class="storage-item-btn" data-sort="time-asc">最早添加</button>
<button class="storage-item-btn" data-sort="name-asc">名称A-Z</button>
<button class="storage-item-btn" data-sort="name-desc">名称Z-A</button>
<button class="storage-item-btn" data-sort="size-desc">大小降序</button>
<button class="storage-item-btn" data-sort="size-asc">大小升序</button>
<div class="batch-actions-container">
<span class="selected-count">已选 0 项</span>
<button class="selected-help-btn" title="多选与快捷键说明" aria-label="选择帮助" style="display: inline-flex;">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="2" fill="white"/>
<line x1="12" y1="7" x2="12" y2="13" stroke="currentColor" stroke-width="2"/>
<circle cx="12" cy="17" r="1.6" fill="currentColor"/>
</svg>
</button>
<div class="multi-select-hint">
<div style="font-weight:600;margin-bottom:6px;">多选与快捷键</div>
<ul style="margin:0;padding-left:18px;">
<li>Ctrl+左键:添加/取消单项选择</li>
<li>Shift+左键:选择范围(与上次选中形成区间)</li>
<li>Ctrl+A:全选</li>
<li>点击空白处或取消按钮清空选择</li>
</ul>
</div>
<button class="batch-result-item-btn copy-btn">复制</button>
<button class="storage-item-btn delete-btn">删除</button>
<button class="storage-item-btn cancel-btn">取消</button>
</div>
</div>
<div class="storage-container" id="storage-container">
<div class="storage-scroll-content" id="storage-scroll-content"></div>
<div class="storage-empty" id="storage-empty">暂无存储数据</div>
</div>
<div class="storage-import-export">
<button class="storage-import-export-btn" id="export-btn">导出数据</button>
<button class="storage-import-export-btn" id="import-btn">导入数据<span class="import-badge">0</span></button>
<button class="storage-import-export-btn extract-btn" id="extract-btn"><div class="internet-icon"></div>导入分享<span class="extract-badge">0</span></button>
<button class="storage-import-export-btn" id="clear-btn">清空数据</button>
</div>
</div>
<div class="storage-tab-content" data-tab-content="verify">
<div class="share-details">正在获取分享信息...</div>
<div class="settings-content">
<div class="form-row">
<div class="form-group">
<label class="label">自定义字符集</label>
<input type="text" class="input chars-input" value="${DEFAULT_CHARS}">
<div class="error chars-error"></div>
</div>
<div class="form-group">
<label class="label">验证策略</label>
<select class="input batch-share-input strategy-select">
<option value="random">随机模式</option>
<option value="sequential">顺序模式</option>
<option value="random-digits">随机数字模式</option>
<option value="sequential-digits">顺序数字模式</option>
</select>
</div>
<div class="concurrent-group">
<label class="concurrent-label">并发数量</label>
<input type="number" min="1" max="10000" value="10" class="input concurrent-input">
</div>
</div>
<div class="btn-group">
<button class="btn-primary pause-resume-btn">开始验证</button>
<button class="btn-primary stop-btn stop">停止验证</button>
</div>
<div class="stats stats-info"></div>
<div class="status status-div"></div>
</div>
</div>
<div class="storage-tab-content" data-tab-content="elementblock">
<div class="element-block-container" id="element-block-container"></div>
</div>
<div class="storage-tab-content" data-tab-content="settings">
<div class="settings-block-container" id="settings-block-container"></div>
</div>
<div class="storage-tab-content" data-tab-content="batchreceive">
<div class="batch-receive-container" id="batch-receive-container"></div>
</div>
<div class="storage-tab-content" data-tab-content="batchrecognize">
<div class="batch-recognize-container" id="batch-recognize-container"></div>
</div>
<div class="storage-tab-content" data-tab-content="batchshare">
<div class="batch-share-container" id="batch-share-container"></div>
</div>
`;
document.body.appendChild(windowElement);
const charsInput = windowElement.querySelector('.chars-input');
const charsError = windowElement.querySelector('.chars-error');
const strategySelect = windowElement.querySelector('.strategy-select');
const concurrentInput = windowElement.querySelector('.concurrent-input');
const pauseResumeBtn = windowElement.querySelector('.pause-resume-btn');
const stopBtn = windowElement.querySelector('.stop-btn');
const statusDiv = windowElement.querySelector('.status-div');
const statsInfo = windowElement.querySelector('.stats-info');
const shareDetails = windowElement.querySelector('.share-details');
const storageContainer = windowElement.querySelector('#storage-container');
const storageScrollContent = windowElement.querySelector('#storage-scroll-content');
const storageEmpty = windowElement.querySelector('#storage-empty');
const exportBtn = windowElement.querySelector('#export-btn');
const importBtn = windowElement.querySelector('#import-btn');
const extractBtn = windowElement.querySelector('#extract-btn');
const clearBtn = windowElement.querySelector('#clear-btn');
const tabContents = windowElement.querySelectorAll('.storage-tab-content');
const tabs = windowElement.querySelectorAll('.storage-tab');
const statusTagContainer = windowElement.querySelector('.status-tag-container');
const elementBlockContainer = windowElement.querySelector('#element-block-container');
const searchInput = windowElement.querySelector('#search-input');
const searchType = windowElement.querySelector('#search-type');
const apiRefreshBtn = windowElement.querySelector('#api-refresh-btn');
const apiRefreshBadge = windowElement.querySelector('.api-refresh-badge');
const refreshBtn = windowElement.querySelector('.refresh-btn');
const filterButtons = windowElement.querySelectorAll('.filter-buttons button[data-filter]');
let apiRefreshRunning = false;
let apiRefreshCancelled = false;
const deleteExpiredBtn = windowElement.querySelector('#delete-expired-btn');
const deleteInvalidBtn = windowElement.querySelector('#delete-invalid-btn');
const sortButtons = windowElement.querySelectorAll('.sort-buttons .storage-item-btn');
const settingsBlockContainer = windowElement.querySelector('#settings-block-container');
const batchReceiveContainer = windowElement.querySelector('#batch-receive-container');
const batchShareContainer = windowElement.querySelector('#batch-share-container');
const batchRecognizeContainer = windowElement.querySelector('#batch-recognize-container');
let currentSearchTerm = '';
let currentSearchType = 'all';
let currentFilterType = 'all';
let currentSortType = 'time-desc';
let filteredItems = [];
let allItems = [];
let itemHeight = 99;
let visibleItemCount = 10;
function getLocalQuota() {
if (typeof window.get115QuotaLocal === 'function') {
return window.get115QuotaLocal();
}
try {
const c = GM_getValue('vip_quota_cache_v1', null);
return c && c.data ? c.data : null;
} catch (_) { return null; }
}
function setLocalQuota(data) {
if (typeof window.set115QuotaLocal === 'function') {
window.set115QuotaLocal(data);
return;
}
try { GM_setValue('vip_quota_cache_v1', { t: Date.now(), data }); } catch (_) {}
}
function decrementQuotaLocal() {
const q = getLocalQuota();
if (!q) return;
const next = {
surplus: Math.max(0, Number(q.surplus || 0) - 1),
count: Number(q.count || 0),
};
next.used = Math.max(0, next.count - next.surplus);
setLocalQuota(next);
updateQuotaWidget();
}
const storageTabContentEl = windowElement.querySelector('.storage-tab-content[data-tab-content="storage"]');
let quotaWidgetEl = null;
let quotaTextEl = null;
let quotaRefreshBtn = null;
function ensureQuotaWidget() {
if (!storageTabContentEl) return;
if (!quotaWidgetEl) {
storageTabContentEl.style.position = storageTabContentEl.style.position || 'relative';
quotaWidgetEl = document.createElement('div');
quotaWidgetEl.id = 'offline-quota-widget';
quotaWidgetEl.style.position = 'absolute';
quotaWidgetEl.style.right = '12px';
quotaWidgetEl.style.bottom = '12px';
quotaWidgetEl.style.zIndex = '5';
quotaWidgetEl.style.display = 'inline-flex';
quotaWidgetEl.style.alignItems = 'center';
quotaWidgetEl.style.gap = '8px';
quotaWidgetEl.style.padding = '6px 10px';
quotaWidgetEl.style.background = 'rgba(255,255,255,0.9)';
quotaWidgetEl.style.backdropFilter = 'blur(2px)';
quotaWidgetEl.style.border = '1px solid #e6e6e6';
quotaWidgetEl.style.borderRadius = '8px';
quotaWidgetEl.style.boxShadow = '0 2px 8px rgba(0,0,0,0.08)';
quotaWidgetEl.style.fontSize = '12px';
quotaTextEl = document.createElement('span');
quotaTextEl.className = 'quota-text';
quotaTextEl.textContent = '离线额度:--/--';
quotaRefreshBtn = document.createElement('button');
quotaRefreshBtn.type = 'button';
quotaRefreshBtn.className = 'storage-item-btn quota-refresh-btn';
quotaRefreshBtn.textContent = '刷新';
quotaRefreshBtn.style.minWidth = '48px';
quotaRefreshBtn.addEventListener('click', async () => {
const btn = quotaRefreshBtn;
const oldText = btn.textContent;
btn.disabled = true;
btn.textContent = '刷新中...';
try {
let data = null;
if (typeof window.refresh115Quota === 'function') {
data = await window.refresh115Quota();
} else {
data = await new Promise((resolve, reject) => {
GM_xmlhttpRequest({
url: 'https://115.com/web/lixian/?ct=lixian&ac=get_quota_package_info',
method: 'GET',
headers: { 'Accept': 'application/json, text/plain, */*', 'Referer': 'https://115.com/web/lixian/' },
onload: (resp) => {
try {
const json = JSON.parse(resp.responseText || 'null');
resolve({
surplus: Number(json?.surplus ?? json?.package?.["1"]?.surplus ?? 0),
count: Number(json?.count ?? json?.package?.["1"]?.count ?? 0),
used: Number(json?.used ?? json?.package?.["1"]?.used ?? 0),
});
} catch (e) { reject(e); }
},
onerror: (e) => reject(e)
});
});
}
if (data) setLocalQuota(data);
} catch (e) {
console.error('刷新离线额度失败', e);
} finally {
btn.disabled = false;
btn.textContent = oldText;
updateQuotaWidget();
}
});
quotaWidgetEl.appendChild(quotaTextEl);
quotaWidgetEl.appendChild(quotaRefreshBtn);
storageTabContentEl.appendChild(quotaWidgetEl);
}
updateQuotaWidget();
}
function updateQuotaWidget() {
if (!quotaTextEl) return;
const q = getLocalQuota();
if (q && typeof q.surplus !== 'undefined' && typeof q.count !== 'undefined') {
quotaTextEl.textContent = `离线额度:${q.surplus}/${q.count}`;
} else {
quotaTextEl.textContent = '离线额度:--/--';
}
}
function updateQuotaWidgetVisibility() {
const enabled = GM_getValue('enableOfflineQuotaWidget', true);
if (!quotaWidgetEl) {
if (enabled) ensureQuotaWidget();
return;
}
quotaWidgetEl.style.display = enabled ? 'inline-flex' : 'none';
}
ensureQuotaWidget();
updateQuotaWidgetVisibility();
let scrollTop = 0;
let renderStartIndex = 0;
let renderEndIndex = 0;
let lastRenderTime = 0;
let requestAnimationFrameId = null;
let debounceTimer = null;
let itemPool = [];
let cachedElements = new Map();
let lastScrollTop = 0;
let scrollThrottleTimer = null;
let isScrolling = false;
let scrollSpeed = 0;
let lastScrollTime = 0;
let renderCount = 0;
let performanceLastRenderTime = 0;
let totalRenderTime = 0;
function processShareTitle(data) {
const enableHarmonizeTitle = GM_getValue('enableHarmonizeTitle', true);
if (!enableHarmonizeTitle) {
return data.data?.shareinfo?.share_title || '';
}
const shareTitle = data.data?.shareinfo?.share_title || '';
if (shareTitle.includes('***')) {
if (data.data?.list && data.data.list.length > 0) {
return data.data.list[0].n || shareTitle;
}
}
return shareTitle;
}
function setupVirtualScroll() {
const containerHeight = storageContainer.clientHeight;
visibleItemCount = Math.ceil(containerHeight / itemHeight) + 2;
updateScrollContentHeight();
storageContainer.addEventListener('scroll', () => {
if (scrollThrottleTimer) return;
scrollThrottleTimer = requestAnimationFrame(() => {
handleScroll();
scrollThrottleTimer = null;
});
});
handleScroll();
scheduleCacheCleanup();
}
function updateScrollContentHeight() {
const totalHeight = filteredItems.length * itemHeight;
storageScrollContent.style.height = `${totalHeight}px`;
}
function handleScroll() {
const currentScrollTop = storageContainer.scrollTop;
const currentTime = performance.now();
if (lastScrollTime > 0) {
const timeDiff = currentTime - lastScrollTime;
const scrollDiff = Math.abs(currentScrollTop - lastScrollTop);
scrollSpeed = scrollDiff / timeDiff;
}
lastScrollTime = currentTime;
if (Math.abs(currentScrollTop - lastScrollTop) < 10) return;
lastScrollTop = currentScrollTop;
scrollTop = currentScrollTop;
let bufferSize;
if (scrollSpeed > 2) {
bufferSize = Math.max(20, Math.ceil(visibleItemCount * 0.8));
} else if (scrollSpeed > 0.5) {
bufferSize = Math.max(15, Math.ceil(visibleItemCount * 0.6));
} else {
bufferSize = Math.max(10, Math.ceil(visibleItemCount * 0.4));
}
const newRenderStartIndex = Math.max(0, Math.floor(scrollTop / itemHeight) - bufferSize);
const newRenderEndIndex = Math.min(filteredItems.length, newRenderStartIndex + visibleItemCount + bufferSize * 2);
if (newRenderStartIndex !== renderStartIndex || newRenderEndIndex !== renderEndIndex) {
renderStartIndex = newRenderStartIndex;
renderEndIndex = newRenderEndIndex;
renderVisibleItems();
}
if (selectedItems.size > 0 && !isScrolling) {
isScrolling = true;
requestAnimationFrame(() => {
updateStorageItemSelection();
isScrolling = false;
});
}
}
function handleCopyButtonStatus(button, text) {
if (button._copyTimer) clearTimeout(button._copyTimer);
const originalText = button.textContent;
button.textContent = '已复制';
button.classList.add('copied');
button._copyTimer = setTimeout(() => {
button.textContent = originalText;
button.classList.remove('copied');
button._copyTimer = null;
}, 2000);
}
function renderVisibleItems() {
const startTime = performance.now();
const needCount = renderEndIndex - renderStartIndex;
while (itemPool.length < needCount) {
const div = document.createElement('div');
div.className = 'virtual-scroll-item';
div.style.height = `${itemHeight}px`;
div.style.position = 'absolute';
itemPool.push(div);
}
while (itemPool.length > needCount + 5) {
const removedDiv = itemPool.pop();
if (removedDiv._item) {
cachedElements.delete(removedDiv._item.shareCode + (removedDiv._item.ed2k || ''));
}
}
const fragment = document.createDocumentFragment();
for (let i = 0; i < needCount; i++) {
const idx = renderStartIndex + i;
const item = filteredItems[idx];
const div = itemPool[i];
if (!item) {
div.style.display = 'none';
continue;
}
div.style.display = '';
div.style.top = `${idx * itemHeight}px`;
const itemKey = item.shareCode + (item.ed2k || '');
if (!div._item || div._item !== item) {
let el;
if (cachedElements.has(itemKey)) {
el = cachedElements.get(itemKey).cloneNode(true);
rebindEventListeners(el, item, idx);
} else {
el = createStorageItemElement({...item, index: idx});
const template = el.cloneNode(true);
cachedElements.set(itemKey, template);
}
div.innerHTML = '';
div.appendChild(el);
div._item = item;
}
const storageItem = div.querySelector('.storage-item');
if (storageItem) {
storageItem.classList.toggle('selected', selectedItems.has(idx));
}
fragment.appendChild(div);
}
storageScrollContent.innerHTML = '';
storageScrollContent.appendChild(fragment);
const endTime = performance.now();
const renderTime = endTime - startTime;
renderCount++;
totalRenderTime += renderTime;
performanceLastRenderTime = renderTime;
if (renderCount % 100 === 0) {
const bufferSize = Math.max(10, Math.ceil(visibleItemCount * 0.4));
console.log(`虚拟滚动性能统计: 平均渲染时间 ${(totalRenderTime / renderCount).toFixed(2)}ms, 最近渲染时间 ${renderTime.toFixed(2)}ms, 缓存命中率 ${((cachedElements.size / Math.max(1, renderCount)) * 100).toFixed(1)}%, 缓冲区域 ${bufferSize}项, 滚动速度 ${scrollSpeed.toFixed(2)}px/ms`);
}
}
function rebindEventListeners(element, item, index) {
element.dataset.index = index;
const copyBtn = element.querySelector('.copy-btn');
const deleteBtn = element.querySelector('.delete-btn');
const offlineBtn = element.querySelector('.offline-btn');
const fetchBtn = element.querySelector('.fetch-btn');
const receiveBtn = element.querySelector('.receive-btn');
const openBtn = element.querySelector('.open-btn');
const noteLabel = element.querySelector('.note-label');
if (copyBtn) {
copyBtn.addEventListener('click', () => {
if (item.ed2k) {
const text = `${item.shareTitle || '无标题'}\n${item.ed2k}`;
navigator.clipboard.writeText(text).then(() => {
handleCopyButtonStatus(copyBtn);
}).catch(err => {
console.error('复制失败:', err);
});
} else {
const title = item.shareTitle || '无标题';
const link = `https://115cdn.com/s/${item.shareCode}${item.password ? `?password=${item.password}` : ''}`;
const text = `${link}#\n${title}`;
navigator.clipboard.writeText(text).then(() => {
handleCopyButtonStatus(copyBtn);
});
}
});
}
if (deleteBtn) {
deleteBtn.addEventListener('click', () => {
const enableDeleteConfirm = GM_getValue('enableDeleteConfirm', true);
if (enableDeleteConfirm) {
if (!confirm('确定要删除该条目吗?')) return;
}
GM_deleteValue(generateStorageKey(item.shareCode, item.ed2k, item.magnet));
renderStorage(false);
});
}
if (offlineBtn) {
offlineBtn.addEventListener('click', () => {
const url = item.magnet || item.ed2k;
const encodedUrl = encodeURIComponent(url);
const apiUrl = `https://115.com/web/lixian/?ct=lixian&ac=add_task_url&url=${encodedUrl}`;
setupButtonWithStatus(offlineBtn, () => {
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: 'GET',
url: apiUrl,
onload: function(response) {
try {
const data = JSON.parse(response.responseText);
if (data.state === true) {
decrementQuotaLocal();
resolve();
} else {
const errorMsg = data.error_msg || data.error || data.errtype || '离线失败';
reject(new Error(errorMsg));
}
} catch (e) {
reject(new Error('解析响应失败'));
}
},
onerror: function(error) {
reject(new Error(`请求失败: ${error.statusText || '网络错误'}`));
}
});
});
}, '离线成功', '离线失败');
});
}
if (receiveBtn) {
receiveBtn.addEventListener('click', () => {
setupButtonWithStatus(receiveBtn, async () => {
const { user_id } = unsafeWindow || {};
const formData = new URLSearchParams();
formData.append("user_id", user_id);
formData.append("share_code", item.shareCode);
formData.append("receive_code", item.password);
formData.append("cid", "100115");
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: "POST",
url: 'https://webapi.115.com/share/receive',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
data: formData.toString(),
onload: r => {
try {
const data = JSON.parse(r.responseText);
if (data.state === true) {
resolve();
} else {
reject(new Error(data.error_msg || data.error || '接收失败'));
}
} catch(e) {
reject(e);
}
},
onerror: e => reject(e)
});
});
}, '接收成功', '接收失败');
});
}
if (openBtn) {
openBtn.addEventListener('click', () => {
if (item.ed2k) {
const text = item.ed2k;
navigator.clipboard.writeText(text).then(() => {
handleCopyButtonStatus(openBtn);
});
} else {
window.open(`https://115cdn.com/s/${item.shareCode}${item.password ? `?password=${item.password}` : ''}`, '_blank');
}
});
}
if (fetchBtn) {
fetchBtn.addEventListener('click', () => {
setupButtonWithStatus(fetchBtn, () => {
return new Promise((resolve, reject) => {
if (item.ed2k) {
reject(new Error('ED2K链接无需更新'));
return;
}
checkPasswordCorrect(item.shareCode, item.password, (isCorrect, responseData) => {
if (isCorrect) {
delete item.error;
if (responseData?.shareTitle) item.shareTitle = responseData.shareTitle;
if (responseData?.expireTime !== undefined) item.expireTime = responseData.expireTime;
if (responseData?.fileSize !== undefined) item.fileSize = responseData.fileSize;
if (responseData?.autoRenewal !== undefined) item.autoRenewal = String(responseData.autoRenewal);
updateItemData(item.note, item.shareTitle, item.password);
resolve();
} else {
const rawMsg = responseData?.error || '验证失败';
const errno = responseData?.errno;
const shareInfo = responseData?.rawResponse?.data?.shareinfo || {};
const shareState = shareInfo?.share_state;
const isCancelledErrno = errno === 4100010;
const isCancelledMsg = typeof rawMsg === 'string' && rawMsg.includes('取消');
if (errno === 4100008) {
item.error = '访问码错误';
updateItemData(item.note, item.shareTitle, item.password, item.error);
reject(new Error('访问码错误'));
return;
}
if (shareState === -1 || isCancelledErrno || isCancelledMsg) {
item.shareTitle = '';
item.expireTime = -1;
item.fileSize = 0;
item.autoRenewal = '0';
delete item.error;
updateItemData(item.note, '', item.password);
reject(new Error('分享已取消'));
return;
}
if (rawMsg === '分享已过期' || rawMsg.includes('过期')) {
const expireTime = shareInfo?.expire_time || -1;
const fileSize = parseInt(shareInfo?.file_size || '0');
const autoRenewal = String(shareInfo?.auto_renewal || '0');
const newTitle = processShareTitle(responseData?.rawResponse || {});
item.expireTime = expireTime;
item.fileSize = fileSize;
item.autoRenewal = autoRenewal;
if (newTitle) item.shareTitle = newTitle;
item.error = shareInfo?.forbid_reason || '分享已过期';
updateItemData(item.note, item.shareTitle, item.password, item.error);
reject(new Error('分享已过期'));
return;
}
item.error = '访问码错误';
updateItemData(item.note, item.shareTitle, item.password, item.error);
reject(new Error('访问码错误'));
}
});
});
}, '更新成功', '更新失败');
});
}
element.addEventListener('click', (e) => {
if (e.target.tagName === 'BUTTON' || e.target.tagName === 'INPUT' || e.target.closest('button') || e.target.closest('input')) {
return;
}
const currentIndex = parseInt(element.dataset.index);
if (e.ctrlKey) {
if (selectedItems.has(currentIndex)) {
selectedItems.delete(currentIndex);
} else {
selectedItems.add(currentIndex);
}
lastSelectedIndex = currentIndex;
} else if (e.shiftKey && lastSelectedIndex !== -1) {
const start = Math.min(lastSelectedIndex, currentIndex);
const end = Math.max(lastSelectedIndex, currentIndex);
for (let i = start; i <= end; i++) {
selectedItems.add(i);
}
} else {
selectedItems.clear();
selectedItems.add(currentIndex);
lastSelectedIndex = currentIndex;
}
updateStorageItemSelection();
updateBatchActions();
});
}
function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
function clearVirtualScrollCache() {
cachedElements.clear();
itemPool.forEach(div => {
if (div._item) {
div._item = null;
}
});
itemPool.length = 0;
if (window.gc) {
window.gc();
}
}
function getMemoryUsage() {
if (performance.memory) {
const memory = performance.memory;
return {
used: Math.round(memory.usedJSHeapSize / 1024 / 1024),
total: Math.round(memory.totalJSHeapSize / 1024 / 1024),
limit: Math.round(memory.jsHeapSizeLimit / 1024 / 1024)
};
}
return null;
}
function scheduleCacheCleanup() {
setInterval(() => {
const memory = getMemoryUsage();
if (memory && memory.used > memory.limit * 0.8) {
console.log('内存使用率过高,清理虚拟滚动缓存');
clearVirtualScrollCache();
}
if (cachedElements.size > 1000) {
const keys = Array.from(cachedElements.keys());
const toDelete = keys.slice(0, 500);
toDelete.forEach(key => cachedElements.delete(key));
console.log(`清理了 ${toDelete.length} 个缓存元素`);
}
const currentTime = performance.now();
if (currentTime - lastScrollTime > 1000) {
scrollSpeed = 0;
}
}, 30000);
}
function testVirtualScrollPerformance() {
console.log('=== 虚拟滚动性能测试 ===');
console.log(`当前渲染范围: ${renderStartIndex} - ${renderEndIndex} (共${renderEndIndex - renderStartIndex}项)`);
console.log(`可见项数量: ${visibleItemCount}`);
console.log(`缓冲区域大小: ${Math.max(10, Math.ceil(visibleItemCount * 0.4))}项`);
console.log(`缓存元素数量: ${cachedElements.size}`);
console.log(`元素池大小: ${itemPool.length}`);
console.log(`滚动速度: ${scrollSpeed.toFixed(2)}px/ms`);
const memory = getMemoryUsage();
if (memory) {
console.log(`内存使用: ${memory.used}MB / ${memory.total}MB (${(memory.used / memory.total * 100).toFixed(1)}%)`);
}
console.log(`渲染统计: 总渲染次数 ${renderCount}, 平均渲染时间 ${(totalRenderTime / Math.max(1, renderCount)).toFixed(2)}ms`);
console.log('========================');
}
const debouncedSearch = debounce((searchTerm) => {
currentSearchTerm = searchTerm;
clearVirtualScrollCache();
renderStorage(false);
}, 300);
function setupEditableField(displayElement, inputElement, onSave) {
displayElement.addEventListener('dblclick', () => {
displayElement.classList.add('storage-item-input-hidden');
inputElement.classList.remove('storage-item-input-hidden');
inputElement.classList.add('storage-item-input-visible');
inputElement.focus();
inputElement.select();
});
const handleClickOutside = (e) => {
if (!inputElement.contains(e.target) && !displayElement.contains(e.target)) {
const newValue = inputElement.value.trim();
if (onSave) {
onSave(newValue);
}
displayElement.classList.remove('storage-item-input-hidden');
inputElement.classList.add('storage-item-input-hidden');
inputElement.classList.remove('storage-item-input-visible');
document.removeEventListener('click', handleClickOutside);
}
};
inputElement.addEventListener('focus', () => {
setTimeout(() => {
document.addEventListener('click', handleClickOutside);
}, 0);
});
inputElement.addEventListener('blur', () => {
const newValue = inputElement.value.trim();
if (onSave) {
onSave(newValue);
}
displayElement.classList.remove('storage-item-input-hidden');
inputElement.classList.add('storage-item-input-hidden');
inputElement.classList.remove('storage-item-input-visible');
document.removeEventListener('click', handleClickOutside);
});
inputElement.addEventListener('keypress', (e) => {
if (e.key === 'Enter') inputElement.blur();
});
}
function setupButtonWithStatus(button, action, successText = '成功', errorText = '失败') {
const originalText = button.textContent;
button.textContent = '处理中...';
button.disabled = true;
return action().then(() => {
button.textContent = successText;
button.style.backgroundColor = 'rgba(76,175,80,0.2)';
button.style.color = '#4caf50';
}).catch((error) => {
const errorMessage = error.message || errorText;
button.textContent = errorMessage.length > 8 ? errorMessage.substring(0, 8) + '...' : errorMessage;
button.style.backgroundColor = 'rgba(244,67,54,0.2)';
button.style.color = '#f44336';
console.error('操作失败:', error);
if (errorMessage.length > 8) {
button.title = errorMessage;
}
}).finally(() => {
setTimeout(() => {
button.textContent = originalText;
button.style.backgroundColor = '';
button.style.color = '';
button.disabled = false;
button.title = '';
}, 2000);
});
}
function createStorageItemElement(item) {
const now = Math.floor(Date.now() / 1000);
const isExpired = item.expireTime !== -1 && item.expireTime <= now;
const isLongTerm = item.expireTime === -1;
const isTimeLimited = item.expireTime !== -1 && !isExpired;
const isCancelled = item.expireTime === -1 && item.fileSize === 0 && item.shareTitle === '';
const isEd2k = item.ed2k && item.ed2k !== '';
const isMagnet = item.magnet && item.magnet !== '';
const hasError = item.error;
let expireDisplay = '';
if (hasError) {
const isExpiredError = item.error === "分享已过期" || item.error === "链接已过期";
const isCancelledError = item.error === "分享已取消";
if (isExpiredError) {
expireDisplay = '<span class="expired-tag">已过期</span>';
} else if (isCancelledError) {
expireDisplay = '<span class="expired-tag">已取消分享</span>';
} else {
if (isLongTerm) {
expireDisplay = '<span class="valid-tag">长期</span><span class="error-tag">访问码错误</span>';
} else if (item.autoRenewal === '1') {
expireDisplay = '<span class="auto-renewal-tag">自动续期</span><span class="error-tag">访问码错误</span>';
} else if (isExpired) {
expireDisplay = '<span class="expired-tag">已过期</span><span class="error-tag">访问码错误</span>';
} else if (isTimeLimited) {
expireDisplay = '<span class="time-limited-tag">限时</span><span class="error-tag">访问码错误</span>';
} else {
expireDisplay = '<span class="error-tag">访问码错误</span>';
}
}
} else if (isCancelled) {
expireDisplay = '<span class="expired-tag">已取消分享</span>';
} else if (isLongTerm) {
expireDisplay = '<span class="valid-tag">长期</span>' + (item.autoRenewal === '1' ? '<span class="auto-renewal-tag">自动续期</span>' : '');
} else if (item.autoRenewal === '1') {
expireDisplay = '<span class="auto-renewal-tag">自动续期</span>';
} else if (isExpired) {
expireDisplay = '<span class="expired-tag">已过期</span>';
} else if (isTimeLimited) {
const remainingSeconds = item.expireTime - now;
expireDisplay = `<span class="time-limited-tag">${formatTime(remainingSeconds)}</span>`;
}
const fileSizeDisplay = item.fileSize ? `<span class="file-size">${formatFileSize(item.fileSize)}</span>` : '';
const ed2kTag = isEd2k ? '<span class="ed2k-tag">ED2K</span>' : '';
const magnetTag = isMagnet ? '<span class="magnet-tag">磁力链</span>' : '';
const itemElement = document.createElement('div');
itemElement.className = 'storage-item';
itemElement.dataset.index = item.index || 0;
if (item.ed2k) {
const ed2kMatch = item.ed2k.match(/ed2k:\/\/\|file\|([^|]+)\|(\d+)\|([0-9A-F]{32})(?:\|h=([^|]+))?(\||\/)?/i);
const ed2kTitle = ed2kMatch ? decodeURIComponent(ed2kMatch[1]) : 'ED2K文件';
const ed2kHash = ed2kMatch ? ed2kMatch[3] : '';
itemElement.innerHTML = `
<div class="storage-item-header">
<div class="storage-item-title" title="${ed2kTitle}">${fileSizeDisplay}${ed2kTag}<span class="title-value">${ed2kTitle}</span><input type="text" class="storage-item-title-input storage-item-input-hidden" value="${ed2kTitle}"></div>
</div>
<div class="storage-item-content">
<div class="storage-item-info">
<span class="ed2k-display">${item.ed2k || ''}</span>
<input type="text" class="storage-item-ed2k-input storage-item-input-hidden" value="${item.ed2k || ''}">
</div>
<div class="storage-item-actions">
<button class="batch-result-item-btn copy-btn">复制</button>
<button class="storage-item-btn offline-btn">离线</button>
<button class="storage-item-btn delete-btn">删除</button>
</div>
</div>
<div class="storage-item-note">
<span class="note-label" title="双击获取为标题">备注:</span>
<span class="note-display">${item.note || '无备注'}</span>
<input type="text" class="storage-item-note-input storage-item-input-hidden" placeholder="添加或修改备注" value="${item.note || ''}">
<span class="note-copy-status"></span>
</div>
`;
const ed2kDisplay = itemElement.querySelector('.ed2k-display');
const ed2kInput = itemElement.querySelector('.storage-item-ed2k-input');
setupEditableField(ed2kDisplay, ed2kInput, (newEd2k) => {
const newEd2kMatch = newEd2k.match(/ed2k:\/\/\|file\|([^|]+)\|(\d+)\|([0-9A-F]{32})(?:\|h=([^|]+))?(\||\/)?/i);
if (newEd2kMatch) {
const newHash = newEd2kMatch[3];
const oldHash = item.shareCode;
if (newHash !== oldHash) {
GM_deleteValue(generateStorageKey(oldHash, item.ed2k));
saveToStorage(newHash, item.password, item.note, item.shareTitle, item.expireTime, item.fileSize, item.autoRenewal, newEd2k);
} else {
saveToStorage(item.shareCode, item.password, item.note, item.shareTitle, item.expireTime, item.fileSize, item.autoRenewal, newEd2k);
}
ed2kDisplay.textContent = newEd2k;
} else {
ed2kInput.value = item.ed2k;
alert('ED2K链接格式不正确,请检查后重试');
}
});
const titleDisplay = itemElement.querySelector('.title-value');
const titleInput = itemElement.querySelector('.storage-item-title-input');
setupEditableField(titleDisplay, titleInput, (newTitle) => {
titleDisplay.textContent = newTitle || '无标题';
titleDisplay.title = newTitle || '无标题';
updateItemData(item.note, newTitle);
});
} else if (item.magnet) {
const magnetMatch = item.magnet.match(/magnet:\?xt=urn:btih:([a-fA-F0-9]{40})/i);
const magnetHash = magnetMatch ? magnetMatch[1] : '';
let magnetName = '磁力链文件';
const nameMatch = item.magnet.match(/[?&]name=([^&]+)/i);
if (nameMatch) {
try {
magnetName = decodeURIComponent(nameMatch[1]);
} catch (e) {
magnetName = nameMatch[1];
}
} else {
const dnMatch = item.magnet.match(/[?&]dn=([^&]+)/i);
if (dnMatch) {
try {
magnetName = decodeURIComponent(dnMatch[1]);
} catch (e) {
magnetName = dnMatch[1];
}
}
}
const displayName = item.shareTitle || item.note || magnetName;
const linkType = 'magnet';
const linkDisplay = 'magnet-display';
const linkInput = 'storage-item-magnet-input';
const linkValue = item.magnet || '';
itemElement.innerHTML = `
<div class="storage-item-header">
<div class="storage-item-title" title="${displayName}">${magnetTag}<span class="title-value">${displayName}</span><input type="text" class="storage-item-title-input storage-item-input-hidden" value="${displayName}"></div>
</div>
<div class="storage-item-content">
<div class="storage-item-info">
<span class="${linkDisplay}">${linkValue}</span>
<input type="text" class="${linkInput} storage-item-input-hidden" value="${linkValue}">
</div>
<div class="storage-item-actions">
<button class="batch-result-item-btn copy-btn">复制</button>
<button class="storage-item-btn offline-btn">离线</button>
<button class="storage-item-btn delete-btn">删除</button>
</div>
</div>
<div class="storage-item-note">
<span class="note-label" title="双击获取为标题">备注:</span>
<span class="note-display">${item.note || '无备注'}</span>
<input type="text" class="storage-item-note-input storage-item-input-hidden" placeholder="添加或修改备注" value="${item.note || ''}">
<span class="note-copy-status"></span>
</div>
`;
const magnetDisplay = itemElement.querySelector('.magnet-display');
const magnetInput = itemElement.querySelector('.storage-item-magnet-input');
setupEditableField(magnetDisplay, magnetInput, (newMagnet) => {
const newMagnetMatch = newMagnet.match(/magnet:\?xt=urn:btih:([a-fA-F0-9]{40})/i);
if (newMagnetMatch) {
const newHash = newMagnetMatch[1];
const oldHash = item.shareCode;
let newName = '';
const nameMatch = newMagnet.match(/[?&]name=([^&]+)/i);
if (nameMatch) {
try {
newName = decodeURIComponent(nameMatch[1]);
} catch (e) {
newName = nameMatch[1];
}
} else {
const dnMatch = newMagnet.match(/[?&]dn=([^&]+)/i);
if (dnMatch) {
try {
newName = decodeURIComponent(dnMatch[1]);
} catch (e) {
newName = dnMatch[1];
}
}
}
if (newHash !== oldHash) {
GM_deleteValue(generateStorageKey(oldHash, item.ed2k, item.magnet));
saveToStorage(newHash, item.password, item.note, item.shareTitle, item.expireTime, item.fileSize, item.autoRenewal, item.ed2k, newMagnet);
} else {
saveToStorage(item.shareCode, item.password, item.note, item.shareTitle, item.expireTime, item.fileSize, item.autoRenewal, item.ed2k, newMagnet);
}
magnetDisplay.textContent = newMagnet;
} else {
magnetInput.value = item.magnet;
alert('磁力链格式不正确,请检查后重试');
}
});
const titleDisplay = itemElement.querySelector('.title-value');
const titleInput = itemElement.querySelector('.storage-item-title-input');
setupEditableField(titleDisplay, titleInput, (newTitle) => {
titleDisplay.textContent = newTitle || '无标题';
titleDisplay.title = newTitle || '无标题';
updateItemData(item.note, newTitle);
});
const noteDisplay = itemElement.querySelector('.note-display');
const noteInput = itemElement.querySelector('.storage-item-note-input');
setupEditableField(noteDisplay, noteInput, (newNote) => {
noteDisplay.textContent = newNote || '无备注';
updateItemData(newNote, item.shareTitle);
});
} else {
itemElement.innerHTML = `
<div class="storage-item-header">
<div class="storage-item-title" title="${item.shareTitle || '无标题'}">${fileSizeDisplay}<span class="title-value">${item.shareTitle || '无标题'}</span><input type="text" class="storage-item-title-input storage-item-input-hidden" value="${item.shareTitle || '无标题'}"></div>
</div>
<div class="storage-item-password">
<div class="storage-item-info">
<span class="storage-item-sharer">分享码: ${item.shareCode}</span>
<span class="storage-item-sharer password-display">访问码: <span class="password-value">${item.password}</span><input type="text" class="storage-item-password-input storage-item-input-hidden" value="${item.password}" maxlength="4"></span>
${expireDisplay}
</div>
<div class="storage-item-actions">
<button class="batch-result-item-btn copy-btn">复制</button>
${(!isEd2k && !isMagnet) ? '<button class="storage-item-btn fetch-btn">更新</button>' : ''}
<button class="storage-item-btn receive-btn">接收</button>
<button class="storage-item-btn open-btn">打开</button>
<button class="storage-item-btn delete-btn">删除</button>
</div>
</div>
<div class="storage-item-note">
<span class="note-label" title="双击获取为标题">备注:</span>
<span class="note-display">${item.note || '无备注'}</span><input type="text" class="storage-item-note-input storage-item-input-hidden" placeholder="添加或修改备注" value="${item.note || ''}">
<span class="note-copy-status"></span>
</div>
`;
}
const copyBtn = itemElement.querySelector('.copy-btn');
const offlineBtn = itemElement.querySelector('.offline-btn');
const fetchBtn = itemElement.querySelector('.fetch-btn');
const receiveBtn = itemElement.querySelector('.receive-btn');
const openBtn = itemElement.querySelector('.open-btn');
const deleteBtn = itemElement.querySelector('.delete-btn');
const noteLabel = itemElement.querySelector('.note-label');
const noteDisplay = itemElement.querySelector('.note-display');
const noteInput = itemElement.querySelector('.storage-item-note-input');
const passwordDisplay = itemElement.querySelector('.password-value');
const passwordInput = itemElement.querySelector('.storage-item-password-input');
const copyStatus = itemElement.querySelector('.note-copy-status');
const titleDisplay = itemElement.querySelector('.title-value');
const titleInput = itemElement.querySelector('.storage-item-title-input');
if (offlineBtn) {
offlineBtn.addEventListener('click', () => {
const url = item.magnet || item.ed2k;
const encodedUrl = encodeURIComponent(url);
const apiUrl = `https://115.com/web/lixian/?ct=lixian&ac=add_task_url&url=${encodedUrl}`;
setupButtonWithStatus(offlineBtn, () => {
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: 'GET',
url: apiUrl,
onload: function(response) {
try {
const data = JSON.parse(response.responseText);
if (data.state === true) {
decrementQuotaLocal();
resolve();
} else {
const errorMsg = data.error_msg || data.error || data.errtype || '离线失败';
reject(new Error(errorMsg));
}
} catch (e) {
reject(new Error('解析响应失败'));
}
},
onerror: function(error) {
reject(new Error(`请求失败: ${error.statusText || '网络错误'}`));
}
});
});
}, '离线成功', '离线失败');
});
}
if (receiveBtn) {
receiveBtn.addEventListener('click', () => {
setupButtonWithStatus(receiveBtn, async () => {
const { user_id } = unsafeWindow || {};
const formData = new URLSearchParams();
formData.append("user_id", user_id);
formData.append("share_code", item.shareCode);
formData.append("receive_code", item.password);
formData.append("cid", "100115");
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: "POST",
url: 'https://webapi.115.com/share/receive',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
data: formData.toString(),
onload: r => {
try {
const data = JSON.parse(r.responseText);
if (data.state === true) {
resolve();
} else {
reject(new Error(data.error_msg || data.error || '接收失败'));
}
} catch(e) {
reject(e);
}
},
onerror: e => reject(e)
});
});
}, '接收成功', '接收失败');
});
}
copyBtn.addEventListener('click', () => {
const enableMagnetTitleCopy = GM_getValue('enableMagnetTitleCopy', false);
if (item.magnet) {
let magnetToCopy;
if (enableMagnetTitleCopy && (item.shareTitle || item.note)) {
const title = item.shareTitle || item.note;
magnetToCopy = `${title}\n${item.magnet}`;
} else {
magnetToCopy = item.magnet;
if (!item.magnet.includes('?name=') && (item.shareTitle || item.note)) {
const title = item.shareTitle || item.note;
magnetToCopy = `${item.magnet}?name=${title}`;
}
}
navigator.clipboard.writeText(magnetToCopy).then(() => {
handleCopyButtonStatus(copyBtn);
});
} else if (item.ed2k) {
const text = `${item.shareTitle || '无标题'}\n${item.ed2k}`;
navigator.clipboard.writeText(text).then(() => {
handleCopyButtonStatus(copyBtn);
});
} else {
const title = item.shareTitle || '无标题';
const link = `https://115cdn.com/s/${item.shareCode}${item.password ? `?password=${item.password}` : ''}`;
const enableShareTitleCopy = GM_getValue('enableShareTitleCopy', false);
let text;
if (enableShareTitleCopy && title) {
text = `${title}\n${link}`;
} else {
text = `${link}#\n${title}`;
}
navigator.clipboard.writeText(text).then(() => {
handleCopyButtonStatus(copyBtn);
});
}
});
if (fetchBtn) {
fetchBtn.addEventListener('click', () => {
setupButtonWithStatus(fetchBtn, () => {
return new Promise((resolve, reject) => {
checkPasswordCorrect(item.shareCode, item.password, (isCorrect, responseData) => {
if (isCorrect) {
delete item.error;
if (responseData?.shareTitle) item.shareTitle = responseData.shareTitle;
if (responseData?.expireTime !== undefined) item.expireTime = responseData.expireTime;
if (responseData?.fileSize !== undefined) item.fileSize = responseData.fileSize;
if (responseData?.autoRenewal !== undefined) item.autoRenewal = String(responseData.autoRenewal);
updateItemData(item.note, item.shareTitle, item.password);
resolve();
} else {
const rawMsg = responseData?.error || '验证失败';
const errno = responseData?.errno;
const shareInfo = responseData?.rawResponse?.data?.shareinfo || {};
const shareState = shareInfo?.share_state;
const isCancelledErrno = errno === 4100010;
const isCancelledMsg = typeof rawMsg === 'string' && rawMsg.includes('取消');
if (errno === 4100008) {
item.error = '访问码错误';
updateItemData(item.note, item.shareTitle, item.password, item.error);
reject(new Error('访问码错误'));
return;
}
if (shareState === -1 || isCancelledErrno || isCancelledMsg) {
delete item.error;
item.shareTitle = '';
item.expireTime = -1;
item.fileSize = 0;
item.autoRenewal = '0';
updateItemData(item.note, '', item.password);
reject(new Error('分享已取消'));
return;
}
if (rawMsg === '分享已过期' || rawMsg.includes('过期')) {
const expireTime = shareInfo?.expire_time || -1;
const fileSize = parseInt(shareInfo?.file_size || '0');
const autoRenewal = String(shareInfo?.auto_renewal || '0');
const newTitle = processShareTitle(responseData?.rawResponse || {});
item.expireTime = expireTime;
item.fileSize = fileSize;
item.autoRenewal = autoRenewal;
if (newTitle) item.shareTitle = newTitle;
item.error = shareInfo?.forbid_reason || '分享已过期';
updateItemData(item.note, item.shareTitle, item.password, item.error);
reject(new Error('分享已过期'));
return;
}
item.error = '访问码错误';
updateItemData(item.note, item.shareTitle, item.password, item.error);
reject(new Error('访问码错误'));
}
});
});
}, '获取成功', '获取失败');
});
}
if (openBtn) {
openBtn.addEventListener('click', () => {
if (item.magnet) {
navigator.clipboard.writeText(item.magnet).then(() => {
handleCopyButtonStatus(openBtn);
});
} else if (item.ed2k) {
navigator.clipboard.writeText(item.ed2k).then(() => {
handleCopyButtonStatus(openBtn);
});
} else {
window.open(`https://115cdn.com/s/${item.shareCode}?password=${item.password}`, '_blank');
}
});
}
noteLabel.addEventListener('dblclick', () => {
if (!item.ed2k && !isCancelled) {
copyStatus.textContent = '处理中...';
copyStatus.style.display = 'inline';
checkPasswordCorrect(item.shareCode, item.password, (isCorrect, responseData) => {
if (isCorrect && responseData) {
const newNote = `${responseData.shareTitle || '无标题'}`;
noteDisplay.textContent = newNote;
noteInput.value = newNote;
updateItemData(newNote);
copyStatus.textContent = '已完成';
setTimeout(() => {
copyStatus.style.display = 'none';
}, 2000);
} else {
copyStatus.style.display = 'none';
}
});
}
});
function updateItemData(newNote = item.note, newTitle = item.shareTitle, newPassword = item.password, newError = item.error) {
const params = [
item.shareCode,
newPassword,
newNote || '',
newTitle || '',
item.expireTime,
item.fileSize,
item.autoRenewal,
item.ed2k,
item.magnet,
newError
];
updateStorageItem(...params);
}
setupEditableField(noteDisplay, noteInput, (newNote) => {
noteDisplay.textContent = newNote || '无备注';
updateItemData(newNote);
});
if (passwordDisplay && passwordInput) {
setupEditableField(passwordDisplay, passwordInput, (newPassword) => {
if (newPassword.length === 4 && /^[0-9a-zA-Z]+$/.test(newPassword)) {
passwordDisplay.textContent = newPassword;
updateItemData(item.note, item.shareTitle, newPassword);
} else {
passwordDisplay.textContent = item.password;
passwordInput.value = item.password;
}
});
passwordInput.addEventListener('input', (e) => {
passwordInput.value = passwordInput.value.replace(/[^0-9a-zA-Z]/g, '');
if (passwordInput.value.length > 4) passwordInput.value = passwordInput.value.slice(0, 4);
});
}
if (titleDisplay && titleInput) {
setupEditableField(titleDisplay, titleInput, (newTitle) => {
titleDisplay.textContent = newTitle || '无标题';
titleDisplay.title = newTitle || '无标题';
updateItemData(item.note, newTitle);
});
}
deleteBtn.addEventListener('click', () => {
const enableDeleteConfirm = GM_getValue('enableDeleteConfirm', true);
if (enableDeleteConfirm) {
if (!confirm('确定要删除该条目吗?')) return;
}
GM_deleteValue(generateStorageKey(item.shareCode, item.ed2k, item.magnet));
renderStorage(false);
});
itemElement.addEventListener('click', (e) => {
if (e.target.tagName === 'BUTTON' || e.target.tagName === 'INPUT' || e.target.closest('button') || e.target.closest('input')) {
return;
}
const currentIndex = parseInt(itemElement.dataset.index);
if (e.ctrlKey) {
if (selectedItems.has(currentIndex)) {
selectedItems.delete(currentIndex);
} else {
selectedItems.add(currentIndex);
}
lastSelectedIndex = currentIndex;
} else if (e.shiftKey && lastSelectedIndex !== -1) {
const start = Math.min(lastSelectedIndex, currentIndex);
const end = Math.max(lastSelectedIndex, currentIndex);
for (let i = start; i <= end; i++) {
selectedItems.add(i);
}
} else {
selectedItems.clear();
selectedItems.add(currentIndex);
lastSelectedIndex = currentIndex;
}
updateStorageItemSelection();
updateBatchActions();
});
const allInputs = itemElement.querySelectorAll('input[type="text"], input[type="password"], input[type="number"]');
allInputs.forEach(input => {
input.addEventListener('blur', (e) => {
setTimeout(() => {
if (input.style.display !== 'none' && input.style.display !== '') {
const displayElement = input.previousElementSibling;
if (displayElement && displayElement.classList.contains('storage-item-title')) {
displayElement.style.display = 'inline';
input.style.display = 'none';
}
}
}, 10);
});
});
return itemElement;
}
function updateStorageItemSelection() {
const virtualScrollItems = document.querySelectorAll('.virtual-scroll-item');
virtualScrollItems.forEach((virtualItem) => {
const storageItem = virtualItem.querySelector('.storage-item');
if (storageItem) {
const index = parseInt(storageItem.dataset.index);
if (selectedItems.has(index)) {
storageItem.classList.add('selected');
} else {
storageItem.classList.remove('selected');
}
}
});
}
function updateBatchActions() {
const container = document.querySelector('.batch-actions-container');
const countSpan = document.querySelector('.batch-actions-container .selected-count');
const batchCopyBtn = document.querySelector('.batch-actions-container .copy-btn');
const batchDeleteBtn = document.querySelector('.batch-actions-container .delete-btn');
const batchCancelBtn = document.querySelector('.batch-actions-container .cancel-btn');
const helpBtn = document.querySelector('.batch-actions-container .selected-help-btn');
const hintBox = document.querySelector('.batch-actions-container .multi-select-hint');
if (selectedItems.size > 0) {
if (container) container.classList.add('has-selection');
if (countSpan) {
countSpan.textContent = `已选 ${selectedItems.size} 项`;
countSpan.style.display = 'inline-block';
}
if (helpBtn) helpBtn.style.display = 'none';
if (hintBox) hintBox.style.display = 'none';
if (batchCopyBtn) batchCopyBtn.style.display = 'inline-block';
if (batchDeleteBtn) batchDeleteBtn.style.display = 'inline-block';
if (batchCancelBtn) batchCancelBtn.style.display = 'inline-block';
} else {
if (container) container.classList.remove('has-selection');
if (countSpan) {
countSpan.textContent = '已选 0 项';
countSpan.style.display = 'none';
}
if (helpBtn) helpBtn.style.display = 'inline-flex';
if (batchCopyBtn) batchCopyBtn.style.display = 'none';
if (batchDeleteBtn) batchDeleteBtn.style.display = 'none';
if (batchCancelBtn) batchCancelBtn.style.display = 'none';
if (hintBox && helpBtn) {
if (!helpBtn._bound) {
helpBtn.addEventListener('click', (e) => {
e.stopPropagation();
hintBox.style.display = hintBox.style.display === 'none' || hintBox.style.display === '' ? 'block' : 'none';
});
hintBox.addEventListener('click', (e) => {
e.stopPropagation();
});
document.addEventListener('click', (e) => {
if (!hintBox.contains(e.target) && e.target !== helpBtn && !helpBtn.contains(e.target)) {
hintBox.style.display = 'none';
}
});
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') {
hintBox.style.display = 'none';
}
});
helpBtn._bound = true;
}
}
}
}
function extractFileNameFromEd2k(ed2k) {
if (!ed2k) return '';
const match = ed2k.match(/ed2k:\/\/\|file\|([^|]+)\|/);
return match ? decodeURIComponent(match[1]) : '';
}
function batchCopySelected() {
const items = filteredItems;
const selectedItemsList = Array.from(selectedItems).map(index => items[index]).filter(Boolean);
if (selectedItemsList.length === 0) return;
const enableMagnetTitleCopy = GM_getValue('enableMagnetTitleCopy', false);
const copyText = selectedItemsList.map(item => {
if (item.magnet) {
if (enableMagnetTitleCopy && (item.shareTitle || item.note)) {
const title = item.shareTitle || item.note;
return `${title}\n${item.magnet}`;
} else {
let magnetToCopy = item.magnet;
if (!item.magnet.includes('?name=') && (item.shareTitle || item.note)) {
const title = item.shareTitle || item.note;
magnetToCopy = `${item.magnet}?name=${title}`;
}
return magnetToCopy;
}
} else if (item.ed2k) {
return item.ed2k;
} else {
const shareUrl = `https://115cdn.com/s/${item.shareCode}`;
const password = item.password || '';
const title = item.shareTitle || item.note || '';
const fullUrl = `${shareUrl}${password ? `?password=${password}` : ''}`;
const enableShareTitleCopy = GM_getValue('enableShareTitleCopy', false);
if (enableShareTitleCopy && title) {
return `${title}\n${fullUrl}`;
} else {
return title ? `${fullUrl}#\n${title}` : fullUrl;
}
}
}).join('\n\n');
navigator.clipboard.writeText(copyText).then(() => {
const batchCopyBtn = document.querySelector('.batch-actions-container .copy-btn');
if (batchCopyBtn) {
const originalText = batchCopyBtn.textContent;
const originalBackground = batchCopyBtn.style.backgroundColor;
batchCopyBtn.textContent = '已复制';
batchCopyBtn.style.backgroundColor = '#4caf50';
batchCopyBtn.style.color = 'white';
setTimeout(() => {
batchCopyBtn.textContent = originalText;
batchCopyBtn.style.backgroundColor = originalBackground;
batchCopyBtn.style.color = 'white';
}, 2000);
}
}).catch(err => {
console.error('复制失败:', err);
const batchCopyBtn = document.querySelector('.batch-actions-container .copy-btn');
if (batchCopyBtn) {
const originalText = batchCopyBtn.textContent;
const originalBackground = batchCopyBtn.style.backgroundColor;
batchCopyBtn.textContent = '复制失败';
batchCopyBtn.style.backgroundColor = '#f44336';
batchCopyBtn.style.color = 'white';
setTimeout(() => {
batchCopyBtn.textContent = originalText;
batchCopyBtn.style.backgroundColor = originalBackground;
batchCopyBtn.style.color = 'white';
}, 2000);
}
});
}
function batchCancelSelected() {
selectedItems.clear();
lastSelectedIndex = -1;
updateStorageItemSelection();
updateBatchActions();
}
function batchDeleteSelected() {
const items = filteredItems;
const selectedItemsList = Array.from(selectedItems).map(index => items[index]).filter(Boolean);
if (selectedItemsList.length === 0) return;
const enableDeleteConfirm = GM_getValue('enableDeleteConfirm', true);
if (enableDeleteConfirm) {
if (!confirm(`确定要删除选中的 ${selectedItemsList.length} 个项目吗?`)) return;
}
selectedItemsList.forEach(item => {
GM_deleteValue(generateStorageKey(item.shareCode, item.ed2k, item.magnet));
});
selectedItems.clear();
lastSelectedIndex = -1;
renderStorage(false);
}
function getShareCode() {
const url = window.location.href;
const match = url.match(/115cdn\.com\/s\/([a-z0-9]+)/);
return match ? match[1] : '';
}
function getPasswordFromUrl() {
const url = window.location.href;
const passwordMatch = url.match(/[?&]password=([^&#]*)/);
return passwordMatch ? decodeURIComponent(passwordMatch[1]) : '';
}
function checkCurrentUrlPassword() {
const shareCode = getShareCode();
const password = getPasswordFromUrl();
if (shareCode && password) {
try { autoFillPassword(password); } catch (e) {}
checkPasswordCorrect(shareCode, password, (isCorrect, data) => {
if (isCorrect) {
saveToStorage(shareCode, password, '', data?.shareTitle, data?.expireTime, data?.fileSize, data?.autoRenewal, '');
try { autoFillPassword(password); } catch (e) {}
}
});
}
}
async function getUserNameByUserId(userId) {
if (!userId || userId === '未知') {
return '未知';
}
try {
console.log(`正在获取用户ID ${userId} 的名称...`);
const response = await new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: 'GET',
url: `https://pmsg.115.com/api/1.0/web/1.0/friend/find?find_str=${userId}`,
onload: r => {
try {
console.log(`API响应状态: ${r.status}, 响应内容:`, r.responseText);
const data = JSON.parse(r.responseText);
resolve(data);
} catch(e) {
console.error('解析API响应失败:', e);
reject(e);
}
},
onerror: e => {
console.error('API请求失败:', e);
reject(e);
}
});
});
console.log('API响应数据:', response);
if (response.state && response.data && response.data.length > 0) {
const userName = response.data[0].friend_name || '未知';
console.log(`获取到用户名称: ${userName}`);
return userName;
} else {
console.log('API返回数据为空或格式不正确');
return '未知';
}
} catch (error) {
console.error('获取用户名称失败:', error);
return '未知';
}
}
async function fetchShareInfo() {
shareInfo.shareCode = getShareCode();
if (!shareInfo.shareCode) {
shareDetails.innerHTML = '当前URL不是有效的115分享链接';
return;
}
const apiUrl = `https://115cdn.com/webapi/share/snap?share_code=${shareInfo.shareCode}`;
GM_xmlhttpRequest({
method: 'GET',
url: apiUrl,
onload: async function(response) {
try {
const data = JSON.parse(response.responseText);
const showOwner = GM_getValue('enableShareOwnerInfo', false);
const shareState = data?.data?.shareinfo?.state;
if (data.state === false) {
const errorMsg = data.error || '未知错误';
const userId = showOwner ? (data.data?.userinfo?.user_id || '未知') : '';
const face = showOwner ? (data.data?.userinfo?.face || '') : '';
const userName = showOwner ? await getUserNameByUserId(userId) : '';
shareDetails.innerHTML = `
<div class="share-info">
${showOwner && face ? `<img src="${face}" class="share-avatar">` : ''}
<div class="share-text">
<span class="highlight-warning">${errorMsg}</span><br>
${showOwner ? `用户ID:<span class="highlight">${userId}</span><br>` : ''}
${showOwner ? `用户名:<span class="highlight">${userName}</span><br>` : ''}
分享链接:<span class="highlight">https://115cdn.com/s/${shareInfo.shareCode}</span><br>
</div>
</div>
`;
try { checkCurrentUrlPassword(); } catch (e) {}
try { checkStoredPassword(); } catch (e) {}
try { setupManualPasswordDetection(); } catch (e) {}
return;
}
if (shareState === -1) {
const errorMsg = '分享已取消';
const userId = showOwner ? (data.data?.userinfo?.user_id || '未知') : '';
const face = showOwner ? (data.data?.userinfo?.face || '') : '';
const userName = showOwner ? await getUserNameByUserId(userId) : '';
shareDetails.innerHTML = `
<div class="share-info">
${showOwner && face ? `<img src="${face}" class="share-avatar">` : ''}
<div class="share-text">
<span class="highlight-warning">${errorMsg}</span><br>
${showOwner ? `用户ID:<span class="highlight">${userId}</span><br>` : ''}
${showOwner ? `用户名:<span class="highlight">${userName}</span><br>` : ''}
分享链接:<span class="highlight">https://115cdn.com/s/${shareInfo.shareCode}</span><br>
</div>
</div>
`;
try { checkCurrentUrlPassword(); } catch (e) {}
try { checkStoredPassword(); } catch (e) {}
try { setupManualPasswordDetection(); } catch (e) {}
return;
}
if (showOwner) {
shareInfo.userId = data.data?.userinfo?.user_id || '未知';
shareInfo.face = data.data?.userinfo?.face || '';
}
shareInfo.isAccessible = true;
shareInfo.shareTitle = processShareTitle(data);
shareInfo.expireTime = data.data?.shareinfo?.expire_time || -1;
shareInfo.fileSize = parseInt(data.data?.shareinfo?.file_size || '0');
shareInfo.autoRenewal = String(data.data?.shareinfo?.auto_renewal || '0');
const receiveCodeElement = document.querySelector('em[rel="receive_code"]');
const receiveCode = receiveCodeElement ? receiveCodeElement.textContent.trim() : '未找到';
const userName = showOwner ? await getUserNameByUserId(shareInfo.userId) : '';
const formRow = document.querySelector('.form-row');
const btnGroup = document.querySelector('.btn-group');
const stats = document.querySelector('.stats');
try { if (formRow) formRow.style.display = 'none'; } catch (e) {}
try { if (btnGroup) btnGroup.style.display = 'none'; } catch (e) {}
try { if (stats) stats.style.display = 'none'; } catch (e) {}
shareDetails.innerHTML = `
<div class="share-info">
${showOwner && shareInfo.face ? `<img src="${shareInfo.face}" class="share-avatar">` : ''}
<div class="share-text">
${showOwner ? `用户ID:<span class="highlight">${shareInfo.userId}</span><br>` : ''}
${showOwner ? `用户名:<span class="highlight">${userName}</span><br>` : ''}
分享链接:<span class="highlight">https://115cdn.com/s/${shareInfo.shareCode}</span><br>
访问密码: <span class="highlight-success">${receiveCode}</span><br>
访问状态: <span class="highlight-success">无需访问码</span><br>
文件大小: <span class="highlight">${formatFileSize(shareInfo.fileSize)}</span><br>
${shareInfo.autoRenewal === '1' ? '<span class="auto-renewal-tag">自动续期</span>' : ''}
</div>
</div>
`;
saveToStorage(shareInfo.shareCode, receiveCode, '自动保存的访问码', shareInfo.shareTitle, shareInfo.expireTime, shareInfo.fileSize, shareInfo.autoRenewal, '');
autoFillPassword(receiveCode);
checkCurrentUrlPassword();
checkStoredPassword();
setupManualPasswordDetection();
} catch (e) {
shareDetails.innerHTML = '解析分享信息失败: ' + e.message;
}
},
onerror: function(error) {
shareDetails.innerHTML = '获取分享信息失败: ' + (error.error || '网络错误');
}
});
}
function setupManualPasswordDetection() {
const formDecode = document.querySelector('.form-decode');
if (!formDecode) return;
const input = formDecode.querySelector('.text');
const confirmBtn = formDecode.querySelector('.button.btn-large');
if (!input || !confirmBtn) return;
confirmBtn.addEventListener('click', function() {
const password = input.value.trim();
const shareCode = getShareCode();
if (!password || !shareCode) return;
checkPasswordCorrect(shareCode, password, (isCorrect, responseData) => {
if (isCorrect) {
const storageKey = generateStorageKey(shareCode, responseData?.ed2k, responseData?.magnet);
const storedData = GM_getValue(storageKey);
let note = '';
if (storedData) {
try {
const data = JSON.parse(storedData);
note = data.note || '';
} catch (e) {
console.error('解析存储数据失败:', e);
}
}
saveToStorage(shareCode, password, note, responseData?.shareTitle, responseData?.expireTime, responseData?.fileSize, String(responseData?.autoRenewal || '0'), responseData?.ed2k);
renderStorage(false);
if (statusDiv) {
statusDiv.innerHTML = `<div class="highlight-success">访问码已验证: ${password}</div>`;
statusDiv.classList.add('active');
}
}
});
});
}
let __autoConfirm = { key: null, inProgress: false, done: false };
function autoFillPassword(password) {
const enableAutoConfirm = GM_getValue('enableAutoConfirm', true);
if (!enableAutoConfirm) return;
let sc = '';
try { sc = getShareCode ? (getShareCode() || '') : ''; } catch (e) { sc = ''; }
const key = `${sc}|${password || ''}`;
if (__autoConfirm.key === key && (__autoConfirm.inProgress || __autoConfirm.done)) {
try { console.debug('[自动确认] 跳过重复执行', __autoConfirm); } catch (e) {}
return;
}
__autoConfirm.key = key;
__autoConfirm.inProgress = true;
__autoConfirm.done = false;
const tryOnceInRoot = (root) => {
const input = root.querySelector?.('.form-decode .text');
let confirmBtn = root.querySelector?.('.form-decode [btn="confirm"]');
if (!confirmBtn) confirmBtn = root.querySelector?.('.form-decode .button.btn-large');
if (input && confirmBtn) {
try { console.debug('[自动确认] 已找到输入框和确认按钮', { root, input, confirmBtn }); } catch (e) {}
input.focus();
try {
const win = input.ownerDocument?.defaultView || window;
const proto = Object.getOwnPropertyDescriptor(win.HTMLInputElement.prototype, 'value');
if (proto && proto.set) proto.set.call(input, password);
else input.value = password;
if (input._valueTracker) input._valueTracker.setValue('');
} catch (e) { input.value = password; }
input.dispatchEvent(new Event('input', { bubbles: true }));
input.dispatchEvent(new Event('change', { bubbles: true }));
input.dispatchEvent(new Event('blur', { bubbles: true }));
try { confirmBtn.classList.remove('btn-gray'); } catch (e) {}
try { confirmBtn.classList.remove('disabled'); } catch (e) {}
try { confirmBtn.classList.remove('z-dis'); } catch (e) {}
try { confirmBtn.removeAttribute('disabled'); } catch (e) {}
try { confirmBtn.setAttribute('aria-disabled', 'false'); } catch (e) {}
const isUnclickable = (btn) => {
try {
const cs = window.getComputedStyle(btn);
const disabledAttr = btn.hasAttribute('disabled') || btn.getAttribute('aria-disabled') === 'true' || btn.disabled === true;
const disabledClass = btn.classList.contains('disabled') || btn.classList.contains('z-dis') || btn.classList.contains('btn-gray');
const styleBlocked = cs && (cs.pointerEvents === 'none' || cs.display === 'none' || cs.visibility === 'hidden');
return disabledAttr || disabledClass || styleBlocked;
} catch (e) { return false; }
};
const doClickPrimary = (btn) => {
try { btn.click(); } catch (e) {}
try { console.debug('[自动确认] 执行 click() 一次', btn); } catch (e) {}
};
const doClickFallback = (btn) => {
try { btn.dispatchEvent(new PointerEvent('pointerdown', { bubbles: true })); } catch (e) {}
try { btn.dispatchEvent(new MouseEvent('mousedown', { bubbles: true })); } catch (e) {}
try { btn.dispatchEvent(new MouseEvent('mouseup', { bubbles: true })); } catch (e) {}
try { btn.dispatchEvent(new PointerEvent('pointerup', { bubbles: true })); } catch (e) {}
try { btn.click(); } catch (e) {}
try { console.debug('[自动确认] 已触发回退点击事件', btn); } catch (e) {}
};
setTimeout(() => {
if (isUnclickable(confirmBtn)) {
doClickFallback(confirmBtn);
} else {
doClickPrimary(confirmBtn);
}
__autoConfirm.done = true;
}, 150);
return true;
}
return false;
};
let tries = 0;
const maxTries = 80;
const timer = setInterval(() => {
tries++;
try { console.debug('[自动确认] 尝试次数', tries); } catch (e) {}
let ok = false;
try {
const tryDocAndShadows = (doc) => {
if (tryOnceInRoot(doc)) return true;
const all = doc.querySelectorAll('*');
for (const el of all) {
const sr = el.shadowRoot;
if (sr && tryOnceInRoot(sr)) return true;
}
return false;
};
ok = tryDocAndShadows(document);
if (!ok) {
const iframes = document.querySelectorAll('iframe');
for (const f of iframes) {
try {
const doc = f.contentDocument || f.contentWindow?.document;
if (doc && tryDocAndShadows(doc)) { ok = true; break; }
} catch (e) {}
}
}
} catch (e) {}
if (ok || tries >= maxTries) {
clearInterval(timer);
try { console.debug('[自动确认] 完成', { ok, tries }); } catch (e) {}
__autoConfirm.inProgress = false;
if (!ok) {
__autoConfirm.done = true;
}
}
}, 100);
}
function checkStoredPassword() {
const enableAutoConfirm = GM_getValue('enableAutoConfirm', true);
if (!enableAutoConfirm) {
return;
}
const shareCode = getShareCode();
if (!shareCode) return;
const storageKey = generateStorageKey(shareCode, shareInfo.ed2k, shareInfo.magnet);
const storedData = GM_getValue(storageKey);
if (storedData) {
try {
const data = JSON.parse(storedData);
autoFillPassword(data.password);
} catch (e) {
console.error('解析存储数据失败:', e);
}
}
}
function setupDrag(element, handle) {
let isDragging = false;
let startX, startY;
let translateX = 0, translateY = 0;
let moved = false;
const style = window.getComputedStyle(element);
const matrix = new DOMMatrix(style.transform);
translateX = matrix.m41;
translateY = matrix.m42;
const onMouseDown = (e) => {
if (e.button !== 0) return;
const tag = e.target.tagName;
if (["INPUT","TEXTAREA","BUTTON","SELECT"].includes(tag)) return;
isDragging = true;
moved = false;
startX = e.clientX;
startY = e.clientY;
element.style.cursor = 'grabbing';
element.style.transition = 'none';
e.preventDefault();
document.body.style.userSelect = 'none';
};
const onMouseMove = (e) => {
if (!isDragging) return;
const dx = e.clientX - startX;
const dy = e.clientY - startY;
if (Math.abs(dx) > 1 || Math.abs(dy) > 1) {
moved = true;
}
translateX += dx;
translateY += dy;
element.style.transform = `translate3d(${translateX}px, ${translateY}px, 0)`;
startX = e.clientX;
startY = e.clientY;
const rect = element.getBoundingClientRect();
if (rect.left < 0) {
translateX -= rect.left;
element.style.transform = `translate3d(${translateX}px, ${translateY}px, 0)`;
}
if (rect.top < 0) {
translateY -= rect.top;
element.style.transform = `translate3d(${translateX}px, ${translateY}px, 0)`;
}
if (rect.right > window.innerWidth) {
translateX -= (rect.right - window.innerWidth);
element.style.transform = `translate3d(${translateX}px, ${translateY}px, 0)`;
}
if (rect.bottom > window.innerHeight) {
translateY -= (rect.bottom - window.innerHeight);
element.style.transform = `translate3d(${translateX}px, ${translateY}px, 0)`;
}
};
const onMouseUp = () => {
if (!isDragging) return;
isDragging = false;
element.style.cursor = 'move';
element.style.transition = 'all 0.3s ease';
document.body.style.userSelect = '';
if (!moved && element === floatingBtn) {
floatingBtn.click();
}
};
handle.addEventListener('mousedown', onMouseDown);
document.addEventListener('mousemove', onMouseMove);
document.addEventListener('mouseup', onMouseUp);
return {
setPosition: (x, y) => {
translateX = x;
translateY = y;
element.style.transform = `translate3d(${translateX}px, ${translateY}px, 0)`;
}
};
}
function setupMaximizeButton() {
const maximizeBtn = windowElement.querySelector('.window-maximize');
const windowHeader = windowElement.querySelector('.window-header');
let isMaximized = false;
let originalPosition = { x: 0, y: 0 };
let originalSize = { width: 600, height: '' };
let originalTabContentHeight = {};
tabContents.forEach(tabContent => {
const tabName = tabContent.getAttribute('data-tab-content');
originalTabContentHeight[tabName] = tabContent.style.height;
});
maximizeBtn.title = '最大化';
function toggleMaximize() {
isMaximized = !isMaximized;
const storageTabContent = windowElement.querySelector('.storage-tab-content[data-tab-content="storage"]');
const storageContainer = windowElement.querySelector('#storage-container');
if (isMaximized) {
const style = window.getComputedStyle(windowElement);
const matrix = new DOMMatrix(style.transform);
originalPosition = { x: matrix.m41, y: matrix.m42 };
originalSize = {
width: parseInt(style.width),
height: windowElement.style.height
};
const activeTab = document.querySelector('.storage-tab-content.active');
if (activeTab) {
const tabName = activeTab.getAttribute('data-tab-content');
originalTabContentHeight[tabName] = activeTab.style.height;
}
windowElement.classList.add('maximized');
windowDrag.setPosition(0, 0);
maximizeBtn.title = '还原';
if (storageTabContent && storageContainer) {
const topHeight = storageTabContent.offsetTop + storageContainer.offsetTop;
storageContainer.style.maxHeight = `calc(100vh - ${topHeight + 40}px)`;
}
const batchShareFlex = document.querySelector('#batch-share-flex-row');
if (batchShareFlex) batchShareFlex.style.width = '100%';
const batchReceiveResult = document.querySelector('#batch-receive-result');
if (batchReceiveResult && batchReceiveResult.style.display !== 'none') {
batchReceiveResult.style.height = 'calc(100vh - 360px)';
batchReceiveResult.style.minHeight = '200px';
}
} else {
windowElement.classList.remove('maximized');
windowElement.style.width = `${originalSize.width}px`;
windowElement.style.height = originalSize.height;
windowDrag.setPosition(originalPosition.x, originalPosition.y);
maximizeBtn.title = '最大化';
if (storageContainer) {
storageContainer.style.maxHeight = '';
}
const batchShareFlex = document.querySelector('#batch-share-flex-row');
if (batchShareFlex) batchShareFlex.style.width = '';
const batchReceiveResult = document.querySelector('#batch-receive-result');
if (batchReceiveResult && batchReceiveResult.style.display !== 'none') {
batchReceiveResult.style.height = '';
batchReceiveResult.style.minHeight = '';
}
}
}
maximizeBtn.addEventListener('click', (e) => {
e.stopPropagation();
toggleMaximize();
});
windowHeader.addEventListener('dblclick', (e) => {
if (e.target.closest('button')) return;
toggleMaximize();
});
maximizeBtn.addEventListener('mouseover', () => {
maximizeBtn.title = isMaximized ? '还原' : '最大化';
});
}
if (performance.navigation.type === 1) {
localStorage.removeItem('batchRecognizeResults');
}
tabs.forEach(tab => {
tab.addEventListener('click', () => {
const tabName = tab.getAttribute('data-tab');
tabs.forEach(t => t.classList.remove('active'));
tabContents.forEach(c => c.classList.remove('active'));
tab.classList.add('active');
const activeTabContent = document.querySelector(`.storage-tab-content[data-tab-content="${tabName}"]`);
activeTabContent.classList.add('active');
if (tabName === 'settings') renderSettingsPage();
if (tabName === 'batchreceive') renderBatchReceivePage();
if (tabName === 'batchrecognize') {
const results = JSON.parse(localStorage.getItem('batchRecognizeResults') || '[]');
if (results.length > 0) {
const batchRecognizeContainer = document.getElementById('batch-recognize-container');
if (!batchRecognizeContainer.querySelector('#batch-recognize-input-container')) {
renderBatchRecognizePage();
} else {
const inputContainer = batchRecognizeContainer.querySelector('#batch-recognize-input-container');
if (inputContainer.style.display !== 'none') {
const controlsContainer = batchRecognizeContainer.querySelector('.batch-recognize-controls');
const settingsGroup = controlsContainer.querySelector('.batch-recognize-settings-group');
const progressWrap = batchRecognizeContainer.querySelector('#batch-recognize-progress');
const exportBtn = batchRecognizeContainer.querySelector('#batch-recognize-export-btn');
const startBtn = batchRecognizeContainer.querySelector('#batch-recognize-start-btn');
const backBtn = batchRecognizeContainer.querySelector('#batch-recognize-back-btn');
const resultDiv = batchRecognizeContainer.querySelector('#batch-recognize-result');
const progressText = batchRecognizeContainer.querySelector('#progress-text');
inputContainer.style.display = 'none';
if (settingsGroup) {
settingsGroup.style.display = 'none';
}
controlsContainer.style.display = 'flex';
resultDiv.style.display = 'block';
progressWrap.style.display = 'block';
exportBtn.style.display = 'inline-block';
startBtn.style.display = 'none';
backBtn.style.display = 'inline-block';
const successCount = results.filter(r => r.success).length;
const failedCount = results.filter(r => !r.success && !r.skipped).length;
const skippedCount = results.filter(r => r.skipped).length;
const totalItems = results.length;
const statusHtml = `<span class="batch-recognize-status-display">
<span class="status-label">总数: ${totalItems}项</span>
<span class="status-separator">|</span>
<span class="status-success">成功: ${successCount}项</span>
<span class="status-separator">|</span>
<span class="status-warning">跳过: ${skippedCount}项</span>
<span class="status-separator">|</span>
<span class="status-error">失败: ${failedCount}项</span>
</span>`;
progressText.innerHTML = statusHtml;
const showResult = () => {
resultDiv.style.display = 'block';
const windowElement = document.querySelector('.window');
const isMaximized = windowElement && windowElement.classList.contains('maximized');
if (isMaximized) {
resultDiv.style.height = 'calc(100vh - 360px)';
resultDiv.style.minHeight = '200px';
} else {
resultDiv.style.height = '';
}
const reversedResults = [...results].reverse();
resultDiv.innerHTML = reversedResults.map((r, index) => {
const originalIndex = results.length - 1 - index;
const shareLink = r.shareLink || (r.ed2k ? r.ed2k : `https://115cdn.com/s/${r.shareCode}?password=${r.password}`);
const title = r.title || r.shareTitle || '无标题';
const fileSizeTag = r.fileSize && r.fileSize > 0 ?
`<span class="batch-recognize-file-size">${formatFileSize(r.fileSize)}</span>` : '';
let statusClass = 'error';
let statusText = r.msg || '识别失败';
if (r.success) {
statusClass = 'success';
statusText = r.msg || '识别成功';
} else if (r.skipped) {
statusClass = 'warning';
statusText = r.msg || '已跳过';
}
return `<div class="batch-result-item compact-layout ${statusClass}" data-index="${originalIndex}">
<div class="batch-result-item-title">
<div>
${fileSizeTag}
<span class="batch-recognize-file-name" title="${title}">${title}</span>
</div>
<div class="batch-result-item-actions">
<button class="storage-item-btn copy-btn">复制</button>
${r.success ? '<button class="storage-item-btn open-btn">打开</button>' : ''}
</div>
</div>
<div class="batch-result-item-details">
<span class="batch-result-link" title="点击打开链接" data-link="${shareLink}">${shareLink}</span>
<span class="batch-result-status ${statusClass}">${statusText}</span>
</div>
</div>`;
}).join('');
reversedResults.forEach((r, displayIndex) => {
const originalIndex = results.length - 1 - displayIndex;
const item = resultDiv.querySelector(`[data-index="${originalIndex}"]`);
const copyBtn = item.querySelector('.copy-btn');
const linkSpan = item.querySelector('.batch-result-link');
const shareLink = r.shareLink || (r.ed2k ? r.ed2k : `https://115cdn.com/s/${r.shareCode}?password=${r.password}`);
const title = r.title || r.shareTitle || '无标题';
copyBtn.addEventListener('click', () => {
if (copyBtn._copyTimer) clearTimeout(copyBtn._copyTimer);
const text = r.ed2k ? r.ed2k : `${shareLink}#\n${title}`;
navigator.clipboard.writeText(text).then(() => {
copyBtn.textContent = '已复制';
copyBtn.classList.add('copied');
copyBtn._copyTimer = setTimeout(() => {
copyBtn.textContent = '复制';
copyBtn.classList.remove('copied');
copyBtn._copyTimer = null;
}, 1000);
}).catch(() => alert('复制失败'));
});
linkSpan.addEventListener('click', () => {
if (r.ed2k) {
navigator.clipboard.writeText(r.ed2k).then(() => {
alert('ED2K链接已复制到剪贴板');
}).catch(() => alert('复制失败'));
} else {
window.open(shareLink, '_blank');
}
});
const openBtn = item.querySelector('.open-btn');
if (openBtn) {
openBtn.addEventListener('click', () => {
if (r.ed2k) {
navigator.clipboard.writeText(r.ed2k).then(() => {
alert('ED2K链接已复制到剪贴板');
}).catch(() => alert('复制失败'));
} else {
window.open(shareLink, '_blank');
}
});
}
});
};
showResult();
}
}
} else {
renderBatchRecognizePage();
}
}
if (tabName === 'batchshare') renderBatchSharePage();
});
});
const btnDrag = setupDrag(floatingBtn, floatingBtn);
const windowDrag = setupDrag(windowElement, windowElement);
btnDrag.setPosition(0, 0);
windowDrag.setPosition(0, 0);
floatingBtn.addEventListener('click', (e) => {
if (e.defaultPrevented || e.target !== floatingBtn) return;
let count = 0;
try {
const iframe = document.querySelector('iframe');
const iframeWindow = iframe?.contentWindow || unsafeWindow;
const selectDOM = iframeWindow?.document?.querySelectorAll('div.list-contents > ul li.selected');
count = selectDOM ? selectDOM.length : 0;
} catch(e) { count = 0; }
if (count > 0) {
windowElement.style.display = 'block';
document.querySelectorAll('.storage-tab').forEach(tab => tab.classList.remove('active'));
document.querySelectorAll('.storage-tab-content').forEach(tab => tab.classList.remove('active'));
const batchTab = document.querySelector('.storage-tab[data-tab="batchshare"]');
const batchContent = document.querySelector('.storage-tab-content[data-tab-content="batchshare"]');
if (batchTab && batchContent) {
batchTab.classList.add('active');
batchContent.classList.add('active');
if (typeof renderBatchSharePage === 'function') renderBatchSharePage();
}
} else {
windowElement.style.display = 'block';
document.querySelectorAll('.storage-tab').forEach(tab => tab.classList.remove('active'));
document.querySelectorAll('.storage-tab-content').forEach(tab => tab.classList.remove('active'));
const storageTab = document.querySelector('.storage-tab[data-tab="storage"]');
const storageContent = document.querySelector('.storage-tab-content[data-tab-content="storage"]');
if (storageTab && storageContent) {
storageTab.classList.add('active');
storageContent.classList.add('active');
}
}
});
windowElement.querySelector('.window-close').addEventListener('click', () => {
windowElement.style.display = 'none';
});
let isRunning = false;
let isPaused = false;
let currentAttempt = 0;
let totalAttempts = 0;
let startTime = 0;
let pauseStartTime = 0;
let totalPausedTime = 0;
let currentPassword = '';
let triedPasswords = new Set();
let currentIndex = 0;
let activeRequests = 0;
let maxConcurrent = 10;
let correctPassword = null;
let currentStatus = 'stopped';
function setupProTagEdit() {
const proTag = windowElement.querySelector('.pro-tag');
const proTagInput = windowElement.querySelector('.pro-tag-input');
if (!proTag || !proTagInput) return;
const savedContent = localStorage.getItem('proTagCustomContent');
if (savedContent) {
proTag.textContent = savedContent;
proTagInput.value = savedContent;
}
function updateProTagStyle() {
const currentText = proTag.textContent.trim();
const defaultText = 'GreasyFork:[email protected]';
if (currentText.toLowerCase() === defaultText.toLowerCase()) {
proTag.classList.add('golden');
} else {
proTag.classList.remove('golden');
}
}
updateProTagStyle();
proTag.addEventListener('dblclick', () => {
proTag.style.display = 'none';
proTagInput.style.display = 'inline-block';
proTagInput.focus();
proTagInput.select();
});
const handleClickOutside = (e) => {
if (!proTagInput.contains(e.target) && !proTag.contains(e.target)) {
const newValue = proTagInput.value.trim();
if (newValue) {
proTag.textContent = newValue;
localStorage.setItem('proTagCustomContent', newValue);
} else {
const defaultValue = 'GreasyFork:[email protected]';
proTag.textContent = defaultValue;
proTagInput.value = defaultValue;
localStorage.removeItem('proTagCustomContent');
}
updateProTagStyle();
proTag.style.display = 'inline';
proTagInput.style.display = 'none';
document.removeEventListener('click', handleClickOutside);
}
};
proTagInput.addEventListener('focus', () => {
setTimeout(() => {
document.addEventListener('click', handleClickOutside);
}, 0);
});
proTagInput.addEventListener('blur', () => {
const newValue = proTagInput.value.trim();
if (newValue) {
proTag.textContent = newValue;
localStorage.setItem('proTagCustomContent', newValue);
} else {
const defaultValue = 'GreasyFork:[email protected]';
proTag.textContent = defaultValue;
proTagInput.value = defaultValue;
localStorage.removeItem('proTagCustomContent');
}
updateProTagStyle();
proTag.style.display = 'inline';
proTagInput.style.display = 'none';
document.removeEventListener('click', handleClickOutside);
});
proTagInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
proTagInput.blur();
}
});
proTagInput.addEventListener('keydown', (e) => {
if (e.key === 'Escape') {
const savedContent = localStorage.getItem('proTagCustomContent') || 'GreasyFork:[email protected]';
proTag.textContent = savedContent;
proTagInput.value = savedContent;
updateProTagStyle();
proTag.style.display = 'inline';
proTagInput.style.display = 'none';
document.removeEventListener('click', handleClickOutside);
}
});
}
function updateStatusTag() {
if (currentStatus === 'stopped') {
statusTagContainer.style.display = 'none';
return;
}
statusTagContainer.style.display = 'block';
const statusTags = {
'running': '<span class="valid-tag">验证中...</span>',
'paused': '<span class="batch-share-status-sharing">暂停中...</span>'
};
statusTagContainer.innerHTML = statusTags[currentStatus] || '';
}
function updateStatsInfo() {
const strategy = strategySelect.value;
const chars = strategy.includes('digits') ? '0123456789' : allChars;
totalAttempts = Math.pow(chars.length, 4);
statsInfo.innerHTML = `
访问码组合: <span class="highlight">${totalAttempts.toLocaleString()}</span> 种 (字符集: ${chars.length}个)<br>
<div class="status-info-item">${strategy.includes('digits') ? '0123456789' : allChars}</div>
`;
}
function validateCharsInput(input) {
charsError.textContent = '';
if (input === '') {
allChars = DEFAULT_CHARS;
updateStatsInfo();
return true;
}
const isDigitsMode = strategySelect.value.includes('digits');
if (isDigitsMode) {
const nonDigits = input.match(/[^0-9]/g);
if (nonDigits) {
charsError.textContent = `数字模式只允许数字,已移除字符: ${nonDigits.join(',')}`;
const filtered = input.replace(/[^0-9]/g, '');
charsInput.value = filtered;
allChars = filtered || '0123456789';
updateStatsInfo();
return false;
}
}
const uniqueChars = [...new Set(input.split(''))];
if (uniqueChars.length !== input.length) {
charsError.textContent = '已自动移除重复字符';
const uniqueStr = uniqueChars.join('');
charsInput.value = uniqueStr;
allChars = uniqueStr;
updateStatsInfo();
return true;
}
if (!isDigitsMode) {
const invalidChars = input.match(/[^0-9a-zA-Z]/g);
if (invalidChars) {
charsError.textContent = `已移除非法字符: ${invalidChars.join(',')} `;
const filtered = input.replace(/[^0-9a-zA-Z]/g, '');
charsInput.value = filtered;
allChars = filtered || DEFAULT_CHARS;
updateStatsInfo();
return false;
}
}
allChars = input;
updateStatsInfo();
return true;
}
function generateRandomPassword() {
let password = '';
const strategy = strategySelect.value;
const chars = strategy.includes('digits') ? '0123456789' : allChars;
for (let i = 0; i < 4; i++) password += chars[Math.floor(Math.random() * chars.length)];
return password;
}
function generateSequentialPassword(index) {
let password = '';
let temp = index;
const strategy = strategySelect.value;
const chars = strategy.includes('digits') ? '0123456789' : allChars;
for (let i = 0; i < 4; i++) {
password = chars[temp % chars.length] + password;
temp = Math.floor(temp / chars.length);
}
return password;
}
function checkPasswordCorrect(shareCode, password, callback, retryCount = 0) {
if (!shareCode) {
callback(false, { error: "缺少分享码" });
return;
}
const apiUrl = `https://115cdn.com/webapi/share/snap?share_code=${shareCode}&offset=0&limit=20&receive_code=${password}`;
activeRequests++;
GM_xmlhttpRequest({
method: 'GET',
url: apiUrl,
timeout: 10000,
onload: function(response) {
activeRequests--;
try {
const data = JSON.parse(response.responseText);
if (data.state === true) {
const shareState = data.data?.shareinfo?.share_state;
const forbidReason = data.data?.shareinfo?.forbid_reason;
if (forbidReason) {
const errorMsg = forbidReason;
callback(false, {
error: errorMsg,
errno: data.errno,
errtype: data.errtype,
data: data.data,
rawResponse: data
});
return;
}
const userId = data.data?.user_id || '';
const shareTitle = processShareTitle(data);
const expireTime = data.data?.shareinfo?.expire_time || -1;
const fileSize = parseInt(data.data?.shareinfo?.file_size || '0');
const autoRenewal = String(data.data?.shareinfo?.auto_renewal || '0');
callback(true, {
userId,
shareTitle,
expireTime,
fileSize,
autoRenewal,
rawResponse: data
});
} else {
if (data.error === "网络错误" && retryCount < 3) {
setTimeout(() => {
checkPasswordCorrect(shareCode, password, callback, retryCount + 1);
}, 1000 * (retryCount + 1));
} else {
const errorMsg = data.error || "未知错误";
let finalError = errorMsg;
const errnoVal = data.errno;
if (data.data && data.data.shareinfo) {
const shareState = data.data.shareinfo.share_state;
const forbidReason = data.data.shareinfo.forbid_reason;
if (forbidReason) {
finalError = forbidReason;
} else if (shareState === -1) {
finalError = "分享已取消";
}
}
if (finalError === errorMsg && (errnoVal === 4100010 || (typeof errorMsg === 'string' && errorMsg.includes('取消')))) {
finalError = '分享已取消';
}
callback(false, {
error: finalError,
errno: data.errno,
errtype: data.errtype,
data: data.data,
rawResponse: data
});
}
}
} catch (e) {
if (retryCount < 3) {
setTimeout(() => {
checkPasswordCorrect(shareCode, password, callback, retryCount + 1);
}, 1000 * (retryCount + 1));
} else {
callback(false, {
error: "解析响应失败: " + e.message,
rawResponse: response.responseText
});
}
}
},
onerror: function(error) {
activeRequests--;
if (retryCount < 3) {
setTimeout(() => {
checkPasswordCorrect(shareCode, password, callback, retryCount + 1);
}, 1000 * (retryCount + 1));
} else {
callback(false, {
error: "请求失败: " + (error.statusText || "网络错误"),
status: error.status
});
}
},
ontimeout: function() {
activeRequests--;
if (retryCount < 3) {
setTimeout(() => {
checkPasswordCorrect(shareCode, password, callback, retryCount + 1);
}, 1000 * (retryCount + 1));
} else {
callback(false, {
error: "请求超时",
status: 408
});
}
}
});
}
function formatTime(seconds, format = 'default') {
if (format === 'HHMMSS') {
const hours = Math.floor(seconds / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
const secs = Math.floor(seconds % 60);
return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
} else {
const days = Math.floor(seconds / 86400);
const hours = Math.floor((seconds % 86400) / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
const secs = Math.floor(seconds % 60);
const parts = [];
if (days > 0) parts.push(`${days}天`);
if (hours > 0 || days > 0) parts.push(`${hours}小时`);
if (minutes > 0 || hours > 0 || days > 0) parts.push(`${minutes}分`);
parts.push(`${secs}秒`);
return parts.join(' ');
}
}
function stopBruteForce() {
isRunning = false;
isPaused = false;
currentStatus = 'stopped';
pauseResumeBtn.textContent = '开始验证';
pauseResumeBtn.classList.remove('pause');
pauseResumeBtn.classList.remove('stop');
pauseResumeBtn.style.backgroundColor = '#4285f4';
const isDigitsMode = strategySelect.value.includes('digits');
charsInput.disabled = isRunning || isDigitsMode;
charsInput.classList.add('disabled-digits');
if (isDigitsMode) charsInput.classList.add('disabled-digits');
else {
charsInput.classList.remove('disabled-input');
charsInput.classList.remove('disabled-digits');
}
statusDiv.style.display = 'none';
updateStatusTag();
}
function togglePauseResume() {
if (!isRunning) {
checkStoredPasswordBeforeStart();
return;
}
if (isPaused) {
isPaused = false;
currentStatus = 'running';
charsInput.disabled = true;
charsInput.classList.add('disabled-input');
totalPausedTime += Date.now() - pauseStartTime;
pauseResumeBtn.textContent = '暂停验证';
pauseResumeBtn.classList.add('pause');
statusDiv.classList.add('active');
statusDiv.style.display = 'block';
tryNextBatch();
} else {
isPaused = true;
currentStatus = 'paused';
const isDigitsMode = strategySelect.value.includes('digits');
charsInput.disabled = isDigitsMode;
if (isDigitsMode) charsInput.classList.add('disabled-digits');
else {
charsInput.classList.remove('disabled-input');
charsInput.classList.remove('disabled-digits');
}
pauseStartTime = Date.now();
pauseResumeBtn.textContent = '继续验证';
pauseResumeBtn.classList.remove('pause');
}
updateStatusTag();
}
function checkStoredPasswordBeforeStart() {
if (!shareInfo.shareCode) {
startBruteForce();
return;
}
const storageKey = generateStorageKey(shareInfo.shareCode, shareInfo.ed2k, shareInfo.magnet);
const storedData = GM_getValue(storageKey);
if (storedData) {
try {
const data = JSON.parse(storedData);
statusDiv.innerHTML = '正在验证存储中的访问码...';
statusDiv.classList.add('active');
statusDiv.style.display = 'block';
checkPasswordCorrect(shareInfo.shareCode, data.password, (isCorrect, responseData) => {
if (isCorrect) {
correctPassword = data.password;
statusDiv.innerHTML = `<div class="highlight-success">访问码已验证成功: ${data.password}</div>`;
updateStatusTag();
} else {
statusDiv.innerHTML = '<div class="highlight-warning">存储中的访问码已失效,开始验证...</div>';
startBruteForce();
}
});
} catch (e) {
console.error('解析存储数据失败:', e);
startBruteForce();
}
} else {
startBruteForce();
}
}
function updateStatus() {
const elapsedTime = (isPaused ? pauseStartTime : Date.now()) - startTime - totalPausedTime;
const attemptsPerSecond = currentAttempt / (elapsedTime / 1000 || 1);
const remainingTime = (totalAttempts - currentAttempt) / (attemptsPerSecond || 1);
const remainingCombinations = totalAttempts - triedPasswords.size;
const strategy = strategySelect.value;
const chars = strategy.includes('digits') ? '0123456789' : allChars;
const progressPercent = (triedPasswords.size / totalAttempts) * 100;
const strategyNames = {
'random': '随机模式',
'sequential': '顺序模式',
'random-digits': '随机数字模式',
'sequential-digits': '顺序数字模式'
};
let statusHTML = `
<div class="network-status">
<span class="highlight"><strong>后台标签/最小化/退出浏览器</strong></span> 都有可能导致无法完整进行验证访问码
${navigator.onLine ? '' : '<div class="network-warning">警告: 当前网络连接不稳定</div>'}
</div>
<div class="progress-container">
<div class="progress-bar progress-bar-dynamic"></div>
</div>
<div class="status-info-grid">
<div class="status-info-column">
<div class="status-info-item"><strong>当前策略:</strong> <span class="highlight">${strategyNames[strategy]}</span></div>
<div class="status-info-item"><strong>当前验证:</strong> <span class="highlight">${currentPassword}</span></div>
<div class="status-info-item"><strong>已验证数:</strong> ${currentAttempt} 次 (${triedPasswords.size}个)</div>
<div class="status-info-item"><strong>并发数量:</strong> <span class="highlight">${activeRequests}</span> / ${maxConcurrent}</div>
<div class="status-info-item"><strong>剩余组合:</strong> <span class="highlight">${remainingCombinations.toLocaleString()}</span></div>
</div>
<div class="status-info-column">
<div class="status-info-item"><strong>当前进度:</strong> <span class="highlight">${progressPercent.toFixed(6)}%</span></div>
<div class="status-info-item"><strong>本机速度:</strong> ${attemptsPerSecond.toFixed(2)} 次/秒</div>
<div class="status-info-item"><strong>剩余时间:</strong> ${remainingTime > 0 ? formatTime(remainingTime) : '计算中...'}</div>
<div class="status-info-item"><strong>已用时间:</strong> ${formatTime(elapsedTime / 1000)}</div>
</div>
</div>
`;
if (correctPassword) {
statusHTML += `
<div class="correct-code">
<div class="correct-code-text">正确访问码: <strong>${correctPassword}</strong></div>
<div class="correct-code-actions">
<button class="batch-result-item-btn copy-btn">复制</button>
<button class="correct-code-btn fill-correct-btn">填入并确认</button>
</div>
</div>
<div class="fill-error" id="fill-error"></div>
`;
}
statusDiv.innerHTML = statusHTML;
const progressBar = statusDiv.querySelector('.progress-bar-dynamic');
if (progressBar) {
progressBar.style.setProperty('--progress-width', `${progressPercent}%`);
}
statusDiv.scrollTop = statusDiv.scrollHeight;
}
function tryNextBatch() {
if (!isRunning || isPaused || correctPassword) return;
if (triedPasswords.size >= totalAttempts) {
statusDiv.innerHTML += '<div class="batch-complete-message">所有组合已验证完毕,未找到正确访问码</div>';
stopBruteForce();
return;
}
const availableSlots = Math.min(maxConcurrent - activeRequests, maxConcurrent);
if (availableSlots <= 0) {
setTimeout(tryNextBatch, 100);
return;
}
const batch = [];
for (let i = 0; i < availableSlots; i++) {
let password;
const strategy = strategySelect.value;
if (strategy === 'sequential' || strategy === 'sequential-digits') {
password = generateSequentialPassword(currentIndex);
currentIndex++;
} else {
do {
password = generateRandomPassword();
} while (triedPasswords.has(password) && triedPasswords.size < totalAttempts);
if (triedPasswords.size >= totalAttempts) break;
}
if (!password) continue;
triedPasswords.add(password);
batch.push(password);
}
if (batch.length === 0) {
statusDiv.innerHTML += '<div class="batch-complete-message">所有组合已验证完毕,未找到正确访问码</div>';
stopBruteForce();
return;
}
let completed = 0;
for (const password of batch) {
currentPassword = password;
currentAttempt++;
updateStatus();
checkPasswordCorrect(shareInfo.shareCode, password, (isCorrect, responseData) => {
completed++;
if (isCorrect) {
correctPassword = password;
saveToStorage(shareInfo.shareCode, password, '验证成功的访问码', responseData.shareTitle, responseData.expireTime, responseData.fileSize, String(responseData.autoRenewal || '0'), '');
updateStatus();
updateStatusTag();
stopBruteForce();
return;
}
if (completed === batch.length && !correctPassword) setTimeout(tryNextBatch, 0);
});
}
updateStatus();
}
function startBruteForce() {
if (!validateCharsInput(charsInput.value)) return;
const isDigitsMode = strategySelect.value.includes('digits');
if (isDigitsMode) allChars = '0123456789';
else allChars = charsInput.value || DEFAULT_CHARS;
maxConcurrent = parseInt(concurrentInput.value) || 10;
if (maxConcurrent < 1) maxConcurrent = 1;
if (maxConcurrent > 10000) maxConcurrent = 10000;
concurrentInput.value = maxConcurrent;
correctPassword = null;
charsInput.disabled = true;
charsInput.classList.add('disabled-input');
statusDiv.classList.add('active');
statusDiv.style.display = 'block';
isRunning = true;
isPaused = false;
currentStatus = 'running';
pauseResumeBtn.textContent = '暂停验证';
pauseResumeBtn.classList.add('pause');
startTime = Date.now();
totalPausedTime = 0;
currentAttempt = 0;
currentIndex = 0;
triedPasswords.clear();
activeRequests = 0;
const strategy = strategySelect.value;
const chars = strategy.includes('digits') ? '0123456789' : allChars;
totalAttempts = Math.pow(chars.length, 4);
updateStatusTag();
tryNextBatch();
}
const FILTER_NAMES = {
'all': '全部',
'valid': '有效',
'longterm': '长期',
'renewal': '续期',
'timelimited': '限时',
'error': '错误',
'expired': '已过期',
'cancelled': '已取消',
'ed2k': 'ED2K',
'magnet': '磁力链'
};
function processLinkInfo(shareCode, shareTitle, note, ed2k, magnet) {
if (magnet) {
try {
const magnetMatch = magnet.match(/magnet:\?xt=urn:btih:([a-fA-F0-9]{40})(?:\?name=([^&]+))?/i);
if (magnetMatch) {
const magnetHash = magnetMatch[1];
const magnetName = magnetMatch[2] ? decodeURIComponent(magnetMatch[2]) : '';
shareCode = magnetHash;
if (!shareTitle && magnetName) {
shareTitle = magnetName;
}
if (!note && magnetName) {
note = magnetName;
}
}
} catch (e) {
console.error('处理磁力链出错:', e);
}
} else if (ed2k) {
try {
const ed2kParts = ed2k.match(/ed2k:\/\/\|file\|([^|]+)\|(\d+)\|([0-9A-F]{32})(?:\|h=([^|]+))?(\||\/)?/i);
if (ed2kParts) {
const safeFilename = ed2kParts[1].replace(/"/g, '\\"');
const ed2kHash = ed2kParts[3];
ed2k = `ed2k://|file|${safeFilename}|${ed2kParts[2]}|${ed2kHash}`;
if (ed2kParts[4]) {
ed2k += `|h=${ed2kParts[4]}|/`;
} else {
ed2k += '|/';
}
shareCode = ed2kHash;
}
} catch (e) {
console.error('处理ED2K链接出错:', e);
}
}
return { shareCode, shareTitle, note, ed2k };
}
function generateStorageKey(shareCode, ed2k, magnet) {
if (magnet) {
const magnetMatch = magnet.match(/magnet:\?xt=urn:btih:([a-fA-F0-9]{40})/i);
if (magnetMatch) {
return `share_magnet_${magnetMatch[1]}`;
}
}
if (ed2k) {
const ed2kMatch = ed2k.match(/ed2k:\/\/\|file\|[^|]+\|\d+\|([0-9A-F]{32})\|/i);
if (ed2kMatch) {
return `share_ed2k_${ed2kMatch[1]}`;
}
}
if (typeof shareCode === 'string') {
if (shareCode.startsWith('ed2k_')) {
shareCode = shareCode.slice(5);
} else if (shareCode.startsWith('magnet_')) {
shareCode = shareCode.slice(7);
}
}
if (magnet) {
return `share_magnet_${shareCode}`;
} else if (ed2k) {
return `share_ed2k_${shareCode}`;
} else {
return `share_115_${shareCode}`;
}
}
function saveToStorage(shareCode, password, note = '', shareTitle = '', expireTime = -1, fileSize = 0, autoRenewal = '0', ed2k = '', magnet = '', error = '', isUpdate = false, skipRender = false) {
let processedShareCode = shareCode;
let processedShareTitle = shareTitle;
let processedNote = note;
let processedEd2k = ed2k;
if (ed2k || magnet) {
const linkInfo = processLinkInfo(shareCode, shareTitle, note, ed2k, magnet);
processedShareCode = linkInfo.shareCode;
processedShareTitle = linkInfo.shareTitle;
processedNote = linkInfo.note;
processedEd2k = linkInfo.ed2k;
}
const storageKey = generateStorageKey(processedShareCode, processedEd2k, magnet);
const existingData = GM_getValue(storageKey);
let existingNote = processedNote;
let existingTimestamp = Date.now();
if (existingData) {
try {
const data = JSON.parse(existingData);
if (!processedNote && data.note) existingNote = data.note;
if (data.timestamp) existingTimestamp = data.timestamp;
} catch (e) {
console.error('解析存储数据失败:', e);
}
}
const data = {
shareCode: processedShareCode,
password,
note: existingNote,
shareTitle: processedShareTitle,
expireTime,
fileSize,
autoRenewal: String(autoRenewal || '0'),
ed2k: processedEd2k,
magnet,
error,
timestamp: existingTimestamp
};
GM_setValue(storageKey, JSON.stringify(data));
const oldIndex = allItems.findIndex(item => item.shareCode === processedShareCode);
if (oldIndex !== -1) {
allItems[oldIndex] = data;
} else {
allItems.push(data);
}
if (!skipRender) {
renderStorage(false);
clearItemCountsCache();
}
}
function updateStorageItem(shareCode, password, note = '', shareTitle = '', expireTime = -1, fileSize = 0, autoRenewal = '0', ed2k = '', magnet = '', error = '') {
return saveToStorage(shareCode, password, note, shareTitle, expireTime, fileSize, autoRenewal, ed2k, magnet, error, true);
}
function batchSaveToStorage(items) {
const newItems = [];
items.forEach(item => {
const data = {
shareCode: item.shareCode,
password: item.password,
note: item.note || '',
shareTitle: item.shareTitle || '',
expireTime: item.expireTime || -1,
fileSize: item.fileSize || 0,
autoRenewal: String(item.autoRenewal || '0'),
ed2k: item.ed2k || '',
magnet: item.magnet || '',
error: item.error || '',
timestamp: item.timestamp || Date.now()
};
const storageKey = generateStorageKey(data.shareCode, data.ed2k, data.magnet);
GM_setValue(storageKey, JSON.stringify(data));
newItems.push(data);
});
allItems.push(...newItems);
renderStorage(false);
clearItemCountsCache();
return newItems.length;
}
function getAllStorageItems() {
const items = [];
const keys = GM_listValues();
for (const key of keys) {
if (key.startsWith('share_ed2k_') || key.startsWith('share_115_') || key.startsWith('share_magnet_')) {
try {
const data = JSON.parse(GM_getValue(key));
if (data && typeof data.autoRenewal !== 'string') {
data.autoRenewal = String(data.autoRenewal || '0');
}
items.push(data);
} catch (e) {
console.error('解析存储数据失败:', key, e);
}
}
}
return items;
}
function sortItems(items, sortType) {
if (!Array.isArray(items)) {
return [];
}
switch(sortType) {
case 'time-desc':
return items.sort((a, b) => b.timestamp - a.timestamp);
case 'time-asc':
return items.sort((a, b) => a.timestamp - b.timestamp);
case 'name-asc':
return items.sort((a, b) => (a.shareTitle || '').localeCompare(b.shareTitle || ''));
case 'name-desc':
return items.sort((a, b) => (b.shareTitle || '').localeCompare(a.shareTitle || ''));
case 'size-desc':
return items.sort((a, b) => (b.fileSize || 0) - (a.fileSize || 0));
case 'size-asc':
return items.sort((a, b) => (a.fileSize || 0) - (b.fileSize || 0));
default:
return items.sort((a, b) => b.timestamp - a.timestamp);
}
}
function normalizeSearchTerm(term) {
return term.replace(/\b(\d+\.\d+)\s*([KMGT]?B)\b/gi, '$1$2')
.replace(/\b(\d+)\s*([KMGT]?B)\b/gi, '$1$2')
.replace(/\b(\d+)\s*([KMGT])\b/gi, '$1$2')
.toLowerCase();
}
function parseSearchLink(term) {
try {
if (!term || typeof term !== 'string') return null;
const str = term.trim();
if (!/[?&]password=/i.test(str) && str.indexOf('#') === -1) return null;
const shareCodeMatch = str.match(/\/s\/([^?#\/]+)/i);
const pwdMatch = str.match(/[?&]password=([0-9A-Za-z]{4})\b/i);
let title = null;
const hashIndex = str.indexOf('#');
if (hashIndex >= 0 && hashIndex < str.length - 1) {
const frag = str.slice(hashIndex + 1);
try {
title = decodeURIComponent(frag.replace(/\+/g, ' '));
} catch (e) {
title = frag;
}
}
let shareCode = shareCodeMatch ? shareCodeMatch[1] : null;
if (!shareCode) {
const qIndex = str.indexOf('?');
const preQ = qIndex >= 0 ? str.slice(0, qIndex) : str;
const lastSlash = preQ.lastIndexOf('/');
const seg = preQ.slice(lastSlash + 1).trim();
if (seg) shareCode = seg;
}
const password = pwdMatch ? pwdMatch[1] : null;
if (shareCode || password || title) {
return { shareCode, password, title };
}
return null;
} catch (e) {
return null;
}
}
function filterStorageItems(items) {
const filterCache = new Map();
const cacheKey = `${currentFilterType}_${currentSearchTerm}_${currentSearchType}`;
if (filterCache.has(cacheKey)) {
return filterCache.get(cacheKey);
}
let result = items;
const now = Math.floor(Date.now() / 1000);
if (currentFilterType === 'valid') {
result = items.filter(item => !item.error && (item.expireTime === -1 || item.expireTime > now) && !item.magnet);
} else if (currentFilterType === 'longterm') {
result = items.filter(item => item.expireTime === -1 && item.fileSize !== 0 && !item.ed2k && !item.magnet);
} else if (currentFilterType === 'renewal') {
result = items.filter(item => item.autoRenewal === '1' && !item.magnet);
} else if (currentFilterType === 'timelimited') {
result = items.filter(item => item.expireTime !== -1 && item.expireTime > now && item.autoRenewal !== '1' && !item.magnet);
} else if (currentFilterType === 'error') {
result = items.filter(item => item.error && !item.magnet && !(item.expireTime !== -1 && item.expireTime <= now));
} else if (currentFilterType === 'expired') {
result = items.filter(item => item.expireTime !== -1 && item.expireTime <= now && item.autoRenewal !== '1' && !item.magnet);
} else if (currentFilterType === 'cancelled') {
result = items.filter(item => item.expireTime === -1 && item.fileSize === 0 && item.shareTitle === '' && !item.magnet && !item.error);
} else if (currentFilterType === 'ed2k') {
result = items.filter(item => item.ed2k && item.ed2k !== '' && !item.magnet);
} else if (currentFilterType === 'magnet') {
result = items.filter(item => item.magnet && item.magnet !== '');
}
if (currentSearchTerm) {
const rawTerm = currentSearchTerm.trim();
const linkParts = parseSearchLink(rawTerm);
const toLower = (s) => (s || '').toLowerCase();
const keywords = linkParts
? [linkParts.shareCode, linkParts.password, linkParts.title].filter(Boolean).map(toLower)
: currentSearchTerm
.split(/\s+|,|,/)
.map(s => s.trim().toLowerCase())
.filter(Boolean);
const matchAny = !!linkParts;
const matchesKw = (item, kw) => {
if (currentSearchType === 'all') {
return (
(item.shareTitle && item.shareTitle.toLowerCase().includes(kw)) ||
(item.shareCode && item.shareCode.toLowerCase().includes(kw)) ||
(item.password && item.password.toLowerCase().includes(kw)) ||
(item.note && item.note.toLowerCase().includes(kw)) ||
(item.ed2k && item.ed2k.toLowerCase().includes(kw))
);
} else if (currentSearchType === 'title') {
return item.shareTitle && item.shareTitle.toLowerCase().includes(kw);
} else if (currentSearchType === 'shareCode') {
return item.shareCode && item.shareCode.toLowerCase().includes(kw);
} else if (currentSearchType === 'password') {
return item.password && item.password.toLowerCase().includes(kw);
} else if (currentSearchType === 'note') {
return item.note && item.note.toLowerCase().includes(kw);
} else if (currentSearchType === 'ed2k') {
return item.ed2k && item.ed2k.toLowerCase().includes(kw);
} else if (currentSearchType === 'magnet') {
return item.magnet && item.magnet.toLowerCase().includes(kw);
}
return false;
};
if (keywords.length > 0) {
result = result.filter(item => matchAny
? keywords.some(kw => matchesKw(item, kw))
: keywords.every(kw => matchesKw(item, kw))
);
}
}
filterCache.set(cacheKey, result);
return result;
}
let cachedItemCounts = null;
let lastCountUpdate = 0;
const COUNT_CACHE_DURATION = 5000;
function updateItemCounts() {
const now = Date.now();
if (cachedItemCounts && (now - lastCountUpdate) < COUNT_CACHE_DURATION) {
return cachedItemCounts;
}
const items = getAllStorageItems();
const currentTime = Math.floor(now / 1000);
cachedItemCounts = {
expired: items.filter(item => item.expireTime !== -1 && item.expireTime <= currentTime && item.autoRenewal !== '1').length,
cancelled: items.filter(item => item.expireTime === -1 && item.fileSize === 0 && item.shareTitle === '' && !item.magnet && !item.error).length,
error: items.filter(item => item.error && !(item.expireTime !== -1 && item.expireTime <= currentTime)).length
};
lastCountUpdate = now;
return cachedItemCounts;
}
function countExpiredItems() {
return updateItemCounts().expired;
}
function countCancelledItems() {
return updateItemCounts().cancelled;
}
function countErrorItems() {
return updateItemCounts().error;
}
function clearItemCountsCache() {
cachedItemCounts = null;
lastCountUpdate = 0;
}
function updateDeleteButtons() {
const expiredCount = countExpiredItems();
const cancelledCount = countCancelledItems();
const errorCount = countErrorItems();
if (currentFilterType === 'all' || currentFilterType === 'expired') {
if (expiredCount > 0) {
deleteExpiredBtn.style.display = 'block';
deleteExpiredBtn.innerHTML = `删除过期<span class="expired-count-badge">${expiredCount}</span>`;
} else {
deleteExpiredBtn.style.display = 'none';
}
deleteInvalidBtn.style.display = 'none';
deleteErrorBtn.style.display = 'none';
} else if (currentFilterType === 'cancelled') {
if (cancelledCount > 0) {
deleteInvalidBtn.style.display = 'block';
deleteInvalidBtn.innerHTML = `删除无效<span class="invalid-count-badge">${cancelledCount}</span>`;
} else {
deleteInvalidBtn.style.display = 'none';
}
deleteExpiredBtn.style.display = 'none';
deleteErrorBtn.style.display = 'none';
} else if (currentFilterType === 'error') {
if (errorCount > 0) {
deleteErrorBtn.style.display = 'block';
deleteErrorBtn.innerHTML = `删除错误<span class="error-count-badge">${errorCount}</span>`;
deleteErrorBtn.style.position = 'relative';
} else {
deleteErrorBtn.style.display = 'none';
}
deleteExpiredBtn.style.display = 'none';
deleteInvalidBtn.style.display = 'none';
} else {
deleteExpiredBtn.style.display = 'none';
deleteInvalidBtn.style.display = 'none';
deleteErrorBtn.style.display = 'none';
}
}
function renderStorage(resetScroll = true) {
const currentScrollTop = storageContainer.scrollTop;
const selectedItemsInfo = new Map();
if (selectedItems.size > 0) {
selectedItems.forEach(index => {
const item = filteredItems[index];
if (item) {
selectedItemsInfo.set(item.shareCode, true);
}
});
}
selectedItems.clear();
lastSelectedIndex = -1;
allItems = getAllStorageItems();
filteredItems = filterStorageItems(allItems);
filteredItems = sortItems(filteredItems, currentSortType);
clearVirtualScrollCache();
if (selectedItemsInfo.size > 0) {
filteredItems.forEach((item, index) => {
if (selectedItemsInfo.has(item.shareCode)) {
selectedItems.add(index);
lastSelectedIndex = index;
}
});
}
updateBatchActions();
let countText = '';
if (currentFilterType === 'all') {
countText = `共 ${filteredItems.length} 条`;
} else {
const totalCount = allItems.length;
countText = `共 ${filteredItems.length}/${totalCount} 条`;
}
if (filteredItems.length === 0) {
storageEmpty.style.display = 'block';
storageScrollContent.style.display = 'none';
searchInput.placeholder = `搜索... (${countText})`;
updateDeleteButtons();
return;
}
storageEmpty.style.display = 'none';
storageScrollContent.style.display = 'block';
searchInput.placeholder = `搜索... (${countText})`;
updateDeleteButtons();
updateScrollContentHeight();
if (resetScroll) {
scrollTop = 0;
storageContainer.scrollTop = 0;
renderStartIndex = 0;
const bufferSize = Math.max(10, Math.ceil(visibleItemCount * 0.5));
renderEndIndex = Math.min(filteredItems.length, visibleItemCount + bufferSize);
} else {
scrollTop = currentScrollTop;
storageContainer.scrollTop = currentScrollTop;
const bufferSize = Math.max(10, Math.ceil(visibleItemCount * 0.5));
renderStartIndex = Math.max(0, Math.floor(currentScrollTop / itemHeight) - bufferSize);
renderEndIndex = Math.min(filteredItems.length, renderStartIndex + visibleItemCount + bufferSize * 2);
}
renderVisibleItems();
}
function getFilterDescription() {
let description = `筛选条件: ${FILTER_NAMES[currentFilterType] || currentFilterType}`;
if (currentSearchTerm) {
description += ` | 搜索: "${currentSearchTerm}"`;
}
return description;
}
function refreshCurrentFilter() {
if (apiRefreshRunning) {
apiRefreshCancelled = true;
apiRefreshRunning = false;
apiRefreshBtn.title = 'API刷新';
return;
}
const hasSelection = selectedItems && selectedItems.size > 0;
const itemsToRefresh = hasSelection
? Array.from(selectedItems)
.sort((a, b) => a - b)
.map(i => filteredItems[i])
.filter(Boolean)
: filteredItems;
const refreshCount = itemsToRefresh.length;
const filterDesc = hasSelection
? `批量选择: 已选 ${refreshCount} 项`
: getFilterDescription();
if (refreshCount === 0) {
alert(`${filterDesc}\n\n当前筛选条件下没有可刷新的项目`);
return;
}
if (!confirm(`${filterDesc}\n\n确定要刷新${hasSelection ? '已选中的' : '当前筛选条件下的'} ${refreshCount} 个项目吗?`)) return;
apiRefreshCancelled = false;
apiRefreshRunning = true;
apiRefreshBtn.title = '停止刷新';
apiRefreshBadge.textContent = refreshCount;
let index = 0;
let processed = 0;
let successCount = 0;
let errorCount = 0;
const processNext = () => {
if (apiRefreshCancelled) return finish(true);
if (index >= itemsToRefresh.length) return finish(false);
const item = itemsToRefresh[index++];
if (item.ed2k || item.magnet) {
processed++;
updateBadge();
return setTimeout(processNext, 0);
}
checkPasswordCorrect(item.shareCode, item.password, (isCorrect, responseData) => {
processed++;
if (!apiRefreshCancelled) {
if (isCorrect) {
let newNote = item.note;
if (newNote === '未验证的访问码' && responseData?.shareTitle) newNote = responseData.shareTitle;
updateStorageItem(
item.shareCode,
item.password,
newNote,
responseData?.shareTitle || item.shareTitle,
responseData?.expireTime || item.expireTime,
responseData?.fileSize || item.fileSize,
String(responseData?.autoRenewal || '0'),
item.ed2k || '',
item.magnet || ''
);
successCount++;
} else if (
responseData?.error === "分享已取消" ||
(typeof responseData?.error === 'string' && responseData.error.includes('取消')) ||
responseData?.errno === 4100010 ||
(((responseData?.rawResponse?.data?.shareinfo) || (responseData?.data?.shareinfo) || {})?.share_state === -1)
) {
updateStorageItem(
item.shareCode,
item.password,
item.note,
'',
-1,
0,
'0',
item.ed2k || '',
item.magnet || ''
);
} else {
const shareInfo = responseData?.rawResponse?.data?.shareinfo || responseData?.data?.shareinfo || {};
const shareState = shareInfo?.share_state;
const forbidReason = shareInfo?.forbid_reason || responseData?.error || '';
if ((typeof forbidReason === 'string' && /过期/.test(forbidReason)) || shareState === 7) {
const newTitle = processShareTitle(responseData?.rawResponse || {});
updateStorageItem(
item.shareCode,
item.password,
item.note,
newTitle || item.shareTitle,
shareInfo?.expire_time || -1,
parseInt(shareInfo?.file_size || 0),
String(shareInfo?.auto_renewal || '0'),
item.ed2k || '',
item.magnet || '',
forbidReason || '分享已过期'
);
} else {
updateStorageItem(
item.shareCode,
item.password,
item.note,
item.shareTitle,
item.expireTime,
item.fileSize,
item.autoRenewal,
item.ed2k || '',
item.magnet || '',
responseData?.error || '验证失败'
);
errorCount++;
}
}
}
updateBadge();
setTimeout(processNext, 0);
});
};
const updateBadge = () => {
const remaining = refreshCount - processed;
apiRefreshBadge.textContent = remaining;
};
const finish = (stopped) => {
apiRefreshRunning = false;
apiRefreshCancelled = false;
apiRefreshBtn.title = 'API刷新';
apiRefreshBadge.textContent = '0';
if (stopped) {
alert(`${filterDesc}\n\n已停止刷新,已处理 ${processed} / ${refreshCount} 个项目`);
} else {
alert(`${filterDesc}\n\n刷新完成!\n成功刷新 ${successCount} 个项目\n失败 ${errorCount} 个项目`);
renderStorage(false);
}
};
processNext();
}
function exportToCSV() {
let filterDesc = `${FILTER_NAMES[currentFilterType] || currentFilterType}`;
if (currentSearchTerm) {
filterDesc += `_搜索"${currentSearchTerm.substring(0, 20)}"`;
}
filterDesc = filterDesc.replace(/[\/\\?%*:|"<>]/g, '');
const items = filteredItems;
const itemCount = items.length;
if (itemCount === 0) {
alert('当前没有数据可导出');
return;
}
const now = new Date();
const year = now.getFullYear();
const month = String(now.getMonth() + 1).padStart(2, '0');
const day = String(now.getDate()).padStart(2, '0');
const hours = String(now.getHours()).padStart(2, '0');
const minutes = String(now.getMinutes()).padStart(2, '0');
const seconds = String(now.getSeconds()).padStart(2, '0');
const fileName = `115存储数据_${filterDesc}_${itemCount}条_${year}${month}${day}_${hours}${minutes}${seconds}.csv`;
let csv = '标题,分享码/哈希,访问码,链接,大小,有效时间,备注\n';
items.forEach(item => {
const title = item.shareTitle || '无标题';
const codeOrHash = item.ed2k ?
item.ed2k.match(/ed2k:\/\/\|file\|[^|]+\|\d+\|([0-9A-F]{32})\|/i)?.[1] ||
item.shareCode :
item.shareCode;
const password = item.password || '无';
const fullLink = item.ed2k ? item.ed2k : `https://115cdn.com/s/${item.shareCode}${item.password ? `?password=${item.password}` : ''}`;
const fileSize = formatFileSize(item.fileSize);
const expireTime = item.expireTime === -1 ? '长期' : new Date(item.expireTime * 1000).toLocaleString();
const note = item.note || '';
const escapeCsv = (str) => {
if (str === null || str === undefined) return '""';
return `"${String(str).replace(/"/g, '""')}"`;
};
csv += [
escapeCsv(title),
escapeCsv(codeOrHash),
escapeCsv(password),
escapeCsv(fullLink),
escapeCsv(fileSize),
escapeCsv(expireTime),
escapeCsv(note)
].join(',') + '\n';
});
try {
const blob = new Blob(["\uFEFF" + csv], { type: 'text/csv;charset=utf-8;' });
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = fileName;
link.style.display = 'none';
document.body.appendChild(link);
link.click();
setTimeout(() => {
document.body.removeChild(link);
URL.revokeObjectURL(url);
}, 100);
} catch (e) {
console.error('导出CSV失败:', e);
alert('导出失败,请检查控制台错误信息');
}
}
function deleteInvalidItems() {
const cancelledItems = filteredItems.filter(item =>
item.expireTime === -1 && item.fileSize === 0 && item.shareTitle === ''
);
const cancelledCount = cancelledItems.length;
const filterDesc = getFilterDescription();
if (cancelledCount === 0) {
alert(`${filterDesc}\n\n当前筛选条件下没有找到已取消分享的项目`);
return;
}
if (confirm(`${filterDesc}\n\n确定要删除当前筛选条件下的 ${cancelledCount} 个已取消分享的项目吗?`)) {
let deletedCount = 0;
cancelledItems.forEach(item => {
GM_deleteValue(generateStorageKey(item.shareCode, item.ed2k, item.magnet));
deletedCount++;
});
renderStorage(false);
alert(`${filterDesc}\n\n已删除 ${deletedCount} 个已取消分享的项目`);
}
}
function importFromCSV() {
const importBtn = document.querySelector('#import-btn');
if (importBtn.dataset.importing === 'true') {
importBtn.dataset.importing = 'false';
importBtn.innerHTML = '导入数据<span class="import-badge">0</span>';
return;
}
const input = document.createElement('input');
input.type = 'file';
input.accept = '.csv';
input.addEventListener('change', (e) => {
const file = e.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = async (event) => {
try {
const content = event.target.result;
const lines = content.split('\n').filter(line => line.trim() !== '');
if (lines.length < 2) {
alert('CSV文件格式不正确');
return;
}
const header = lines[0].split(',');
const expectedHeaders = ['标题', '分享码/哈希', '访问码', '完整链接', '文件大小', '有效时间', '备注'];
if (!expectedHeaders.every(h => header.includes(h))) {
alert('CSV文件格式不正确,必须包含以下列:标题,分享码/哈希,访问码,完整链接,文件大小,有效时间,备注');
return;
}
importBtn.dataset.importing = 'true';
importBtn.innerHTML = '取消导入<span class="import-badge">0</span>';
const importBadge = importBtn.querySelector('.import-badge');
if (importBadge) {
importBadge.textContent = lines.length - 1;
importBadge.classList.add('show');
}
let isCancelled = false;
let importedCount = 0;
let skippedCount = 0;
let failedItems = [];
const totalLines = lines.length - 1;
const batchSize = 1000;
const startTime = Date.now();
function parseCsvLine(line) {
const values = [];
let current = '';
let inQuotes = false;
for (let i = 0; i < line.length; i++) {
const char = line[i];
if (char === '"') {
if (inQuotes && line[i + 1] === '"') {
current += '"';
i++;
} else {
inQuotes = !inQuotes;
}
} else if (char === ',' && !inQuotes) {
values.push(current);
current = '';
} else {
current += char;
}
}
values.push(current);
return values.map(v => v.replace(/^"|"$/g, ''));
}
function parseExpireTime(timeStr) {
if (!timeStr || timeStr.trim() === '长期') return -1;
const date = new Date(timeStr);
if (!isNaN(date.getTime())) return Math.floor(date.getTime() / 1000);
const timeParts = timeStr.match(/(\d+)天\s(\d+)小时\s(\d+)分\s(\d+)秒/) ||
timeStr.match(/(\d+)小时\s(\d+)分\s(\d+)秒/) ||
timeStr.match(/(\d+)分\s(\d+)秒/) ||
timeStr.match(/(\d+)秒/);
if (timeParts) {
let totalSeconds = 0;
if (timeParts.length === 5) {
totalSeconds = parseInt(timeParts[1]) * 86400 +
parseInt(timeParts[2]) * 3600 +
parseInt(timeParts[3]) * 60 +
parseInt(timeParts[4]);
} else if (timeParts.length === 4) {
totalSeconds = parseInt(timeParts[1]) * 3600 +
parseInt(timeParts[2]) * 60 +
parseInt(timeParts[3]);
} else if (timeParts.length === 3) {
totalSeconds = parseInt(timeParts[1]) * 60 +
parseInt(timeParts[2]);
} else if (timeParts.length === 2) {
totalSeconds = parseInt(timeParts[1]);
}
return Math.floor(Date.now() / 1000) + totalSeconds;
}
return -1;
}
function parseFileSize(sizeStr) {
if (!sizeStr) return 0;
const sizeMatch = sizeStr.match(/^(\d+(?:\.\d+)?)\s*([KMGT]?B)$/i);
if (sizeMatch) {
const sizeValue = parseFloat(sizeMatch[1]);
const sizeUnit = sizeMatch[2].toUpperCase();
const units = ['B', 'KB', 'MB', 'GB', 'TB'];
const unitIndex = units.indexOf(sizeUnit);
if (unitIndex !== -1) return Math.round(sizeValue * Math.pow(1024, unitIndex));
}
return 0;
}
const currentAllItems = getAllStorageItems();
for (let i = 1; i < lines.length; i += batchSize) {
if (importBtn.dataset.importing !== 'true') {
isCancelled = true;
break;
}
const batchLines = lines.slice(i, i + batchSize);
const batchStartTime = Date.now();
for (let j = 0; j < batchLines.length; j++) {
if (importBtn.dataset.importing !== 'true') {
isCancelled = true;
break;
}
const line = batchLines[j];
const values = parseCsvLine(line);
if (values.length >= 7) {
const title = values[0].trim();
const codeOrHash = values[1].trim();
const password = values[2].trim();
const fullLink = values[3].trim();
const fileSize = parseFileSize(values[4].trim());
const expireTime = parseExpireTime(values[5].trim());
const note = values[6].trim();
const isEd2k = fullLink.startsWith('ed2k://');
const shareCode = isEd2k ? codeOrHash : codeOrHash;
const enableImportSkip = GM_getValue('enableImportSkip', true);
const existingItem = currentAllItems.find(item =>
item.shareCode === shareCode &&
(isEd2k || item.password === password)
);
if (existingItem && enableImportSkip) {
skippedCount++;
} else if (isEd2k) {
saveToStorage(
shareCode,
'',
note,
title,
expireTime,
fileSize,
'0',
fullLink
);
importedCount++;
} else if (shareCode && password) {
const enableImportVerify = GM_getValue('enableImportVerify', false);
if (enableImportVerify) {
try {
await new Promise((resolve, reject) => {
checkPasswordCorrect(shareCode, password, (isCorrect, data) => {
if (isCorrect) {
saveToStorage(
shareCode,
password,
note,
data?.shareTitle || title,
data?.expireTime || expireTime,
data?.fileSize || fileSize,
data?.autoRenewal || '0'
);
importedCount++;
} else {
failedItems.push({
title,
shareCode,
password,
reason: '访问码验证失败'
});
}
resolve();
});
});
} catch (error) {
failedItems.push({
title,
shareCode,
password,
reason: '验证过程出错'
});
}
} else {
saveToStorage(
shareCode,
password,
note,
title,
expireTime,
fileSize,
'0'
);
importedCount++;
}
} else {
failedItems.push({
title,
shareCode,
password,
reason: '数据不完整'
});
}
const processedCount = i + j - 1;
const importBadge = importBtn.querySelector('.import-badge');
if (importBadge) {
const remainingCount = totalLines - processedCount;
importBadge.textContent = remainingCount;
if (remainingCount === 0) {
importBadge.classList.remove('show');
}
}
if (j % 10 === 0) await new Promise(resolve => setTimeout(resolve, 0));
}
}
const batchElapsedTime = Date.now() - batchStartTime;
if (batchElapsedTime < 50) await new Promise(resolve => setTimeout(resolve, 50 - batchElapsedTime));
}
importBtn.dataset.importing = 'false';
importBtn.innerHTML = '导入数据<span class="import-badge">0</span>';
if (!isCancelled) {
const enableImportSkip = GM_getValue('enableImportSkip', true);
const enableImportVerify = GM_getValue('enableImportVerify', false);
let resultMessage = `导入完成!成功 ${importedCount} 条`;
if (enableImportSkip) {
resultMessage += `,跳过 ${skippedCount} 条`;
}
if (failedItems.length > 0) {
resultMessage += `,失败 ${failedItems.length} 条`;
}
if (enableImportVerify) {
resultMessage += `(已启用验证)`;
}
alert(resultMessage);
} else {
alert('导入已取消');
}
renderStorage(false);
} catch (e) {
importBtn.dataset.importing = 'false';
importBtn.innerHTML = '导入数据<span class="import-badge">0</span>';
alert('导入失败: ' + e.message);
}
};
reader.readAsText(file);
});
input.click();
}
function deleteExpiredItems() {
const expiredCount = countExpiredItems();
if (expiredCount === 0) {
alert('没有找到已过期的项目');
return;
}
if (confirm(`确定要删除 ${expiredCount} 个已过期的项目吗?`)) {
const now = Math.floor(Date.now() / 1000);
const keys = GM_listValues();
let deletedCount = 0;
keys.forEach(key => {
if (key.startsWith('share_ed2k_') || key.startsWith('share_115_')) {
try {
const data = JSON.parse(GM_getValue(key));
if (data.expireTime !== -1 && data.expireTime <= now) {
GM_deleteValue(key);
deletedCount++;
}
} catch (e) {
console.error('解析存储数据失败:', key, e);
}
}
});
renderStorage(false);
alert(`已删除 ${deletedCount} 个已过期的项目`);
}
}
function deleteErrorItems() {
const errorItems = filteredItems.filter(item => item.error);
const errorCount = errorItems.length;
const filterDesc = getFilterDescription();
if (errorCount === 0) {
alert(`${filterDesc}\n\n当前筛选条件下没有找到错误项目`);
return;
}
if (confirm(`${filterDesc}\n\n确定要删除当前筛选条件下的 ${errorCount} 个错误项目吗?`)) {
let deletedCount = 0;
errorItems.forEach(item => {
GM_deleteValue(generateStorageKey(item.shareCode, item.ed2k, item.magnet));
deletedCount++;
});
renderStorage(false);
alert(`${filterDesc}\n\n已删除 ${deletedCount} 个错误项目`);
}
}
function clearStorage() {
const hasSearchOrFilter = currentSearchTerm || currentFilterType !== 'all';
let message;
let itemsToDelete;
if (hasSearchOrFilter) {
itemsToDelete = filteredItems.length;
message = `当前有${currentSearchTerm ? '搜索 "' + currentSearchTerm + '"' : ''}${
currentSearchTerm && currentFilterType !== 'all' ? ' 和 ' : ''
}${
currentFilterType !== 'all' ? '筛选 "' + document.querySelector(`button[data-filter="${currentFilterType}"]`).textContent + '"' : ''
}\n\n确定要删除当前显示的 ${itemsToDelete} 个项目吗?`;
} else {
itemsToDelete = allItems.length;
message = `确定要清空所有 ${itemsToDelete} 个存储数据吗?此操作不可恢复!`;
}
if (confirm(message)) {
if (hasSearchOrFilter) {
filteredItems.forEach(item => {
GM_deleteValue(generateStorageKey(item.shareCode, item.ed2k, item.magnet));
});
} else {
const keys = GM_listValues();
keys.forEach(key => {
if (key.startsWith('share_ed2k_') || key.startsWith('share_115_') || key.startsWith('share_magnet_')) GM_deleteValue(key);
});
}
renderStorage(false);
alert(`已删除 ${itemsToDelete} 个项目`);
}
}
function renderElementBlockSettings() {
elementBlockContainer.innerHTML = '';
const categories = {
'通用': elementBlockItems.filter(item => item.category === '通用'),
'分享页': elementBlockItems.filter(item => item.category === '分享页'),
'导航': elementBlockItems.filter(item => item.category === '导航')
};
Object.entries(categories).forEach(([categoryName, items]) => {
if (items.length === 0) return;
const sectionElement = document.createElement('div');
sectionElement.className = 'settings-section';
sectionElement.innerHTML = `
<div class="settings-section-title">
${categoryName}
<div class="category-toggle-container">
<label class="element-block-switch">
<input type="checkbox" class="category-toggle" data-category="${categoryName}">
<span class="element-block-slider"></span>
</label>
</div>
</div>
<div class="element-block-grid"></div>
`;
const grid = sectionElement.querySelector('.element-block-grid');
items.forEach(item => {
const itemElement = document.createElement('div');
itemElement.className = 'element-block-item';
itemElement.innerHTML = `
<div class="element-block-item-header">
<label class="element-block-switch">
<input type="checkbox" ${item.enabled ? 'checked' : ''}>
<span class="element-block-slider"></span>
</label>
<span class="element-block-name">${item.name}</span>
</div>
<div class="element-block-selector">${item.selector}</div>
`;
const checkbox = itemElement.querySelector('input');
checkbox.addEventListener('change', () => {
item.enabled = checkbox.checked;
saveElementBlockSettings();
executeElementBlock();
const section = itemElement.closest('.settings-section');
const categoryToggle = section.querySelector('.category-toggle');
const checkboxes = section.querySelectorAll('.element-block-grid input[type="checkbox"]');
const enabledCount = Array.from(checkboxes).filter(cb => cb.checked).length;
categoryToggle.checked = enabledCount === checkboxes.length;
categoryToggle.indeterminate = enabledCount > 0 && enabledCount < checkboxes.length;
});
grid.appendChild(itemElement);
});
const categoryToggle = sectionElement.querySelector('.category-toggle');
categoryToggle.addEventListener('change', () => {
const isChecked = categoryToggle.checked;
items.forEach(item => {
item.enabled = isChecked;
});
const checkboxes = sectionElement.querySelectorAll('.element-block-grid input[type="checkbox"]');
checkboxes.forEach(checkbox => {
checkbox.checked = isChecked;
});
saveElementBlockSettings();
executeElementBlock();
});
const enabledCount = items.filter(item => item.enabled).length;
categoryToggle.checked = enabledCount === items.length;
categoryToggle.indeterminate = enabledCount > 0 && enabledCount < items.length;
elementBlockContainer.appendChild(sectionElement);
});
}
function initElementBlock() {
initElementBlockSettings();
renderElementBlockSettings();
window.addEventListener('load', executeElementBlock);
}
async function extractShares() {
const extractBadge = extractBtn.querySelector('.extract-badge');
const originalText = extractBtn.innerHTML;
extractBtn.innerHTML = '<div class="internet-icon"></div>准备中...';
extractBtn.style.background = 'rgba(0,0,0,0.05)';
extractBtn.style.color = '#333';
extractBtn.style.transition = 'background 0.3s ease, color 0.3s ease';
extractBtn.disabled = true;
let totalShares = 0;
let processed = 0;
let savedCount = 0;
let skippedCount = 0;
let errorCount = 0;
try {
let offset = 0;
const limit = 1150;
let hasMore = true;
let allShares = [];
let fetchedCount = 0;
let estimatedTotal = 1000;
while (hasMore) {
const apiUrl = `https://webapi.115.com/share/slist?user_id=${shareInfo.userId}&offset=${offset}&limit=${limit}`;
try {
const response = await new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: 'GET',
url: apiUrl,
onload: resolve,
onerror: reject
});
});
const data = JSON.parse(response.responseText);
if (data.state === true && Array.isArray(data.list)) {
allShares = allShares.concat(data.list);
fetchedCount += data.list.length;
hasMore = data.list.length === limit;
offset += limit;
if (data.list.length === limit) {
estimatedTotal = Math.max(estimatedTotal, fetchedCount + limit);
}
const fetchProgress = Math.min((fetchedCount / estimatedTotal) * 100, 95);
extractBtn.innerHTML = `<div class="internet-icon"></div>获取中 ${fetchedCount} 个`;
extractBtn.style.background = `linear-gradient(90deg, #2196f3 ${fetchProgress}%, rgba(0,0,0,0.05) ${fetchProgress}%)`;
extractBtn.style.color = fetchProgress > 50 ? 'white' : '#333';
await new Promise(resolve => requestAnimationFrame(resolve));
} else {
hasMore = false;
}
} catch (e) {
hasMore = false;
console.error('获取分享列表失败:', e);
throw new Error(`获取分享列表失败: ${e.message}`);
}
}
totalShares = allShares.length;
if (totalShares === 0) {
throw new Error('没有找到任何分享内容');
}
extractBtn.innerHTML = `<div class="internet-icon"></div>准备完成 ${totalShares} 个`;
extractBtn.style.background = '#2196f3';
extractBtn.style.color = 'white';
await new Promise(resolve => setTimeout(resolve, 800));
extractBtn.innerHTML = `<div class="internet-icon"></div>开始导入...`;
extractBtn.style.background = '#ff9800';
extractBtn.style.color = 'white';
await new Promise(resolve => setTimeout(resolve, 300));
extractBtn.innerHTML = `<div class="internet-icon"></div>0/${totalShares}(0%)`;
extractBtn.style.background = 'rgba(0,0,0,0.05)';
extractBtn.style.color = '#333';
const existingShareCodes = new Set(allItems.map(item => item.shareCode));
const newShares = allShares.filter(share =>
!existingShareCodes.has(share.share_code) && share.receive_code
);
skippedCount = allShares.length - newShares.length;
const validShares = newShares.filter(share => share.receive_code);
errorCount = newShares.length - validShares.length;
const batchSize = 100;
const allNewItems = [];
for (let i = 0; i < validShares.length; i += batchSize) {
const batch = validShares.slice(i, i + batchSize);
const batchItems = batch.map(share => ({
shareCode: share.share_code,
password: share.receive_code,
note: '分享导入的内容',
shareTitle: processShareTitle({data: {shareinfo: share, list: share.list || []}}),
expireTime: share.share_ex_time || -1,
fileSize: parseInt(share.file_size || '0'),
autoRenewal: String(share.auto_renewal || '0'),
ed2k: '',
magnet: '',
error: '',
timestamp: Date.now()
}));
allNewItems.push(...batchItems);
savedCount += batchItems.length;
processed += batch.length;
const remainingCount = totalShares - processed;
const progressPercent = Math.round((processed / totalShares) * 100);
extractBtn.innerHTML = `<div class="internet-icon"></div>导入 ${processed}/${totalShares}(${progressPercent}%)`;
extractBtn.classList.add('extract-btn-progress');
extractBtn.style.setProperty('--progress-percent', `${progressPercent}%`);
extractBtn.classList.toggle('progress-high', progressPercent > 50);
extractBtn.classList.toggle('progress-low', progressPercent <= 50);
await new Promise(resolve => requestAnimationFrame(resolve));
}
if (allNewItems.length > 0) {
batchSaveToStorage(allNewItems);
const successMsg = `成功导入 ${savedCount} 个分享,跳过 ${skippedCount} 个已存在的分享,${errorCount} 个无访问码的分享`;
console.log(successMsg);
extractBtn.innerHTML = `<div class="internet-icon"></div>完成 ${savedCount} 个`;
extractBtn.classList.remove('extract-btn-progress', 'progress-high', 'progress-low');
extractBtn.style.removeProperty('--progress-percent');
extractBtn.style.background = '#4caf50';
extractBtn.style.color = 'white';
setTimeout(() => {
extractBtn.innerHTML = originalText;
extractBtn.style.background = 'rgba(0,0,0,0.05)';
extractBtn.style.color = '#333';
}, 3000);
}
} catch (error) {
console.error('导入分享失败:', error);
extractBtn.innerHTML = '<div class="internet-icon"></div>提取失败';
extractBtn.classList.remove('extract-btn-progress', 'progress-high', 'progress-low');
extractBtn.style.removeProperty('--progress-percent');
extractBtn.style.background = '#f44336';
extractBtn.style.color = 'white';
setTimeout(() => {
extractBtn.innerHTML = originalText;
extractBtn.style.background = 'rgba(0,0,0,0.05)';
extractBtn.style.color = '#333';
}, 3000);
} finally {
extractBtn.disabled = false;
extractBtn.classList.remove('extract-btn-progress', 'progress-high', 'progress-low');
extractBtn.style.removeProperty('--progress-percent');
extractBtn.style.background = 'rgba(0,0,0,0.05)';
extractBtn.style.color = '#333';
}
}
pauseResumeBtn.addEventListener('click', togglePauseResume);
stopBtn.addEventListener('click', stopBruteForce);
charsInput.addEventListener('input', () => validateCharsInput(charsInput.value));
strategySelect.addEventListener('change', function() {
const isDigitsMode = this.value.includes('digits');
if (isDigitsMode) {
if (!this.dataset.lastChars) this.dataset.lastChars = charsInput.value;
charsInput.value = '0123456789';
allChars = '0123456789';
charsInput.disabled = isRunning || isDigitsMode;
charsInput.classList.add('disabled-digits');
} else {
const lastChars = this.dataset.lastChars || DEFAULT_CHARS;
charsInput.value = lastChars;
allChars = lastChars;
charsInput.disabled = isRunning;
charsInput.classList.remove('disabled-input');
charsInput.classList.remove('disabled-digits');
}
validateCharsInput(charsInput.value);
updateStatsInfo();
if (isRunning || isPaused) {
const chars = this.value.includes('digits') ? '0123456789' : allChars;
totalAttempts = Math.pow(chars.length, 4);
}
});
concurrentInput.addEventListener('change', function() {
let value = parseInt(this.value) || 10;
if (value < 1) value = 1;
if (value > 10000) value = 10000;
this.value = value;
maxConcurrent = value;
updateStatsInfo();
});
exportBtn.addEventListener('click', exportToCSV);
importBtn.addEventListener('click', importFromCSV);
extractBtn.addEventListener('click', extractShares);
clearBtn.addEventListener('click', clearStorage);
apiRefreshBtn.addEventListener('click', refreshCurrentFilter);
refreshBtn.addEventListener('click', () => renderStorage(false));
deleteExpiredBtn.addEventListener('click', deleteExpiredItems);
deleteInvalidBtn.addEventListener('click', deleteInvalidItems);
const deleteErrorBtn = windowElement.querySelector('#delete-error-btn');
if (deleteErrorBtn) {
deleteErrorBtn.addEventListener('click', deleteErrorItems);
}
const batchCopyBtn = windowElement.querySelector('.batch-actions-container .copy-btn');
const batchDeleteBtn = windowElement.querySelector('.batch-actions-container .delete-btn');
const batchCancelBtn = windowElement.querySelector('.batch-actions-container .cancel-btn');
if (batchCopyBtn) {
batchCopyBtn.addEventListener('click', batchCopySelected);
}
if (batchDeleteBtn) {
batchDeleteBtn.addEventListener('click', batchDeleteSelected);
}
if (batchCancelBtn) {
batchCancelBtn.addEventListener('click', batchCancelSelected);
}
document.addEventListener('keydown', (e) => {
const storageTabContent = document.querySelector('.storage-tab-content[data-tab-content="storage"]');
if (!storageTabContent || !storageTabContent.classList.contains('active')) {
return;
}
if (e.ctrlKey && e.key === 'a') {
e.preventDefault();
selectedItems.clear();
for (let i = 0; i < filteredItems.length; i++) {
selectedItems.add(i);
}
lastSelectedIndex = filteredItems.length - 1;
updateStorageItemSelection();
updateBatchActions();
} else if (e.key === 'Escape') {
batchCancelSelected();
}
});
document.addEventListener('click', (e) => {
const storageTabContent = document.querySelector('.storage-tab-content[data-tab-content="storage"]');
if (!storageTabContent || !storageTabContent.classList.contains('active')) {
return;
}
const storageContainer = document.querySelector('#storage-container');
const sortButtons = document.querySelector('.sort-buttons');
if (!e.target.closest('.storage-item') &&
!e.target.closest('.sort-buttons') &&
!e.target.closest('.storage-container')) {
if (selectedItems.size > 0) {
batchCancelSelected();
}
}
const activeInputs = document.querySelectorAll('input[type="text"], input[type="password"], input[type="number"]');
activeInputs.forEach(input => {
if (input !== e.target && !input.contains(e.target)) {
if (input.style.display !== 'none' &&
input.style.display !== '' &&
input.classList.contains('storage-item-title-input') ||
input.classList.contains('storage-item-password-input') ||
input.classList.contains('storage-item-note-input') ||
input.classList.contains('storage-item-ed2k-input')) {
input.blur();
}
}
});
});
searchInput.addEventListener('input', () => {
clearTimeout(debounceTimer);
debounceTimer = setTimeout(() => {
currentSearchTerm = searchInput.value.trim();
updateSearchClearButton();
renderStorage();
}, 300);
});
const searchClearBtn = windowElement.querySelector('#search-clear-btn');
searchClearBtn.addEventListener('click', () => {
searchInput.value = '';
currentSearchTerm = '';
updateSearchClearButton();
renderStorage();
searchInput.focus();
});
function updateSearchClearButton() {
const hasContent = searchInput.value.trim().length > 0;
searchClearBtn.style.display = hasContent ? 'flex' : 'none';
}
searchType.addEventListener('change', () => {
currentSearchType = searchType.value;
if (currentSearchTerm) {
renderStorage();
}
});
filterButtons.forEach(btn => {
btn.addEventListener('click', () => {
currentFilterType = btn.dataset.filter;
filterButtons.forEach(b => b.classList.remove('active'));
btn.classList.add('active');
renderStorage();
});
});
sortButtons.forEach(btn => {
btn.addEventListener('click', () => {
currentSortType = btn.dataset.sort;
sortButtons.forEach(b => b.classList.remove('active'));
btn.classList.add('active');
renderStorage();
});
});
windowElement.addEventListener('click', (e) => {
if (e.target.classList.contains('copy-correct-btn') && correctPassword) {
navigator.clipboard.writeText(correctPassword).then(() => {
const btn = e.target;
const originalText = btn.textContent;
btn.textContent = '已复制';
setTimeout(() => {
btn.textContent = originalText;
}, 2000);
});
}
if (e.target.classList.contains('fill-correct-btn') && correctPassword) {
const input = document.querySelector('.form-decode .text');
const confirmBtn = document.querySelector('.form-decode .button.btn-large');
if (input && confirmBtn) {
input.value = correctPassword;
const event = new Event('input', { bubbles: true });
input.dispatchEvent(event);
confirmBtn.classList.remove('btn-gray');
confirmBtn.click();
} else {
const errorElement = document.getElementById('fill-error');
errorElement.textContent = '错误:找不到访问码输入框或确认按钮';
errorElement.style.display = 'block';
setTimeout(() => {
errorElement.style.display = 'none';
}, 3000);
}
}
});
updateStatsInfo();
updateStatusTag();
try {
const urlPwd = new URLSearchParams(location.search).get('password');
if (urlPwd) setTimeout(() => { try { autoFillPassword(urlPwd.trim()); } catch (e) {} }, 0);
} catch (e) {}
try {
window.addEventListener('DOMContentLoaded', () => {
try { checkCurrentUrlPassword(); } catch (e) {}
});
} catch (e) {}
fetchShareInfo();
renderStorage();
initElementBlock();
setupVirtualScroll();
setupMaximizeButton();
setupProTagEdit();
updateSearchClearButton();
updateTabVisibility();
const defaultSortBtn = windowElement.querySelector('.sort-buttons .storage-item-btn[data-sort="time-desc"]');
if (defaultSortBtn) {
defaultSortBtn.classList.add('active');
}
function renderSettingsPage() {
const enableCustomSave = GM_getValue('enableCustomSaveButton', true);
const enableAutoConfirm = GM_getValue('enableAutoConfirm', true);
const enableShareOwnerInfo = GM_getValue('enableShareOwnerInfo', false);
const enableDeleteConfirm = GM_getValue('enableDeleteConfirm', true);
const enableImportSkip = GM_getValue('enableImportSkip', true);
const enableImportVerify = GM_getValue('enableImportVerify', false);
const enableMagnetTitleCopy = GM_getValue('enableMagnetTitleCopy', false);
const enableShareTitleCopy = GM_getValue('enableShareTitleCopy', false);
const enableOfflineQuotaWidget = GM_getValue('enableOfflineQuotaWidget', true);
const enableVerifyTab = GM_getValue('enableVerifyTab', false);
const enableBatchReceiveTab = GM_getValue('enableBatchReceiveTab', true);
const enableElementBlockTab = GM_getValue('enableElementBlockTab', true);
const enableBatchRecognizeTab = GM_getValue('enableBatchRecognizeTab', true);
settingsBlockContainer.innerHTML = `
<!-- 通用功能 -->
<div class="settings-section">
<div class="settings-section-title">
通用功能
<div class="category-toggle-container">
<label class="element-block-switch">
<input type="checkbox" class="category-toggle" data-category="通用功能">
<span class="element-block-slider"></span>
</label>
</div>
</div>
<div class="element-block-grid">
<div class="element-block-item">
<div class="element-block-item-header element-block-item-header-relative">
<label class="element-block-switch">
<input type="checkbox" id="custom-save-switch" ${enableCustomSave ? 'checked' : ''}>
<span class="element-block-slider"></span>
</label>
<span class="element-block-name">转存按钮</span>
<span id="open-tab-icon" title="打开独立版脚本站点" class="open-tab-icon"></span>
</div>
<div class="element-block-selector">分享者进入自己的分享页显示"转存"按钮</div>
</div>
<div class="element-block-item">
<div class="element-block-item-header">
<label class="element-block-switch">
<input type="checkbox" id="auto-confirm-switch" ${enableAutoConfirm ? 'checked' : ''}>
<span class="element-block-slider"></span>
</label>
<span class="element-block-name">自动确认</span>
</div>
<div class="element-block-selector">分享页自动填写并确认访问码</div>
</div>
<div class="element-block-item">
<div class="element-block-item-header">
<label class="element-block-switch">
<input type="checkbox" id="share-owner-info-switch" ${enableShareOwnerInfo ? 'checked' : ''}>
<span class="element-block-slider"></span>
</label>
<span class="element-block-name">分享者信息</span>
</div>
<div class="element-block-selector">“访问码验证”显示分享者用户ID/用户名/头像(非必要建议关闭)</div>
</div>
<div class="element-block-item">
<div class="element-block-item-header">
<label class="element-block-switch">
<input type="checkbox" id="offline-quota-widget-switch" ${enableOfflineQuotaWidget ? 'checked' : ''}>
<span class="element-block-slider"></span>
</label>
<span class="element-block-name">离线额度显示</span>
</div>
<div class="element-block-selector">存储管理页右下角显示离线额度</div>
</div>
</div>
</div>
<!-- 存储功能 -->
<div class="settings-section">
<div class="settings-section-title">
存储功能
<div class="category-toggle-container">
<label class="element-block-switch">
<input type="checkbox" class="category-toggle" data-category="存储功能">
<span class="element-block-slider"></span>
</label>
</div>
</div>
<div class="element-block-grid">
<div class="element-block-item">
<div class="element-block-item-header">
<label class="element-block-switch">
<input type="checkbox" id="import-skip-switch" ${enableImportSkip ? 'checked' : ''}>
<span class="element-block-slider"></span>
</label>
<span class="element-block-name">导入跳过</span>
</div>
<div class="element-block-selector">导入时会跳过已存在的内容</div>
</div>
<div class="element-block-item">
<div class="element-block-item-header">
<label class="element-block-switch">
<input type="checkbox" id="import-verify-switch" ${enableImportVerify ? 'checked' : ''}>
<span class="element-block-slider"></span>
</label>
<span class="element-block-name">导入验证</span>
</div>
<div class="element-block-selector">导入时会通过API验证访问码有效性,ED2K、磁力链无需验证</div>
</div>
<div class="element-block-item">
<div class="element-block-item-header">
<label class="element-block-switch">
<input type="checkbox" id="share-title-copy-switch" ${enableShareTitleCopy ? 'checked' : ''}>
<span class="element-block-slider"></span>
</label>
<span class="element-block-name">115分享标题复制</span>
</div>
<div class="element-block-selector">复制115分享链接同时复制标题,格式为:标题+换行+分享链接</div>
</div>
<div class="element-block-item">
<div class="element-block-item-header">
<label class="element-block-switch">
<input type="checkbox" id="magnet-title-copy-switch" ${enableMagnetTitleCopy ? 'checked' : ''}>
<span class="element-block-slider"></span>
</label>
<span class="element-block-name">磁力链标题复制</span>
</div>
<div class="element-block-selector">复制磁力链同时复制标题,格式为:标题+换行+磁力链</div>
</div>
<div class="element-block-item">
<div class="element-block-item-header">
<label class="element-block-switch">
<input type="checkbox" id="harmonize-title-switch" ${GM_getValue('enableHarmonizeTitle', true) ? 'checked' : ''}>
<span class="element-block-slider"></span>
</label>
<span class="element-block-name">和谐分享标题</span>
</div>
<div class="element-block-selector">当分享标题包含3个以上***时,尝试从文件列表获取完整标题</div>
</div>
<div class="element-block-item">
<div class="element-block-item-header">
<label class="element-block-switch">
<input type="checkbox" id="delete-confirm-switch" ${enableDeleteConfirm ? 'checked' : ''}>
<span class="element-block-slider"></span>
</label>
<span class="element-block-name">删除提示</span>
</div>
<div class="element-block-selector">存储管理页删除按钮点击时会弹出确认提示</div>
</div>
</div>
</div>
<!-- 隐藏分页 -->
<div class="settings-section">
<div class="settings-section-title">
隐藏分页
<div class="category-toggle-container">
<label class="element-block-switch">
<input type="checkbox" class="category-toggle" data-category="隐藏分页">
<span class="element-block-slider"></span>
</label>
</div>
</div>
<div class="element-block-grid">
<div class="element-block-item">
<div class="element-block-item-header">
<label class="element-block-switch">
<input type="checkbox" id="batch-receive-tab-switch" ${enableBatchReceiveTab ? 'checked' : ''}>
<span class="element-block-slider"></span>
</label>
<span class="element-block-name">批量接收</span>
</div>
<div class="element-block-selector">显示批量接收分页</div>
</div>
<div class="element-block-item">
<div class="element-block-item-header">
<label class="element-block-switch">
<input type="checkbox" id="batch-recognize-tab-switch" ${enableBatchRecognizeTab ? 'checked' : ''}>
<span class="element-block-slider"></span>
</label>
<span class="element-block-name">批量识别</span>
</div>
<div class="element-block-selector">显示批量识别分页</div>
</div>
<div class="element-block-item">
<div class="element-block-item-header">
<label class="element-block-switch">
<input type="checkbox" id="verify-tab-switch" ${enableVerifyTab ? 'checked' : ''}>
<span class="element-block-slider"></span>
</label>
<span class="element-block-name">验证访问码</span>
</div>
<div class="element-block-selector">显示验证访问码分页</div>
</div>
<div class="element-block-item">
<div class="element-block-item-header">
<label class="element-block-switch">
<input type="checkbox" id="element-block-tab-switch" ${enableElementBlockTab ? 'checked' : ''}>
<span class="element-block-slider"></span>
</label>
<span class="element-block-name">元素屏蔽</span>
</div>
<div class="element-block-selector">显示元素屏蔽分页</div>
</div>
</div>
</div>
`;
const switchInput = settingsBlockContainer.querySelector('#custom-save-switch');
const autoConfirmSwitch = settingsBlockContainer.querySelector('#auto-confirm-switch');
const shareOwnerInfoSwitch = settingsBlockContainer.querySelector('#share-owner-info-switch');
const offlineQuotaWidgetSwitch = settingsBlockContainer.querySelector('#offline-quota-widget-switch');
const deleteConfirmSwitch = settingsBlockContainer.querySelector('#delete-confirm-switch');
const importSkipSwitch = settingsBlockContainer.querySelector('#import-skip-switch');
const harmonizeTitleSwitch = settingsBlockContainer.querySelector('#harmonize-title-switch');
const importVerifySwitch = settingsBlockContainer.querySelector('#import-verify-switch');
const magnetTitleCopySwitch = settingsBlockContainer.querySelector('#magnet-title-copy-switch');
const shareTitleCopySwitch = settingsBlockContainer.querySelector('#share-title-copy-switch');
const verifyTabSwitch = settingsBlockContainer.querySelector('#verify-tab-switch');
const batchReceiveTabSwitch = settingsBlockContainer.querySelector('#batch-receive-tab-switch');
const elementBlockTabSwitch = settingsBlockContainer.querySelector('#element-block-tab-switch');
const batchRecognizeTabSwitch = settingsBlockContainer.querySelector('#batch-recognize-tab-switch');
switchInput.addEventListener('change', function() {
GM_setValue('enableCustomSaveButton', this.checked);
if (this.checked) {
enableCustomSaveButtonFeature();
} else {
disableCustomSaveButtonFeature();
}
updateCategoryToggleState(this);
});
autoConfirmSwitch.addEventListener('change', function() {
GM_setValue('enableAutoConfirm', this.checked);
updateCategoryToggleState(this);
});
shareOwnerInfoSwitch.addEventListener('change', function() {
GM_setValue('enableShareOwnerInfo', this.checked);
updateCategoryToggleState(this);
});
offlineQuotaWidgetSwitch.addEventListener('change', function() {
GM_setValue('enableOfflineQuotaWidget', this.checked);
if (typeof updateQuotaWidgetVisibility === 'function') {
updateQuotaWidgetVisibility();
}
updateCategoryToggleState(this);
});
deleteConfirmSwitch.addEventListener('change', function() {
GM_setValue('enableDeleteConfirm', this.checked);
updateCategoryToggleState(this);
});
importSkipSwitch.addEventListener('change', function() {
GM_setValue('enableImportSkip', this.checked);
updateCategoryToggleState(this);
});
harmonizeTitleSwitch.addEventListener('change', function() {
GM_setValue('enableHarmonizeTitle', this.checked);
updateCategoryToggleState(this);
});
importVerifySwitch.addEventListener('change', function() {
GM_setValue('enableImportVerify', this.checked);
updateCategoryToggleState(this);
});
magnetTitleCopySwitch.addEventListener('change', function() {
GM_setValue('enableMagnetTitleCopy', this.checked);
updateCategoryToggleState(this);
});
shareTitleCopySwitch.addEventListener('change', function() {
GM_setValue('enableShareTitleCopy', this.checked);
updateCategoryToggleState(this);
});
verifyTabSwitch.addEventListener('change', function() {
GM_setValue('enableVerifyTab', this.checked);
updateTabVisibility();
updateCategoryToggleState(this);
});
batchReceiveTabSwitch.addEventListener('change', function() {
GM_setValue('enableBatchReceiveTab', this.checked);
updateTabVisibility();
updateCategoryToggleState(this);
});
elementBlockTabSwitch.addEventListener('change', function() {
GM_setValue('enableElementBlockTab', this.checked);
updateTabVisibility();
updateCategoryToggleState(this);
});
batchRecognizeTabSwitch.addEventListener('change', function() {
GM_setValue('enableBatchRecognizeTab', this.checked);
updateTabVisibility();
updateCategoryToggleState(this);
});
const categoryToggles = settingsBlockContainer.querySelectorAll('.category-toggle');
categoryToggles.forEach(toggle => {
toggle.addEventListener('change', function() {
const category = this.getAttribute('data-category');
const section = this.closest('.settings-section');
const checkboxes = section.querySelectorAll('.element-block-grid input[type="checkbox"]');
const isChecked = this.checked;
checkboxes.forEach(checkbox => {
checkbox.checked = isChecked;
checkbox.dispatchEvent(new Event('change'));
});
const enabledCount = Array.from(checkboxes).filter(cb => cb.checked).length;
this.checked = enabledCount === checkboxes.length;
this.indeterminate = enabledCount > 0 && enabledCount < checkboxes.length;
});
});
categoryToggles.forEach(toggle => {
const category = toggle.getAttribute('data-category');
const section = toggle.closest('.settings-section');
const checkboxes = section.querySelectorAll('.element-block-grid input[type="checkbox"]');
const enabledCount = Array.from(checkboxes).filter(cb => cb.checked).length;
toggle.checked = enabledCount === checkboxes.length;
toggle.indeterminate = enabledCount > 0 && enabledCount < checkboxes.length;
});
function updateCategoryToggleState(checkbox) {
const section = checkbox.closest('.settings-section');
const categoryToggle = section.querySelector('.category-toggle');
if (!categoryToggle) return;
const checkboxes = section.querySelectorAll('.element-block-grid input[type="checkbox"]');
const enabledCount = Array.from(checkboxes).filter(cb => cb.checked).length;
categoryToggle.checked = enabledCount === checkboxes.length;
categoryToggle.indeterminate = enabledCount > 0 && enabledCount < checkboxes.length;
}
const openTabIcon = settingsBlockContainer.querySelector('#open-tab-icon');
if (openTabIcon) {
openTabIcon.addEventListener('click', function(e) {
e.stopPropagation();
if (confirm('是否要进入独立版脚本站点?')) {
window.open('https://gf.qytechs.cn/zh-CN/scripts/543416', '_blank');
}
});
}
updateTabVisibility();
}
function updateTabVisibility() {
const enableVerifyTab = GM_getValue('enableVerifyTab', false);
const enableBatchReceiveTab = GM_getValue('enableBatchReceiveTab', true);
const enableElementBlockTab = GM_getValue('enableElementBlockTab', true);
const enableBatchRecognizeTab = GM_getValue('enableBatchRecognizeTab', true);
const verifyTab = document.querySelector('.storage-tab[data-tab="verify"]');
const batchReceiveTab = document.querySelector('.storage-tab[data-tab="batchreceive"]');
const elementBlockTab = document.querySelector('.storage-tab[data-tab="elementblock"]');
const batchRecognizeTab = document.querySelector('.storage-tab[data-tab="batchrecognize"]');
if (verifyTab) {
verifyTab.style.display = enableVerifyTab ? 'block' : 'none';
}
if (batchReceiveTab) {
batchReceiveTab.style.display = enableBatchReceiveTab ? 'block' : 'none';
}
if (elementBlockTab) {
elementBlockTab.style.display = enableElementBlockTab ? 'block' : 'none';
}
if (batchRecognizeTab) {
batchRecognizeTab.style.display = enableBatchRecognizeTab ? 'block' : 'none';
}
const activeTab = document.querySelector('.storage-tab.active');
if (activeTab && activeTab.style.display === 'none') {
const storageTab = document.querySelector('.storage-tab[data-tab="storage"]');
if (storageTab) {
storageTab.click();
}
}
}
let customSaveObserver = null;
function enableCustomSaveButtonFeature() {
if (window._customSaveButtonEnabled) return;
window._customSaveButtonEnabled = true;
const createSaveMenu = () => {
if (document.getElementById('custom-save-menu')) return document.getElementById('custom-save-menu');
const menu = document.createElement('div');
menu.className = 'context-menu';
menu.id = 'custom-save-menu';
menu.style.cssText = `display: none; position: absolute; top: 100%; left: 0; z-index: 999; min-width: 120px; background-color: #fff; box-shadow: 0 2px 10px rgba(0,0,0,0.2); border-radius: 4px;`;
menu.innerHTML = `<div class="cell-icon"><ul><li><a href="javascript:;" class="fast-save-link"><i class="icon-operate ifo-fastsaveto"></i><span>一键转存</span></a></li></ul></div>`;
document.body.appendChild(menu);
return menu;
};
const addSaveButton = () => {
const menuContainer = document.getElementById('js-menu');
const downloadButton = document.querySelector('a[btn="download"]');
if (!menuContainer || !downloadButton) return;
document.querySelectorAll('#custom-save-button').forEach(btn => btn.remove());
const saveButton = document.createElement('a');
saveButton.id = 'custom-save-button';
saveButton.setAttribute('href', 'javascript:;');
saveButton.setAttribute('btn', 'save');
saveButton.setAttribute('data-custom', 'true');
saveButton.className = 'button';
saveButton.style.position = 'relative';
saveButton.innerHTML = `<i class="icon-operate ifo-saveto"></i><span>转存</span><i class="ibco-arrow-solid"></i>`;
let saveMenu = document.getElementById('custom-save-menu') || createSaveMenu();
const fastSaveLink = saveMenu.querySelector('.fast-save-link');
menuContainer.insertBefore(saveButton, downloadButton);
const handleMouseEnter = () => {
if (saveButton.classList.contains('btn-disabled')) return;
const rect = saveButton.getBoundingClientRect();
saveMenu.style.display = 'block';
saveMenu.style.top = `${rect.bottom + window.scrollY}px`;
saveMenu.style.left = `${rect.left + window.scrollX}px`;
};
const handleMouseLeave = () => {
setTimeout(() => {
if (!saveMenu.matches(':hover') && !saveButton.matches(':hover')) {
saveMenu.style.display = 'none';
}
}, 100);
};
saveButton.addEventListener('mouseenter', handleMouseEnter);
saveButton.addEventListener('mouseleave', handleMouseLeave);
saveMenu.addEventListener('mouseleave', () => saveMenu.style.display = 'none');
fastSaveLink.addEventListener('click', (e) => {
e.stopPropagation();
e.preventDefault();
saveMenu.style.display = 'none';
const enterDownloadBox = document.getElementById('enter_download_box');
if (enterDownloadBox) enterDownloadBox.style.display = 'block';
});
saveButton.addEventListener('click', (e) => {
if (!e.target.closest('.fast-save-link')) {
const nativeSaveButton = document.querySelector('a[btn="save"]:not([data-custom])');
if (nativeSaveButton) {
nativeSaveButton.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }));
}
}
});
};
const checkAndAddSaveButton = () => {
const menuContainer = document.getElementById('js-menu');
if (!menuContainer) return;
const existingCustomButton = document.getElementById('custom-save-button');
if (existingCustomButton) {
existingCustomButton.style.display = '';
return;
}
const nativeSaveButton = document.querySelector('a[btn="save"]:not([data-custom])');
if (!nativeSaveButton || nativeSaveButton.style.display === 'none') {
addSaveButton();
}
};
const initObserver = () => {
const targetNode = document.getElementById('js-warp');
if (!targetNode) return;
customSaveObserver = new MutationObserver(() => checkAndAddSaveButton());
customSaveObserver.observe(targetNode, { childList: true, subtree: true });
};
window.addEventListener('load', () => {
checkAndAddSaveButton();
initObserver();
window._customSaveInterval = setInterval(checkAndAddSaveButton, 1000);
});
checkAndAddSaveButton();
initObserver();
window._customSaveInterval = setInterval(checkAndAddSaveButton, 1000);
}
function disableCustomSaveButtonFeature() {
window._customSaveButtonEnabled = false;
document.querySelectorAll('#custom-save-button').forEach(btn => btn.remove());
document.getElementById('custom-save-menu')?.remove();
if (customSaveObserver) {
customSaveObserver.disconnect();
customSaveObserver = null;
}
if (window._customSaveInterval) {
clearInterval(window._customSaveInterval);
window._customSaveInterval = null;
}
}
if (GM_getValue('enableCustomSaveButton', true)) {
enableCustomSaveButtonFeature();
}
function renderBatchReceivePage() {
function getBatchReceiveElements() {
return {
inputContainer: batchReceiveContainer.querySelector('#batch-receive-input-container'),
controlsMain: batchReceiveContainer.querySelector('.batch-receive-controls-main'),
controlsContainer: batchReceiveContainer.querySelector('.batch-receive-controls'),
resultDiv: batchReceiveContainer.querySelector('#batch-receive-result'),
progressWrap: batchReceiveContainer.querySelector('#batch-receive-progress'),
exportBtn: batchReceiveContainer.querySelector('#batch-receive-export-btn'),
backBtn: batchReceiveContainer.querySelector('#batch-receive-back-btn'),
startBtn: batchReceiveContainer.querySelector('#batch-receive-start-btn'),
progressBtn: batchReceiveContainer.querySelector('#batch-receive-progress-btn'),
textarea: batchReceiveContainer.querySelector('#batch-receive-textarea'),
cidInput: batchReceiveContainer.querySelector('#batch-receive-cid'),
cidSelect: batchReceiveContainer.querySelector('#batch-receive-cid-select'),
delayInput: batchReceiveContainer.querySelector('#batch-receive-delay'),
autoStorageCheckbox: batchReceiveContainer.querySelector('#batch-receive-auto-storage'),
progressBar: batchReceiveContainer.querySelector('#batch-receive-progress-bar'),
statusDiv: batchReceiveContainer.querySelector('#batch-receive-status')
};
}
function switchBatchReceiveUI(mode) {
const elements = getBatchReceiveElements();
switch(mode) {
case 'input':
elements.inputContainer?.classList.remove('batch-receive-container-hidden');
elements.inputContainer?.classList.add('batch-receive-container-visible');
elements.controlsMain?.classList.remove('batch-receive-container-hidden');
elements.controlsMain?.classList.add('batch-receive-container-visible');
elements.controlsContainer?.classList.remove('batch-receive-flex-hidden');
elements.controlsContainer?.classList.add('batch-receive-flex-visible');
elements.resultDiv?.classList.remove('batch-receive-container-visible');
elements.resultDiv?.classList.add('batch-receive-container-hidden');
elements.backBtn?.classList.remove('batch-receive-btn-visible');
elements.backBtn?.classList.add('batch-receive-btn-hidden');
elements.startBtn?.classList.remove('batch-receive-btn-hidden');
elements.startBtn?.classList.add('batch-receive-btn-visible');
elements.progressWrap?.classList.remove('batch-receive-container-visible');
elements.progressWrap?.classList.add('batch-receive-container-hidden');
elements.progressBtn?.classList.remove('batch-receive-btn-visible');
elements.progressBtn?.classList.add('batch-receive-btn-hidden');
elements.exportBtn?.classList.remove('batch-receive-btn-visible');
elements.exportBtn?.classList.add('batch-receive-btn-hidden');
break;
case 'progress':
elements.inputContainer?.classList.remove('batch-receive-container-visible');
elements.inputContainer?.classList.add('batch-receive-container-hidden');
elements.controlsMain?.classList.remove('batch-receive-container-visible');
elements.controlsMain?.classList.add('batch-receive-container-hidden');
elements.controlsContainer?.classList.remove('batch-receive-flex-visible');
elements.controlsContainer?.classList.add('batch-receive-flex-hidden');
elements.resultDiv?.classList.remove('batch-receive-container-hidden');
elements.resultDiv?.classList.add('batch-receive-container-visible');
elements.progressWrap?.classList.remove('batch-receive-container-hidden');
elements.progressWrap?.classList.add('batch-receive-container-visible');
break;
}
}
if (batchReceiveContainer.querySelector('#batch-receive-input-container')) {
const batchReceiveSettings = JSON.parse(localStorage.getItem('batchReceiveSettings') || '{}');
const elements = getBatchReceiveElements();
if (batchReceiveSettings.autoStorage !== undefined) {
elements.autoStorageCheckbox.checked = batchReceiveSettings.autoStorage;
}
if (batchReceiveSettings.cid) {
elements.cidInput.value = batchReceiveSettings.cid;
if (['0','100115'].includes(batchReceiveSettings.cid)) {
elements.cidSelect.value = batchReceiveSettings.cid;
} else {
elements.cidSelect.value = '';
}
}
return;
}
batchReceiveContainer.innerHTML = `
<div id="batch-receive-input-container">
<textarea id="batch-receive-textarea" class="modal-textarea" placeholder="请在此处粘贴需要接收的分享内容...
支持域名:
- 115.com | 115cdn.com | anxia.com
支持的格式:
• 完整链接:https://115.com/s/分享码?password=访问码
• 简化链接:分享码?password=访问码
• 移动端格式:/分享码-访问码/
• 分离格式:分享码 访问码
• 关键词格式:分享码 提取码: xxxx 或 分享码 密码: xxxx
• 访问码格式:访问码: xxxx
• 提取码格式:提取码: xxxx
• 密码格式:密码: xxxx
提示:每行一个分享内容,支持混合格式"></textarea>
</div>
<div id="batch-receive-result" class="batch-results-container"></div>
<div class="batch-receive-controls">
<div class="batch-receive-controls-main">
<div class="batch-receive-controls-row">
<div class="batch-receive-setting-item">
<label class="label">目录CID</label>
<div class="batch-receive-cid-controls">
<input type="text" id="batch-receive-cid" class="input" value="100115" pattern="[0-9]*" inputmode="numeric">
<select id="batch-receive-cid-select" class="input">
<option value="0">根目录</option>
<option value="100115" selected>最近接收</option>
</select>
</div>
</div>
<div class="batch-receive-setting-item">
<label class="label">延迟接收(ms)</label>
<input type="number" id="batch-receive-delay" class="input input-width-small" value="100" min="0" max="5000">
</div>
<div class="batch-receive-setting-item">
<label class="label">存储到管理页</label>
<span class="element-block-switch">
<input type="checkbox" id="batch-receive-auto-storage" checked>
<span class="element-block-slider"></span>
</span>
</div>
</div>
</div>
<button id="batch-receive-start-btn" class="btn-primary batch-receive-start-btn">开始接收</button>
<button id="batch-receive-back-btn" class="btn-primary batch-receive-start-btn btn-hidden">返回接收</button>
</div>
<div id="batch-receive-progress" class="progress-container batch-recognize-progress">
<div class="progress-header batch-recognize-progress-header">
<div>
<span class="progress-info">接收进度</span>
</div>
<button id="batch-receive-progress-btn" class="btn-primary batch-recognize-start-btn batch-recognize-progress-btn btn-visible">返回接收</button>
</div>
<div class="progress-bar-container batch-recognize-progress-bar-container">
<span id="batch-receive-progress-text"><span class="batch-recognize-progress-status">
<span class="status-label">目录: 最近接收</span>
<span class="status-separator">|</span>
<span class="status-label">存储: 开启</span>
<span class="status-separator">|</span>
<span class="status-progress">进度: 0/0 (0%)</span>
<span class="status-separator">|</span>
<span class="status-success">成功: 0</span>
<span class="status-separator">|</span>
<span class="status-failed">失败: 0</span>
</span></span>
<button id="batch-receive-export-btn" class="storage-import-export-btn batch-recognize-export-btn btn-hidden" data-text="导出结果">导出结果</button>
</div>
<div class="progress-bar" id="batch-receive-progress-bar"></div>
</div>
`;
const textarea = batchReceiveContainer.querySelector('#batch-receive-textarea');
const cidInput = batchReceiveContainer.querySelector('#batch-receive-cid');
const cidSelect = batchReceiveContainer.querySelector('#batch-receive-cid-select');
const startBtn = batchReceiveContainer.querySelector('#batch-receive-start-btn');
const backBtn = batchReceiveContainer.querySelector('#batch-receive-back-btn');
const progressBar = batchReceiveContainer.querySelector('#batch-receive-progress-bar');
const progressWrap = batchReceiveContainer.querySelector('#batch-receive-progress');
const statusDiv = batchReceiveContainer.querySelector('#batch-receive-status');
const resultDiv = batchReceiveContainer.querySelector('#batch-receive-result');
const exportBtn = batchReceiveContainer.querySelector('#batch-receive-export-btn');
const autoStorageCheckbox = batchReceiveContainer.querySelector('#batch-receive-auto-storage');
const progressBtn = batchReceiveContainer.querySelector('#batch-receive-progress-btn');
const delayInput = batchReceiveContainer.querySelector('#batch-receive-delay');
const inputContainerEl = batchReceiveContainer.querySelector('#batch-receive-input-container');
if (inputContainerEl && !inputContainerEl.querySelector('.clear-text-btn')) {
const clearBtn = document.createElement('button');
clearBtn.type = 'button';
clearBtn.className = 'clear-text-btn';
clearBtn.title = '清空';
clearBtn.innerHTML = `
<svg class="clear-brush-svg" width="16" height="16" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" fill="none" aria-hidden="true">
<g stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path fill="none" d="M10,4 C10,2.8954305 10.8954305,2 12,2 C13.1045695,2 14,2.8954305 14,4 L14,10 L20,10 L20,14 L4,14 L4,10 L10,10 L10,4 Z M4,14 L20,14 L20,22 L12,22 L4,22 L4,14 Z M16,22 L16,16.3646005 M8,22 L8,16.3646005 M12,22 L12,16.3646005"></path>
</g>
</svg>`;
clearBtn.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
if (textarea) {
textarea.value = '';
textarea.dispatchEvent(new Event('input', { bubbles: true }));
textarea.focus();
}
});
inputContainerEl.appendChild(clearBtn);
const updateClearVisibility = () => {
if (!textarea) return;
clearBtn.style.display = textarea.value.trim() ? 'inline-flex' : 'none';
};
updateClearVisibility();
const updateClearPosition = () => {
if (!textarea) return;
const scrollbarWidth = textarea.offsetWidth - textarea.clientWidth;
const baseRight = 6 + (scrollbarWidth > 0 ? scrollbarWidth : 0);
clearBtn.style.right = baseRight + 'px';
};
updateClearPosition();
if (textarea && !textarea._clearBound) {
textarea.addEventListener('input', () => {
updateClearVisibility();
updateClearPosition();
});
window.addEventListener('resize', updateClearPosition);
textarea._clearBound = true;
}
}
let isCancelled = false;
let totalItems = 0;
let processedItems = 0;
let successCount = 0;
let failedCount = 0;
function updateProgress() {
const progress = totalItems > 0 ? (processedItems / totalItems) * 100 : 0;
progressBar.style.width = `${progress}%`;
let directoryText = '最近接收';
if (cidSelect.value === '0') {
directoryText = '根目录';
} else if (cidSelect.value === '100115') {
directoryText = '最近接收';
} else if (cidInput.value && !['0', '100115'].includes(cidInput.value)) {
directoryText = `CID: ${cidInput.value}`;
}
const storageText = autoStorageCheckbox.checked ? '开启' : '关闭';
const delayText = `${delayInput.value}ms`;
const progressHtml = `<span class="batch-recognize-progress-status">
<span class="status-label">目录: ${directoryText}</span>
<span class="status-separator">|</span>
<span class="status-label">存储: ${storageText}</span>
<span class="status-separator">|</span>
<span class="status-label">延迟: ${delayText}</span>
<span class="status-separator">|</span>
<span class="status-progress">进度: ${processedItems}/${totalItems} (${Math.round(progress)}%)</span>
<span class="status-separator">|</span>
<span class="status-success">成功: ${successCount}</span>
<span class="status-separator">|</span>
<span class="status-failed">失败: ${failedCount}</span>
</span>`;
const progressText = batchReceiveContainer.querySelector('#batch-receive-progress-text');
if (progressText) {
progressText.innerHTML = progressHtml;
}
}
function saveBatchReceiveSettings() {
const settings = {
autoStorage: autoStorageCheckbox.checked,
cid: cidInput.value,
delay: delayInput.value
};
localStorage.setItem('batchReceiveSettings', JSON.stringify(settings));
}
autoStorageCheckbox.addEventListener('change', saveBatchReceiveSettings);
cidInput.addEventListener('input', saveBatchReceiveSettings);
const batchReceiveSwitch = batchReceiveContainer.querySelector('.element-block-switch');
if (batchReceiveSwitch) {
batchReceiveSwitch.addEventListener('click', (e) => {
const checkbox = batchReceiveSwitch.querySelector('input[type="checkbox"]');
if (checkbox) {
checkbox.checked = !checkbox.checked;
checkbox.dispatchEvent(new Event('change', { bubbles: true }));
}
});
}
let results = JSON.parse(localStorage.getItem('batchReceiveResults') || '[]');
function showSharedResult() {
const inputContainer = batchReceiveContainer.querySelector('#batch-receive-input-container');
if (inputContainer && inputContainer.style.display !== 'none') {
resultDiv.style.display = 'none';
return;
}
if (results && results.length > 0) {
showResult(true);
} else {
resultDiv.style.display = 'none';
}
}
function parseLine(line) {
let urlMatch = line.match(/https?:\/\/(?:115cdn\.com|anxia\.com|115\.com)\/s\/(\w+)(?:\?password=(\w+))?[#]?/i);
if (urlMatch) {
return {share_code: urlMatch[1], receive_code: urlMatch[2] || ''};
}
let simMatch = line.match(/(\w{8,20})\?password=(\w{4,20})/i);
if (simMatch) {
return {share_code: simMatch[1], receive_code: simMatch[2]};
}
let slashMatch = line.match(/^\/([a-zA-Z0-9]{8,20})-([a-zA-Z0-9]{4,10})\/$/);
if (slashMatch) {
return {share_code: slashMatch[1], receive_code: slashMatch[2]};
}
let txtMatch = line.match(/([a-zA-Z0-9]{8,20})\s+([a-zA-Z0-9]{4,10})/);
if (txtMatch) {
if (txtMatch[1].length >= txtMatch[2].length) {
return {share_code: txtMatch[1], receive_code: txtMatch[2]};
} else {
return {share_code: txtMatch[2], receive_code: txtMatch[1]};
}
}
let keyMatch = line.match(/([a-zA-Z0-9]{8,20}).*?(?:提取码|密码|code)[::]?\s*([a-zA-Z0-9]{4,10})/i);
if (keyMatch) {
return {share_code: keyMatch[1], receive_code: keyMatch[2]};
}
return null;
}
function parseMultiLineFormat(lines) {
const results = [];
let currentShareCode = '';
let currentReceiveCode = '';
let currentTitle = '';
for (let i = 0; i < lines.length; i++) {
const line = lines[i].trim();
if (!line) continue;
const urlMatch = line.match(/https?:\/\/(?:115cdn\.com|anxia\.com|115\.com)\/s\/(\w+)(?:\?password=(\w+))?[#]?/i);
if (urlMatch) {
if (currentShareCode && currentReceiveCode) {
results.push({
share_code: currentShareCode,
receive_code: currentReceiveCode,
title: currentTitle
});
}
currentShareCode = urlMatch[1];
currentReceiveCode = urlMatch[2] || '';
currentTitle = '';
continue;
}
const slashMatch = line.match(/^\/([a-zA-Z0-9]{8,20})-([a-zA-Z0-9]{4,10})\/$/);
if (slashMatch) {
if (currentShareCode && currentReceiveCode) {
results.push({
share_code: currentShareCode,
receive_code: currentReceiveCode,
title: currentTitle
});
}
currentShareCode = slashMatch[1];
currentReceiveCode = slashMatch[2];
currentTitle = '';
continue;
}
const codeMatch = line.match(/访问码[::]\s*([a-zA-Z0-9]{4,10})/i);
if (codeMatch) {
currentReceiveCode = codeMatch[1];
continue;
}
const extractMatch = line.match(/提取码[::]\s*([a-zA-Z0-9]{4,10})/i);
if (extractMatch) {
currentReceiveCode = extractMatch[1];
continue;
}
const passwordMatch = line.match(/密码[::]\s*([a-zA-Z0-9]{4,10})/i);
if (passwordMatch) {
currentReceiveCode = passwordMatch[1];
continue;
}
if (line.includes('.') && (line.includes('.mkv') || line.includes('.mp4') || line.includes('.avi') ||
line.includes('.mov') || line.includes('.wmv') || line.includes('.flv') ||
line.includes('.rmvb') || line.includes('.ts') || line.includes('.m4v'))) {
currentTitle = line;
continue;
}
const codeOnlyMatch = line.match(/^([a-zA-Z0-9]{4,20})$/);
if (codeOnlyMatch) {
const code = codeOnlyMatch[1];
if (code.length >= 8) {
if (!currentShareCode) {
currentShareCode = code;
}
} else {
if (!currentReceiveCode) {
currentReceiveCode = code;
}
}
continue;
}
}
if (currentShareCode && currentReceiveCode) {
results.push({
share_code: currentShareCode,
receive_code: currentReceiveCode,
title: currentTitle
});
}
return results;
}
function showBatchReceiveStatus(msg, color, stats = null) {
const progressText = batchReceiveContainer.querySelector('#batch-receive-progress-text');
if (progressText) {
let directoryText = '最近接收';
if (cidSelect.value === '0') {
directoryText = '根目录';
} else if (cidSelect.value === '100115') {
directoryText = '最近接收';
} else if (cidInput.value && !['0', '100115'].includes(cidInput.value)) {
directoryText = `CID: ${cidInput.value}`;
}
const storageText = autoStorageCheckbox.checked ? '开启' : '关闭';
const delayText = `${delayInput.value}ms`;
if (stats) {
const statsMatch = stats.match(/成功:\s*(\d+)\s*\|\s*失败:\s*(\d+)/);
if (statsMatch) {
const successCount = parseInt(statsMatch[1]);
const failCount = parseInt(statsMatch[2]);
const totalCount = successCount + failCount;
const progress = totalCount > 0 ? Math.round((successCount + failCount) / totalCount * 100) : 0;
const progressHtml = `<span class="batch-recognize-progress-status">
<span class="status-label">目录: ${directoryText}</span>
<span class="status-separator">|</span>
<span class="status-label">存储: ${storageText}</span>
<span class="status-separator">|</span>
<span class="status-label">延迟: ${delayText}</span>
<span class="status-separator">|</span>
<span class="status-progress">进度: ${successCount + failCount}/${totalCount} (${progress}%)</span>
<span class="status-separator">|</span>
<span class="status-success">成功: ${successCount}</span>
<span class="status-separator">|</span>
<span class="status-failed">失败: ${failCount}</span>
</span>`;
progressText.innerHTML = progressHtml;
} else {
progressText.innerHTML = `<span class="text-color-gray">${stats}</span>`;
}
} else {
progressText.innerHTML = `<span class="text-color-custom" style="--text-color: ${color||'#4285f4'};">${msg}</span>`;
}
}
}
function showResult(showExportButton = false) {
try {
const inputContainer = batchReceiveContainer.querySelector('#batch-receive-input-container');
if (inputContainer && !inputContainer.classList.contains('batch-receive-container-hidden')) {
resultDiv.classList.remove('batch-receive-container-visible');
resultDiv.classList.add('batch-receive-container-hidden');
return;
}
resultDiv.classList.remove('batch-receive-container-hidden');
resultDiv.classList.add('batch-receive-container-visible');
const isMaximized = windowElement.classList.contains('maximized');
if (isMaximized) {
resultDiv.classList.remove('batch-receive-result-dynamic');
resultDiv.classList.add('batch-receive-result-maximized');
} else {
resultDiv.classList.remove('batch-receive-result-maximized');
resultDiv.classList.add('batch-receive-result-dynamic');
}
resultDiv.innerHTML = results.map((r, index) => {
const shareLink = `https://115cdn.com/s/${r.share_code}?password=${r.receive_code}`;
const title = r.title || '无标题';
const fileSize = r.fileSize || 0;
const fileSizeTag = (r.success && fileSize > 0) ?
`<span class="batch-receive-file-size">${formatFileSize(fileSize)}</span>` : '';
let statusClass = 'error';
let statusText = r.msg || '接收失败';
if (r.success) {
statusClass = 'success';
statusText = r.msg || '接收成功';
}
return `<div class="batch-result-item compact-layout ${statusClass}" data-index="${index}">
<div class="batch-result-item-title">
<div>
${fileSizeTag}
<span class="batch-receive-file-name" title="${title}">${title}</span>
</div>
<div class="batch-result-item-actions">
<button class="storage-item-btn copy-btn">复制</button>
<button class="storage-item-btn open-btn ${r.success ? '' : 'btn-hidden'}">打开</button>
</div>
</div>
<div class="batch-result-item-details">
<span class="batch-result-link" title="点击打开链接" data-link="${shareLink}">${shareLink}</span>
<span class="batch-result-status ${statusClass}">${statusText}</span>
</div>
</div>`;
}).join('');
results.forEach((r, index) => {
const item = resultDiv.querySelector(`[data-index="${index}"]`);
const copyBtn = item.querySelector('.copy-btn');
const linkSpan = item.querySelector('.batch-result-link');
const shareLink = `https://115cdn.com/s/${r.share_code}?password=${r.receive_code}`;
const title = r.title || '无标题';
copyBtn.addEventListener('click', () => {
if (copyBtn._copyTimer) clearTimeout(copyBtn._copyTimer);
const text = `${shareLink}#\n${title}`;
navigator.clipboard.writeText(text).then(() => {
copyBtn.textContent = '已复制';
copyBtn.classList.add('copied');
copyBtn._copyTimer = setTimeout(() => {
copyBtn.textContent = '复制';
copyBtn.classList.remove('copied');
copyBtn._copyTimer = null;
}, 1000);
}).catch(() => alert('复制失败'));
});
linkSpan.addEventListener('click', () => {
window.open(shareLink, '_blank');
});
const openBtn = item.querySelector('.open-btn');
if (openBtn) {
openBtn.addEventListener('click', () => {
window.open(shareLink, '_blank');
});
}
});
if (results.length > 0) {
exportBtn.classList.remove('batch-receive-btn-hidden');
exportBtn.classList.add('batch-receive-btn-visible');
exportBtn.classList.remove('btn-hidden');
exportBtn.classList.add('btn-visible');
statusDiv.classList.remove('batch-receive-container-hidden');
statusDiv.classList.add('batch-receive-container-visible');
} else {
exportBtn.classList.remove('batch-receive-btn-visible');
exportBtn.classList.add('batch-receive-btn-hidden');
exportBtn.classList.add('btn-hidden');
exportBtn.classList.remove('btn-visible');
}
localStorage.setItem('batchReceiveResults', JSON.stringify(results));
} catch (error) {
console.error('showResult函数发生错误:', error);
}
}
async function batchReceive() {
const elements = getBatchReceiveElements();
const lines = elements.textarea.value.split('\n').map(l => l.trim()).filter(l => l);
const cid = elements.cidInput.value || '0';
const autoStorageChecked = elements.autoStorageCheckbox.checked;
if (!lines.length) {
showBatchReceiveStatus('请粘贴分享内容', '#f44336');
return;
}
switchBatchReceiveUI('progress');
elements.resultDiv.innerHTML = '<div class="text-color-gray batch-receive-ready">准备开始批量接收...</div>';
results = [];
progressWrap.classList.remove('batch-receive-container-hidden');
progressWrap.classList.add('batch-receive-container-visible');
exportBtn.classList.remove('batch-receive-btn-visible');
exportBtn.classList.add('batch-receive-btn-hidden');
totalItems = 0;
processedItems = 0;
successCount = 0;
failedCount = 0;
let validInfos = lines.map(line => ({line, info: parseLine(line)})).filter(obj => obj.info && obj.info.share_code && obj.info.receive_code);
if (validInfos.length === 0) {
const multiLineResults = parseMultiLineFormat(lines);
if (multiLineResults.length > 0) {
validInfos = multiLineResults.map(info => ({line: `${info.share_code} - ${info.receive_code}`, info}));
}
}
if (validInfos.length === 0) {
showBatchReceiveStatus('未识别到有效的分享链接或格式', '#f44336');
switchBatchReceiveUI('input');
return;
}
totalItems = validInfos.length;
updateProgress();
elements.startBtn.textContent = '取消接收';
isCancelled = false;
elements.progressBtn.classList.remove('batch-receive-btn-hidden');
elements.progressBtn.classList.add('batch-receive-btn-visible');
elements.progressBtn.textContent = '取消接收';
try {
for (let i = 0; i < validInfos.length; i++) {
if (isCancelled) break;
const {line, info} = validInfos[i];
let title = '';
let shareInfo = null;
let apiResp = null;
let fileSize = 0;
processedItems = i + 1;
updateProgress();
console.log(`处理第 ${i + 1}/${validInfos.length} 个: ${info.share_code}`);
try {
const apiUrl = `https://115cdn.com/webapi/share/snap?share_code=${info.share_code}&receive_code=${info.receive_code}`;
apiResp = await new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: 'GET',
url: apiUrl,
onload: r => {
try {
const data = JSON.parse(r.responseText);
resolve(data);
} catch(e) { reject(e); }
},
onerror: e => reject(e)
});
});
if (apiResp && apiResp.state && apiResp.data) {
if (apiResp.data.shareinfo) {
shareInfo = apiResp.data.shareinfo;
title = processShareTitle({data: {shareinfo: shareInfo, list: apiResp?.data?.list || []}});
fileSize = parseInt(shareInfo.file_size || '0');
} else if (apiResp.data) {
shareInfo = apiResp.data;
title = processShareTitle({data: {shareinfo: shareInfo, list: apiResp?.data?.list || []}});
fileSize = parseInt(shareInfo.file_size || '0');
}
}
} catch (e) {
}
try {
const resp = await new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: 'POST',
url: 'https://webapi.115.com/share/receive',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
data: `share_code=${encodeURIComponent(info.share_code)}&receive_code=${encodeURIComponent(info.receive_code)}&file_id=0&cid=${encodeURIComponent(cid)}`,
onload: r => {
try {
const data = JSON.parse(r.responseText);
resolve(data);
} catch(e) { reject(e); }
},
onerror: e => reject(e)
});
});
if (resp && resp.state) {
if (autoStorageChecked && shareInfo) {
let userId = '未知';
if (apiResp && apiResp.data && apiResp.data.userinfo) {
userId = apiResp.data.userinfo.user_id || '未知';
} else if (shareInfo.user_id) {
userId = shareInfo.user_id;
}
const shareTitle = shareInfo.share_title || '';
const expireTime = parseInt(shareInfo.expire_time || '-1');
const autoRenewal = String(shareInfo.auto_renewal || '0');
const note = shareTitle ? `[批量接收] ${shareTitle}` : '[批量接收]';
saveToStorage(
info.share_code,
info.receive_code,
note,
shareTitle,
expireTime,
fileSize,
autoRenewal
);
}
results.push({share_code: info.share_code, receive_code: info.receive_code, title, success: true, msg: '转存成功', fileSize: fileSize});
successCount++;
updateProgress();
} else if (resp && typeof resp.error === 'string') {
results.push({share_code: info.share_code, receive_code: info.receive_code, title, success: false, msg: resp.error, fileSize: fileSize});
failedCount++;
updateProgress();
} else {
results.push({share_code: info.share_code, receive_code: info.receive_code, title, success: false, msg: '未知错误', fileSize: fileSize});
failedCount++;
updateProgress();
}
} catch (e) {
results.push({share_code: info.share_code, receive_code: info.receive_code, title, success: false, msg: '网络/解析错误', fileSize: fileSize});
failedCount++;
updateProgress();
}
showResult(true);
console.log(`完成第 ${i + 1}/${validInfos.length} 个处理`);
const delay = parseInt(delayInput.value) || 100;
await new Promise(res=>setTimeout(res, delay));
}
console.log(`循环结束,总共处理了 ${processedItems} 个项目`);
} catch (error) {
console.error('批量接收过程中发生错误:', error);
showBatchReceiveStatus(`处理过程中发生错误: ${error.message}`, '#f44336');
}
progressBtn.textContent = '返回接收';
progressBtn.disabled = false;
progressBtn.style.opacity = '';
progressBtn.style.cursor = '';
progressBtn.classList.remove('batch-receive-btn-hidden');
progressBtn.classList.add('batch-receive-btn-visible');
backBtn.classList.remove('batch-receive-btn-hidden');
backBtn.classList.add('batch-receive-btn-visible');
startBtn.classList.remove('batch-receive-btn-visible');
startBtn.classList.add('batch-receive-btn-hidden');
controlsContainer.classList.remove('batch-receive-flex-visible');
controlsContainer.classList.add('batch-receive-flex-hidden');
controlsMain.classList.remove('batch-receive-container-visible');
controlsMain.classList.add('batch-receive-container-hidden');
if (isCancelled) {
showBatchReceiveStatus(`已取消!共处理${results.length}条`, '#f44336', `成功: ${successCount} | 失败: ${failedCount}`);
exportBtn.classList.remove('batch-receive-btn-hidden');
exportBtn.classList.add('batch-receive-btn-visible');
exportBtn.classList.remove('btn-hidden');
exportBtn.classList.add('btn-visible');
} else {
showBatchReceiveStatus(`完成!共${validInfos.length}条`, successCount===validInfos.length?'#4caf50':'#f44336', `成功: ${successCount} | 失败: ${failedCount}`);
exportBtn.classList.remove('batch-receive-btn-hidden');
exportBtn.classList.add('batch-receive-btn-visible');
exportBtn.classList.remove('btn-hidden');
exportBtn.classList.add('btn-visible');
}
showResult(true);
}
startBtn.onclick = batchReceive;
progressBtn.onclick = () => {
const elements = getBatchReceiveElements();
if (progressBtn.textContent === '取消接收') {
isCancelled = true;
elements.progressBtn.textContent = '已取消接收';
elements.progressBtn.disabled = true;
elements.progressBtn.style.opacity = '0.6';
elements.progressBtn.style.cursor = 'not-allowed';
showBatchReceiveStatus('已取消接收', '#f44336');
elements.exportBtn.classList.remove('batch-receive-btn-visible');
elements.exportBtn.classList.add('batch-receive-btn-hidden');
showResult(true);
} else if (progressBtn.textContent === '已取消接收') {
elements.progressBtn.textContent = '返回接收';
elements.progressBtn.disabled = false;
elements.progressBtn.style.opacity = '';
elements.progressBtn.style.cursor = '';
} else {
switchBatchReceiveUI('input');
elements.startBtn.textContent = '开始接收';
elements.progressBtn.disabled = false;
elements.progressBtn.style.opacity = '';
elements.progressBtn.style.cursor = '';
elements.exportBtn.classList.remove('batch-receive-btn-visible');
elements.exportBtn.classList.add('batch-receive-btn-hidden');
results = [];
isCancelled = false;
totalItems = 0;
processedItems = 0;
successCount = 0;
failedCount = 0;
}
};
backBtn.onclick = () => {
const elements = getBatchReceiveElements();
switchBatchReceiveUI('input');
elements.progressBtn.disabled = false;
elements.progressBtn.style.opacity = '';
elements.progressBtn.style.cursor = '';
elements.exportBtn.style.display = 'none';
results = [];
};
const batchReceiveSettings = JSON.parse(localStorage.getItem('batchReceiveSettings') || '{}');
const elements = getBatchReceiveElements();
if (batchReceiveSettings.cid) {
elements.cidInput.value = batchReceiveSettings.cid;
if (['0','100115'].includes(batchReceiveSettings.cid)) {
elements.cidSelect.value = batchReceiveSettings.cid;
} else {
elements.cidSelect.value = '';
}
} else {
elements.cidSelect.value = elements.cidInput.value = '100115';
}
if (batchReceiveSettings.delay) {
elements.delayInput.value = batchReceiveSettings.delay;
}
elements.cidSelect.addEventListener('change',()=>{
elements.cidInput.value = elements.cidSelect.value;
saveBatchReceiveSettings();
lastCid = elements.cidInput.value;
});
elements.cidInput.addEventListener('input',()=>{
elements.cidInput.value = elements.cidInput.value.replace(/[^0-9]/g, '');
if(elements.cidInput.value===''){
elements.cidInput.value = '100115';
}
if(elements.cidSelect.value!==elements.cidInput.value && ['0','100115'].includes(elements.cidInput.value)){
elements.cidSelect.value = elements.cidInput.value;
} else if(!['0','100115'].includes(elements.cidInput.value)){
elements.cidSelect.value = '';
}
saveBatchReceiveSettings();
lastCid = elements.cidInput.value;
});
elements.delayInput.addEventListener('input', () => {
saveBatchReceiveSettings();
});
exportBtn.addEventListener('click', function() {
if (!results.length) {
alert('没有接收结果可导出');
return;
}
const csvContent = [
['标题', '链接', '状态', '信息'],
...results.map(result => {
const shareLink = `https://115cdn.com/s/${result.share_code}?password=${result.receive_code}`;
const title = result.title || '无标题';
const status = result.success ? '成功' : '失败';
const message = result.success ? (result.msg || '转存成功') : (result.msg || '接收失败');
return [
title,
shareLink,
status,
message
];
})
].map(row => row.map(cell => `"${cell}"`).join(',')).join('\n');
const blob = new Blob(["\uFEFF" + csvContent], { type: 'text/csv;charset=utf-8;' });
const link = document.createElement('a');
const url = URL.createObjectURL(blob);
link.setAttribute('href', url);
const now = new Date();
const year = now.getFullYear();
const month = String(now.getMonth() + 1).padStart(2, '0');
const day = String(now.getDate()).padStart(2, '0');
const hours = String(now.getHours()).padStart(2, '0');
const minutes = String(now.getMinutes()).padStart(2, '0');
const seconds = String(now.getSeconds()).padStart(2, '0');
link.setAttribute('download', `115接收结果_${year}-${month}-${day}_${hours}-${minutes}-${seconds}.csv`);
link.style.visibility = 'hidden';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
});
startBtn.onclick = batchReceive;
}
function renderBatchRecognizePage() {
const batchRecognizeContainer = document.getElementById('batch-recognize-container');
if (batchRecognizeContainer.querySelector('#batch-recognize-input-container')) {
const savedSettings = JSON.parse(localStorage.getItem('batchRecognizeSettings') || '{}');
const batchSizeInput = batchRecognizeContainer.querySelector('#batch-size-input');
const verifyMethodSelect = batchRecognizeContainer.querySelector('#verify-method-select');
const performanceSwitch = batchRecognizeContainer.querySelector('#performance-mode-switch');
if (savedSettings.batchSize) {
batchSizeInput.value = savedSettings.batchSize;
}
if (savedSettings.verifyMethod) {
verifyMethodSelect.value = savedSettings.verifyMethod;
}
const concurrencyInput = batchRecognizeContainer.querySelector('#concurrency-input');
if (concurrencyInput && savedSettings.concurrency) {
concurrencyInput.value = savedSettings.concurrency;
}
if (performanceSwitch) {
performanceSwitch.checked = savedSettings.performanceMode !== false;
}
const inputContainer = batchRecognizeContainer.querySelector('#batch-recognize-input-container');
const controlsContainer = batchRecognizeContainer.querySelector('.batch-recognize-controls');
const controlsMain = controlsContainer.querySelector('.batch-recognize-controls-main');
const progressWrap = batchRecognizeContainer.querySelector('#batch-recognize-progress');
const exportBtn = batchRecognizeContainer.querySelector('#batch-recognize-export-btn');
const startBtn = batchRecognizeContainer.querySelector('#batch-recognize-start-btn');
const backBtn = batchRecognizeContainer.querySelector('#batch-recognize-back-btn');
const resultDiv = batchRecognizeContainer.querySelector('#batch-recognize-result');
const progressBtn = batchRecognizeContainer.querySelector('#batch-recognize-progress-btn');
const isGlobProcessing = !!(window.__br_processing);
if (isGlobProcessing) {
inputContainer.style.display = 'none';
if (controlsMain) controlsMain.style.display = 'none';
controlsContainer.style.display = 'flex';
resultDiv.style.display = 'block';
progressWrap.style.display = 'block';
startBtn.style.display = 'none';
backBtn.style.display = 'none';
if (progressBtn) progressBtn.style.display = 'inline-block';
} else {
inputContainer.style.display = 'block';
if (controlsMain) controlsMain.style.display = 'block';
controlsContainer.style.display = 'flex';
resultDiv.style.display = 'none';
progressWrap.style.display = 'none';
exportBtn.style.display = 'none';
startBtn.style.display = 'inline-block';
backBtn.style.display = 'none';
}
try {
const textarea = batchRecognizeContainer.querySelector('#batch-recognize-textarea');
const inputContainerEl = batchRecognizeContainer.querySelector('#batch-recognize-input-container');
if (inputContainerEl && textarea && !inputContainerEl.querySelector('.clear-text-btn')) {
const clearBtn = document.createElement('button');
clearBtn.type = 'button';
clearBtn.className = 'clear-text-btn';
clearBtn.title = '清空';
clearBtn.innerHTML = `
<svg class="clear-brush-svg" width="16" height="16" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" fill="none" aria-hidden="true">
<g stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path fill="none" d="M10,4 C10,2.8954305 10.8954305,2 12,2 C13.1045695,2 14,2.8954305 14,4 L14,10 L20,10 L20,14 L4,14 L4,10 L10,10 L10,4 Z M4,14 L20,14 L20,22 L12,22 L4,22 L4,14 Z M16,22 L16,16.3646005 M8,22 L8,16.3646005 M12,22 L12,16.3646005"></path>
</g>
</svg>`;
clearBtn.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
textarea.value = '';
textarea.dispatchEvent(new Event('input', { bubbles: true }));
textarea.focus();
});
inputContainerEl.appendChild(clearBtn);
const updateClearVisibility = () => {
clearBtn.style.display = textarea.value.trim() ? 'inline-flex' : 'none';
};
const updateClearPosition = () => {
const scrollbarWidth = textarea.offsetWidth - textarea.clientWidth;
const baseRight = 6 + (scrollbarWidth > 0 ? scrollbarWidth : 0);
clearBtn.style.right = baseRight + 'px';
};
updateClearVisibility();
updateClearPosition();
if (!textarea._clearBound) {
textarea.addEventListener('input', () => {
updateClearVisibility();
updateClearPosition();
});
window.addEventListener('resize', updateClearPosition);
textarea._clearBound = true;
}
}
} catch (err) {
console.warn('初始化批量识别清空按钮失败: ', err);
}
return;
}
batchRecognizeContainer.innerHTML = `
<div id="batch-recognize-input-container" class="batch-recognize-input-container">
<textarea id="batch-recognize-textarea" class="modal-textarea batch-recognize-textarea" placeholder="请在此处粘贴需要识别的分享内容...
支持域名:
- 115.com | 115cdn.com | anxia.com
支持的格式:
• 完整链接:https://115.com/s/分享码?password=访问码
• 简化链接:分享码?password=访问码
• 移动端格式:/分享码-访问码/
• 分离格式:分享码 访问码
• 关键词格式:分享码 提取码: xxxx 或 分享码 密码: xxxx
• 访问码格式:访问码: xxxx
• 提取码格式:提取码: xxxx
• 密码格式:密码: xxxx
• ED2K链接:ed2k://|file|文件名|大小|哈希|h=根哈希|/
• 磁力链格式:
- 完整磁力链:magnet:?xt=urn:btih:哈希值&name=文件名
- 标题+磁力链:标题 magnet:?xt=urn:btih:哈希值
提示:每行一个分享内容,支持混合格式"></textarea>
</div>
<div id="batch-recognize-result" class="batch-results-container"></div>
<div class="batch-recognize-controls">
<div class="batch-recognize-controls-main">
<div class="batch-recognize-controls-row">
<div class="batch-recognize-setting-item">
<label class="label">批量大小</label>
<input type="number" id="batch-size-input" class="input batch-size-input" value="20" min="1" max="1000" placeholder="20">
</div>
<div class="batch-recognize-setting-item">
<label class="label">并发数</label>
<input type="number" id="concurrency-input" class="input concurrency-input" value="5" min="1" max="50" placeholder="5">
</div>
<div class="batch-recognize-setting-item">
<label class="label">验证方式</label>
<select id="verify-method-select" class="input verify-method-select">
<option value="full" selected>完整验证(智能)</option>
<option value="quick">快速验证</option>
<option value="none">不验证</option>
</select>
</div>
<div class="batch-recognize-setting-item">
<label class="label">性能模式</label>
<span class="element-block-switch element-block-switch-clickable">
<input type="checkbox" id="performance-mode-switch" class="batch-recognize-checkbox" checked>
<span class="element-block-slider"></span>
</span>
</div>
</div>
</div>
<button id="batch-recognize-start-btn" class="btn-primary batch-recognize-start-btn">开始识别</button>
<button id="batch-recognize-back-btn" class="btn-primary batch-recognize-start-btn btn-hidden">返回识别</button>
</div>
<div id="batch-recognize-progress" class="progress-container batch-recognize-progress">
<div class="progress-header batch-recognize-progress-header">
<div>
<span class="progress-info">识别进度</span>
</div>
<button id="batch-recognize-progress-btn" class="btn-primary batch-recognize-start-btn batch-recognize-progress-btn">取消识别</button>
</div>
<div class="progress-bar-container batch-recognize-progress-bar-container">
<span id="progress-text">进度: 0/0 (0%)</span>
<button id="batch-recognize-export-btn" class="storage-import-export-btn batch-recognize-export-btn" data-text="导出结果">导出结果</button>
</div>
<div class="progress-bar" id="batch-recognize-progress-bar"></div>
</div>
`;
const textarea = batchRecognizeContainer.querySelector('#batch-recognize-textarea');
const batchSizeInput = batchRecognizeContainer.querySelector('#batch-size-input');
const concurrencyInput = batchRecognizeContainer.querySelector('#concurrency-input');
const verifyMethodSelect = batchRecognizeContainer.querySelector('#verify-method-select');
const performanceSwitch = batchRecognizeContainer.querySelector('#performance-mode-switch');
const startBtn = batchRecognizeContainer.querySelector('#batch-recognize-start-btn');
const backBtn = batchRecognizeContainer.querySelector('#batch-recognize-back-btn');
const progressBtn = batchRecognizeContainer.querySelector('#batch-recognize-progress-btn');
const progressBar = batchRecognizeContainer.querySelector('#batch-recognize-progress-bar');
const progressWrap = batchRecognizeContainer.querySelector('#batch-recognize-progress');
const resultDiv = batchRecognizeContainer.querySelector('#batch-recognize-result');
const exportBtn = batchRecognizeContainer.querySelector('#batch-recognize-export-btn');
const progressText = batchRecognizeContainer.querySelector('#progress-text');
const inputContainerEl = batchRecognizeContainer.querySelector('#batch-recognize-input-container');
if (inputContainerEl && textarea && !inputContainerEl.querySelector('.clear-text-btn')) {
const clearBtn = document.createElement('button');
clearBtn.type = 'button';
clearBtn.className = 'clear-text-btn';
clearBtn.title = '清空';
clearBtn.innerHTML = `
<svg class="clear-brush-svg" width="16" height="16" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" fill="none" aria-hidden="true">
<g stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path fill="none" d="M10,4 C10,2.8954305 10.8954305,2 12,2 C13.1045695,2 14,2.8954305 14,4 L14,10 L20,10 L20,14 L4,14 L4,10 L10,10 L10,4 Z M4,14 L20,14 L20,22 L12,22 L4,22 L4,14 Z M16,22 L16,16.3646005 M8,22 L8,16.3646005 M12,22 L12,16.3646005"></path>
</g>
</svg>`;
clearBtn.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
textarea.value = '';
textarea.dispatchEvent(new Event('input', { bubbles: true }));
textarea.focus();
});
inputContainerEl.appendChild(clearBtn);
const updateClearVisibility = () => {
clearBtn.style.display = textarea.value.trim() ? 'inline-flex' : 'none';
};
const updateClearPosition = () => {
const scrollbarWidth = textarea.offsetWidth - textarea.clientWidth;
const baseRight = 6 + (scrollbarWidth > 0 ? scrollbarWidth : 0);
clearBtn.style.right = baseRight + 'px';
};
updateClearVisibility();
updateClearPosition();
if (!textarea._clearBound) {
textarea.addEventListener('input', () => {
updateClearVisibility();
updateClearPosition();
});
window.addEventListener('resize', updateClearPosition);
textarea._clearBound = true;
}
}
let isProcessing = false;
let isCancelled = false;
let totalItems = 0;
let processedItems = 0;
let successCount = 0;
let failedCount = 0;
let skippedCount = 0;
let results = [];
let resultsBaseIndex = 0;
let currentSessionId = null;
let sessionCount = 0;
function updateProgress() {
const progress = totalItems > 0 ? (processedItems / totalItems) * 100 : 0;
progressBar.style.width = `${progress}%`;
const batchSizeText = batchSizeInput.value;
const verifyMethodText = verifyMethodSelect.options[verifyMethodSelect.selectedIndex].text;
const perfText = performanceSwitch && performanceSwitch.checked ? '开' : '关';
const concurrencyText = concurrencyInput ? concurrencyInput.value : '5';
const progressHtml = `<span class="batch-recognize-progress-status">
<span class="status-label">批量大小: ${batchSizeText}</span>
<span class="status-separator">|</span>
<span class="status-label">并发数: ${concurrencyText}</span>
<span class="status-separator">|</span>
<span class="status-label">${verifyMethodText}</span>
<span class="status-separator">|</span>
<span class="status-progress">进度: ${processedItems}/${totalItems} (${Math.round(progress)}%)</span>
<span class="status-separator">|</span>
<span class="status-success">成功: ${successCount}</span>
<span class="status-separator">|</span>
<span class="status-failed">失败: ${failedCount}</span>
<span class="status-separator">|</span>
<span class="status-skipped">跳过: ${skippedCount}</span>
<span class="status-separator">|</span>
<span class="status-label">性能模式: ${perfText}</span>
</span>`;
progressText.innerHTML = progressHtml;
}
function showBatchRecognizeStatus(msg, color, stats = null) {
if (stats) {
const statsMatch = stats.match(/成功:\s*(\d+)\s*\|\s*跳过:\s*(\d+)\s*\|\s*失败:\s*(\d+)/);
if (statsMatch) {
const successCount = parseInt(statsMatch[1]);
const skippedCount = parseInt(statsMatch[2]);
const failCount = parseInt(statsMatch[3]);
const totalCount = successCount + skippedCount + failCount;
const statusHtml = `<span class="batch-recognize-progress-status">
<span class="status-label">总数: ${totalCount}项</span>
<span class="status-separator">|</span>
<span class="status-success">成功: ${successCount}项</span>
<span class="status-separator">|</span>
<span class="status-skipped">跳过: ${skippedCount}项</span>
<span class="status-separator">|</span>
<span class="status-failed">失败: ${failCount}项</span>
</span>`;
progressText.innerHTML = statusHtml;
} else {
progressText.innerHTML = `<span class="text-color-gray">${stats}</span>`;
}
} else {
progressText.innerHTML = `<span class="text-color-custom" style="--text-color: ${color||'#4285f4'};">${msg}</span>`;
}
}
function showFullProgressStatus(msg, color) {
updateProgress();
}
function saveBatchRecognizeSettings() {
const settings = {
batchSize: batchSizeInput.value,
concurrency: concurrencyInput ? concurrencyInput.value : '5',
verifyMethod: verifyMethodSelect.value,
performanceMode: performanceSwitch ? performanceSwitch.checked : true
};
localStorage.setItem('batchRecognizeSettings', JSON.stringify(settings));
}
const handleBatchSizeInput = () => {
batchSizeInput.value = batchSizeInput.value.replace(/[^0-9]/g, '');
saveBatchRecognizeSettings();
updateProgress();
};
const handleBatchSizeBlur = () => {
let value = parseInt(batchSizeInput.value);
if (isNaN(value) || value < 1) {
value = 20;
} else if (value > 1000) {
value = 1000;
}
batchSizeInput.value = value;
saveBatchRecognizeSettings();
updateProgress();
};
const handleBatchSizePaste = (e) => {
e.preventDefault();
const pastedText = (e.clipboardData || window.clipboardData).getData('text');
const numbersOnly = pastedText.replace(/[^0-9]/g, '');
if (numbersOnly) {
batchSizeInput.value = numbersOnly;
saveBatchRecognizeSettings();
updateProgress();
}
};
const handleBatchSizeKeypress = (e) => {
const charCode = e.which ? e.which : e.keyCode;
if (charCode < 48 || charCode > 57) {
e.preventDefault();
}
};
const handleConcurrencyInput = () => {
if (!concurrencyInput) return;
concurrencyInput.value = concurrencyInput.value.replace(/[^0-9]/g, '');
saveBatchRecognizeSettings();
updateProgress();
};
const handleConcurrencyBlur = () => {
if (!concurrencyInput) return;
let value = parseInt(concurrencyInput.value);
if (isNaN(value) || value < 1) {
value = 1;
} else if (value > 50) {
value = 50;
}
concurrencyInput.value = value;
saveBatchRecognizeSettings();
updateProgress();
};
const handleConcurrencyPaste = (e) => {
if (!concurrencyInput) return;
e.preventDefault();
const pastedText = (e.clipboardData || window.clipboardData).getData('text');
const numbersOnly = pastedText.replace(/[^0-9]/g, '');
if (numbersOnly) {
concurrencyInput.value = numbersOnly;
saveBatchRecognizeSettings();
updateProgress();
}
};
const handleConcurrencyKeypress = (e) => {
const charCode = e.which ? e.which : e.keyCode;
if (charCode < 48 || charCode > 57) {
e.preventDefault();
}
};
const handleVerifyMethodChange = () => {
saveBatchRecognizeSettings();
updateProgress();
};
const handlePerformanceChange = () => {
saveBatchRecognizeSettings();
updateProgress();
};
batchSizeInput.addEventListener('input', handleBatchSizeInput);
batchSizeInput.addEventListener('blur', handleBatchSizeBlur);
batchSizeInput.addEventListener('paste', handleBatchSizePaste);
batchSizeInput.addEventListener('keypress', handleBatchSizeKeypress);
if (concurrencyInput) {
concurrencyInput.addEventListener('input', handleConcurrencyInput);
concurrencyInput.addEventListener('blur', handleConcurrencyBlur);
concurrencyInput.addEventListener('paste', handleConcurrencyPaste);
concurrencyInput.addEventListener('keypress', handleConcurrencyKeypress);
}
verifyMethodSelect.addEventListener('change', handleVerifyMethodChange);
if (performanceSwitch) {
performanceSwitch.addEventListener('change', handlePerformanceChange);
}
updateProgress();
const switchToInputMode = () => {
const inputContainer = batchRecognizeContainer.querySelector('#batch-recognize-input-container');
const controlsContainer = batchRecognizeContainer.querySelector('.batch-recognize-controls');
const controlsMain = controlsContainer.querySelector('.batch-recognize-controls-main');
inputContainer.style.display = 'block';
if (controlsMain) {
controlsMain.style.display = 'block';
}
controlsContainer.style.display = 'flex';
resultDiv.style.display = 'none';
progressWrap.style.display = 'none';
exportBtn.style.display = 'none';
backBtn.style.display = 'none';
progressBtn.style.display = 'none';
startBtn.style.display = 'inline-block';
startBtn.textContent = '开始识别';
startBtn.style.backgroundColor = '';
startBtn.style.borderColor = '';
isProcessing = false;
results = [];
localStorage.removeItem('batchRecognizeResults');
};
backBtn.addEventListener('click', switchToInputMode);
progressBtn.addEventListener('click', () => {
if (isProcessing) {
isCancelled = true;
progressBtn.textContent = '已取消识别';
progressBtn.disabled = true;
progressBtn.style.opacity = '0.6';
progressBtn.style.cursor = 'not-allowed';
progressBtn.style.display = 'inline-block';
showFullProgressStatus('已取消识别', '#f44336');
} else {
if (window.__br_processing && typeof window.__br_cancel === 'function') {
window.__br_cancel();
progressBtn.textContent = '已取消识别';
progressBtn.disabled = true;
progressBtn.style.opacity = '0.6';
progressBtn.style.cursor = 'not-allowed';
progressBtn.style.display = 'inline-block';
showFullProgressStatus('已取消识别', '#f44336');
} else {
switchToInputMode();
}
}
});
function parseInputLines(lines) {
const items = [];
let currentItem = {};
for (const line of lines) {
const trimmedLine = line.trim();
if (!trimmedLine) continue;
if (trimmedLine.includes('","')) {
const csvValues = trimmedLine.split('","').map(v => v.replace(/^"|"$/g, ''));
if (csvValues.length >= 7) {
const title = csvValues[0];
const codeOrHash = csvValues[1];
const password = csvValues[2];
const fullLink = csvValues[3];
const fileSize = csvValues[4];
const expireTime = csvValues[5];
const note = csvValues[6];
const ed2kMatch = fullLink.match(/ed2k:\/\/\|file\|([^|]+)\|(\d+)\|([0-9A-F]{32})(?:\|h=([^|]+))?(\||\/)?/i);
if (ed2kMatch) {
items.push({
shareCode: ed2kMatch[3],
ed2k: fullLink,
title: decodeURIComponent(ed2kMatch[1]),
fileSize: parseInt(ed2kMatch[2]),
source: line,
type: 'csv-ed2k'
});
continue;
}
const magnetMatch = fullLink.match(/magnet:\?xt=urn:btih:([a-fA-F0-9]{40})(?:\?name=([^&]+))?/i);
if (magnetMatch) {
const magnetHash = magnetMatch[1];
const magnetName = magnetMatch[2] ? decodeURIComponent(magnetMatch[2]) : '磁力链文件';
items.push({
shareCode: magnetHash,
magnet: fullLink,
title: magnetName,
source: line,
type: 'csv-magnet'
});
continue;
}
const urlMatch = fullLink.match(/(?:115\.com|115cdn\.com|anxia\.com)\/s\/([a-z0-9]+)(?:\?password=([a-zA-Z0-9]+))?/i);
if (urlMatch) {
items.push({
shareCode: urlMatch[1],
password: password !== '无' ? password : (urlMatch[2] || ''),
title: title,
source: line,
type: 'csv-url'
});
continue;
}
}
continue;
}
const linkCodeMatch = trimmedLine.match(/(https?:\/\/(?:115\.com|115cdn\.com|anxia\.com)\/s\/([a-z0-9]+))\s+([a-zA-Z0-9]{4})/i);
if (linkCodeMatch) {
items.push({
shareCode: linkCodeMatch[2],
password: linkCodeMatch[3],
source: line,
type: 'link-code'
});
continue;
}
const ed2kMatch = trimmedLine.match(/ed2k:\/\/\|file\|([^|]+)\|(\d+)\|([0-9A-F]{32})(?:\|h=([^|]+))?(\||\/)?/i);
if (ed2kMatch) {
let fullEd2k = `ed2k://|file|${ed2kMatch[1]}|${ed2kMatch[2]}|${ed2kMatch[3]}`;
if (ed2kMatch[4]) {
fullEd2k += `|h=${ed2kMatch[4]}`;
}
fullEd2k += '|/';
items.push({
shareCode: ed2kMatch[3],
ed2k: fullEd2k,
title: decodeURIComponent(ed2kMatch[1]),
fileSize: parseInt(ed2kMatch[2]),
source: line,
type: 'ed2k'
});
currentItem = {};
continue;
}
const titleMagnetMatch = trimmedLine.match(/^(.+?)[\t\s]+(magnet:\?xt=urn:btih:[a-fA-F0-9]{40}(?:\?name=[^&]+)?)$/i);
if (titleMagnetMatch) {
const title = titleMagnetMatch[1].trim();
const magnetLink = titleMagnetMatch[2];
const magnetMatch = magnetLink.match(/magnet:\?xt=urn:btih:([a-fA-F0-9]{40})(?:\?name=([^&]+))?/i);
if (magnetMatch) {
const magnetHash = magnetMatch[1];
items.push({
shareCode: magnetHash,
magnet: magnetLink,
title: title,
source: line,
type: 'title-magnet'
});
currentItem = {};
continue;
}
}
const magnetMatch = trimmedLine.match(/magnet:\?xt=urn:btih:([a-fA-F0-9]{40})(?:\?name=([^&]+))?/i);
if (magnetMatch) {
const magnetHash = magnetMatch[1];
const magnetName = magnetMatch[2] ? decodeURIComponent(magnetMatch[2]) : '磁力链文件';
items.push({
shareCode: magnetHash,
magnet: trimmedLine,
title: magnetName,
source: line,
type: 'magnet'
});
currentItem = {};
continue;
}
const btihMatch = trimmedLine.match(/btih:([a-fA-F0-9]{40})/i);
if (btihMatch) {
const magnetHash = btihMatch[1];
const fullMagnet = `magnet:?xt=urn:btih:${magnetHash}`;
items.push({
shareCode: magnetHash,
magnet: fullMagnet,
title: '磁力链文件',
source: line,
type: 'magnet-hash'
});
currentItem = {};
continue;
}
const slashMatch = trimmedLine.match(/^\/([a-z0-9]+)-([a-zA-Z0-9]+)\/$/);
const urlMatch = trimmedLine.match(/(?:115\.com|115cdn\.com|anxia\.com)\/s\/([a-z0-9]+)(?:\?password=([a-zA-Z0-9]+))?/i);
const passwordMatch = trimmedLine.match(/访问码[::]\s*([a-zA-Z0-9]+)/i);
if (slashMatch) {
if (currentItem.shareCode) items.push(currentItem);
currentItem = {
shareCode: slashMatch[1],
password: slashMatch[2],
source: line,
type: 'slash'
};
continue;
}
if (urlMatch) {
if (currentItem.shareCode) items.push(currentItem);
currentItem = {
shareCode: urlMatch[1],
password: urlMatch[2],
source: line,
type: 'url'
};
continue;
}
if (passwordMatch && currentItem.shareCode && !currentItem.password) {
currentItem.password = passwordMatch[1];
continue;
}
if (!currentItem.title && !trimmedLine.includes('访问码') && !trimmedLine.includes('复制这段内容')) {
currentItem.title = trimmedLine;
}
}
if (currentItem.shareCode) {
items.push(currentItem);
}
return items;
}
async function processRecognizeItem(item, verifyMethod, ctx) {
let currentAllItems = null;
const hasCtx = ctx && (ctx.existingEd2k || ctx.existingMagnet || ctx.existingShare);
if (!hasCtx) {
currentAllItems = getAllStorageItems();
}
if (item.ed2k) {
const ed2kMatch = item.ed2k.match(/ed2k:\/\/\|file\|([^|]+)\|(\d+)\|([0-9A-F]{32})(?:\|h=([^|]+))?(\||\/)?/i);
const ed2kTitle = ed2kMatch ? decodeURIComponent(ed2kMatch[1]) : 'ED2K文件';
const existing = hasCtx ? (ctx.existingEd2k?.has(item.ed2k)) : currentAllItems.find(i => i.ed2k === item.ed2k);
if (existing) {
return {
success: false,
skipped: true,
title: ed2kTitle,
shareCode: item.shareCode,
password: '',
msg: '已存在相同ED2K链接',
shareLink: item.ed2k
};
}
const note = ed2kTitle ? `[批量识别] ${ed2kTitle}` : '[批量识别]';
saveToStorage(
item.shareCode,
'',
note,
ed2kTitle,
-1,
item.fileSize,
'0',
item.ed2k
);
if (hasCtx && ctx.existingEd2k) ctx.existingEd2k.add(item.ed2k);
return {
success: true,
title: ed2kTitle,
shareCode: item.shareCode,
password: '',
msg: 'ED2K链接已保存',
shareLink: item.ed2k,
fileSize: item.fileSize
};
}
if (item.magnet) {
const magnetMatch = item.magnet.match(/magnet:\?xt=urn:btih:([a-fA-F0-9]{40})/i);
const magnetHash = magnetMatch ? magnetMatch[1] : '';
let magnetName = '磁力链文件';
if (item.type === 'title-magnet' && item.title) {
magnetName = item.title;
} else {
const nameMatch = item.magnet.match(/[?&]name=([^&]+)/i);
if (nameMatch) {
try {
magnetName = decodeURIComponent(nameMatch[1]);
} catch (e) {
magnetName = nameMatch[1];
}
} else {
const dnMatch = item.magnet.match(/[?&]dn=([^&]+)/i);
if (dnMatch) {
try {
magnetName = decodeURIComponent(dnMatch[1]);
} catch (e) {
magnetName = dnMatch[1];
}
}
}
}
const existing = hasCtx ? (ctx.existingMagnet?.has(item.magnet)) : currentAllItems.find(i => i.magnet === item.magnet);
if (existing) {
return {
success: false,
skipped: true,
title: magnetName,
shareCode: item.shareCode,
password: '',
msg: '已存在相同磁力链',
shareLink: item.magnet
};
}
const note = magnetName ? `[批量识别] ${magnetName}` : '[批量识别]';
saveToStorage(
item.shareCode,
'',
note,
magnetName,
-1,
0,
'0',
'',
item.magnet
);
if (hasCtx && ctx.existingMagnet) ctx.existingMagnet.add(item.magnet);
return {
success: true,
title: magnetName,
shareCode: item.shareCode,
password: '',
msg: '磁力链已保存',
shareLink: item.magnet
};
}
if (!item.password) {
return {
success: false,
title: item.title || '无标题',
shareCode: item.shareCode,
password: '',
msg: '缺少访问码'
};
}
let existingItem = null;
let exists = false;
if (hasCtx) {
exists = !!(ctx.existingShare && ctx.existingShare.has(item.shareCode));
if (exists) {
try {
const storageKey = generateStorageKey(item.shareCode, '', '');
const raw = GM_getValue(storageKey);
if (raw) existingItem = JSON.parse(raw);
} catch (e) {}
}
} else {
existingItem = currentAllItems.find(i => i.shareCode === item.shareCode) || null;
exists = !!existingItem;
}
const existedBefore = exists;
if (existedBefore && existingItem && existingItem.password === item.password) {
return {
success: false,
skipped: true,
title: existingItem.shareTitle || '无标题',
shareCode: item.shareCode,
password: item.password,
msg: '已存在(跳过)'
};
}
if (verifyMethod === 'none') {
const note = item.title ? `[批量识别] ${item.title}` : '[批量识别]';
saveToStorage(
item.shareCode,
item.password,
note,
item.title,
-1,
0,
'0'
);
return {
success: true,
title: item.title || '未验证的访问码',
shareCode: item.shareCode,
password: item.password,
msg: '跳过验证'
};
}
try {
const response = await new Promise((resolve) => {
checkPasswordCorrect(item.shareCode, item.password, (isCorrect, responseData) => {
resolve({ isCorrect, responseData });
});
});
if (response.isCorrect) {
let title = response.responseData?.shareTitle || item.title;
const note = title ? `[批量识别] ${title}` : '[批量识别]';
saveToStorage(
item.shareCode,
item.password,
note,
response.responseData?.shareTitle || item.title,
response.responseData?.expireTime || -1,
response.responseData?.fileSize || 0,
response.responseData?.autoRenewal || '0'
);
return {
success: true,
title: item.title || '未验证的访问码',
shareCode: item.shareCode,
password: item.password,
msg: existedBefore ? '已更新访问码' : '验证成功'
};
}
else {
let errorMsg = '验证失败';
if (response.responseData?.error) {
errorMsg = response.responseData.error;
} else if (response.responseData?.rawResponse?.data?.shareinfo?.forbid_reason) {
errorMsg = response.responseData.rawResponse.data.shareinfo.forbid_reason;
} else if (
response.responseData?.rawResponse?.data?.shareinfo?.share_state === -1 ||
response.responseData?.data?.shareinfo?.share_state === -1
) {
errorMsg = '分享已取消';
}
const rawShareInfo = response.responseData?.rawResponse?.data?.shareinfo || response.responseData?.data?.shareinfo || {};
const shareState = rawShareInfo?.share_state;
const forbidReason = rawShareInfo?.forbid_reason || errorMsg;
const expireTime = rawShareInfo?.expire_time || -1;
const fileSize = parseInt((rawShareInfo?.file_size || 0));
const autoRenewal = String(rawShareInfo?.auto_renewal || '0');
const newTitle = processShareTitle(response.responseData?.rawResponse || response.responseData || {});
if (verifyMethod === 'quick') {
if ((typeof forbidReason === 'string' && /过期/.test(forbidReason)) || shareState === 7) {
const note = newTitle ? `[批量识别] ${newTitle}` : (item.title ? `[批量识别] ${item.title}` : '[批量识别]');
saveToStorage(
item.shareCode,
item.password,
note,
newTitle || item.title,
expireTime,
fileSize,
autoRenewal,
'',
'',
forbidReason || '分享已过期'
);
return {
success: true,
title: newTitle || item.title || '未验证的访问码',
shareCode: item.shareCode,
password: item.password,
msg: '快速:链接已过期',
fileSize
};
}
if (errorMsg === '分享已取消' || shareState === -1) {
const note = newTitle ? `[批量识别] ${newTitle}` : (item.title ? `[批量识别] ${item.title}` : '[批量识别]');
saveToStorage(
item.shareCode,
item.password,
note,
'',
-1,
0,
'0',
'',
'',
''
);
return {
success: true,
title: newTitle || item.title || '未验证的访问码',
shareCode: item.shareCode,
password: item.password,
msg: '快速:分享已取消'
};
}
const note = item.title ? `[批量识别] ${item.title}` : '[批量识别]';
saveToStorage(
item.shareCode,
item.password,
note,
item.title,
-1,
fileSize,
'0'
);
return {
success: true,
title: item.title || '未验证的访问码',
shareCode: item.shareCode,
password: item.password,
msg: '快速验证模式',
fileSize
};
}
return {
success: false,
title: item.title || '无标题',
shareCode: item.shareCode,
password: item.password,
msg: errorMsg,
fileSize
};
}
} catch (error) {
console.error('验证访问码失败:', error);
if (verifyMethod === 'quick') {
let rawShareInfo = {};
try {
const resp = error && (error.responseData || error.rawResponse || {});
rawShareInfo = resp?.rawResponse?.data?.shareinfo || resp?.data?.shareinfo || {};
} catch (e) {
}
const shareState = rawShareInfo?.share_state;
const forbidReason = rawShareInfo?.forbid_reason || '';
const expireTime = rawShareInfo?.expire_time || -1;
const fileSize = parseInt((rawShareInfo?.file_size || 0));
const autoRenewal = String(rawShareInfo?.auto_renewal || '0');
const newTitle = processShareTitle(error?.responseData?.rawResponse || error?.responseData || {});
if ((typeof forbidReason === 'string' && /过期/.test(forbidReason)) || shareState === 7) {
const note = newTitle ? `[批量识别] ${newTitle}` : (item.title ? `[批量识别] ${item.title}` : '[批量识别]');
saveToStorage(
item.shareCode,
item.password,
note,
newTitle || item.title,
expireTime,
fileSize,
autoRenewal,
'',
'',
forbidReason || '分享已过期'
);
return {
success: true,
title: newTitle || item.title || '未验证的访问码',
shareCode: item.shareCode,
password: item.password,
msg: '快速:链接已过期',
fileSize
};
}
if (shareState === -1) {
const note = newTitle ? `[批量识别] ${newTitle}` : (item.title ? `[批量识别] ${item.title}` : '[批量识别]');
saveToStorage(
item.shareCode,
item.password,
note,
'',
-1,
0,
'0',
'',
'',
''
);
return {
success: true,
title: newTitle || item.title || '未验证的访问码',
shareCode: item.shareCode,
password: item.password,
msg: '快速:分享已取消'
};
}
saveToStorage(
item.shareCode,
item.password,
'[批量识别]',
item.title,
-1,
fileSize,
'0'
);
return {
success: true,
title: item.title || '未验证的访问码',
shareCode: item.shareCode,
password: item.password,
msg: '快速验证模式',
fileSize
};
}
return {
success: false,
title: item.title || '无标题',
shareCode: item.shareCode,
password: item.password,
msg: '验证出错'
};
}
}
function showResult() {
const inputContainer = batchRecognizeContainer.querySelector('#batch-recognize-input-container');
if (inputContainer && inputContainer.style.display !== 'none') {
resultDiv.style.display = 'none';
return;
}
resultDiv.style.display = 'block';
const isMaximized = windowElement.classList.contains('maximized');
if (isMaximized) {
resultDiv.classList.remove('batch-recognize-result-dynamic');
resultDiv.classList.add('batch-recognize-result-maximized');
} else {
resultDiv.classList.remove('batch-recognize-result-maximized');
resultDiv.classList.add('batch-recognize-result-dynamic');
}
if (results.length === 0) {
resultDiv.innerHTML = '<div class="no-recognition-message">未识别到有效的分享内容</div>';
return;
}
const isPerf = performanceSwitch && performanceSwitch.checked;
const displayResults = isPerf ? results : results;
const reversedResults = [...displayResults].reverse();
resultDiv.innerHTML = reversedResults.map((r, index) => {
const baseLen = displayResults.length;
const originalIndex = resultsBaseIndex + (results.length - baseLen) + (baseLen - 1 - index);
const shareLink = r.shareLink || (r.ed2k ? r.ed2k : `https://115cdn.com/s/${r.shareCode}?password=${r.password}`);
const title = r.title || r.shareTitle || '无标题';
const fileSizeTag = r.fileSize && r.fileSize > 0 ?
`<span class="batch-recognize-file-size">${formatFileSize(r.fileSize)}</span>` : '';
let statusClass = 'error';
let statusText = r.msg || '识别失败';
if (r.success) {
statusClass = 'success';
statusText = r.msg || '识别成功';
} else if (r.skipped) {
statusClass = 'warning';
statusText = r.msg || '已跳过';
}
return `<div class="batch-result-item compact-layout ${statusClass}" data-index="${originalIndex}">
<div class="batch-result-item-title">
<div>
${fileSizeTag}
<span class="batch-recognize-file-name" title="${title}">${title}</span>
</div>
<div class="batch-result-item-actions">
<button class="storage-item-btn copy-btn">复制</button>
${r.success ? '<button class="storage-item-btn open-btn">打开</button>' : ''}
</div>
</div>
<div class="batch-result-item-details">
<span class="batch-result-link" title="点击打开链接" data-link="${shareLink}">${shareLink}</span>
<span class="batch-result-status ${statusClass}">${statusText}</span>
</div>
</div>`;
}).join('');
reversedResults.forEach((r, displayIndex) => {
const baseLen = displayResults.length;
const originalIndex = resultsBaseIndex + (results.length - baseLen) + (baseLen - 1 - displayIndex);
const item = resultDiv.querySelector(`[data-index="${originalIndex}"]`);
const copyBtn = item.querySelector('.copy-btn');
const linkSpan = item.querySelector('.batch-result-link');
const shareLink = r.shareLink || (r.ed2k ? r.ed2k : `https://115cdn.com/s/${r.shareCode}?password=${r.password}`);
const title = r.title || r.shareTitle || '无标题';
copyBtn.addEventListener('click', () => {
if (copyBtn._copyTimer) clearTimeout(copyBtn._copyTimer);
const enableMagnetTitleCopy = GM_getValue('enableMagnetTitleCopy', false);
let text;
if (r.magnet && enableMagnetTitleCopy && title) {
text = `${title}\n${r.magnet}`;
} else if (r.ed2k) {
text = r.ed2k;
} else {
const enableShareTitleCopy = GM_getValue('enableShareTitleCopy', false);
if (enableShareTitleCopy && title) {
text = `${title}\n${shareLink}`;
} else {
text = `${shareLink}#\n${title}`;
}
}
navigator.clipboard.writeText(text).then(() => {
copyBtn.textContent = '已复制';
copyBtn.classList.add('copied');
copyBtn._copyTimer = setTimeout(() => {
copyBtn.textContent = '复制';
copyBtn.classList.remove('copied');
copyBtn._copyTimer = null;
}, 1000);
}).catch(() => alert('复制失败'));
});
linkSpan.addEventListener('click', () => {
if (r.ed2k) {
navigator.clipboard.writeText(r.ed2k).then(() => {
alert('ED2K链接已复制到剪贴板');
}).catch(() => alert('复制失败'));
} else {
window.open(shareLink, '_blank');
}
});
const openBtn = item.querySelector('.open-btn');
if (openBtn) {
openBtn.addEventListener('click', () => {
if (r.ed2k) {
navigator.clipboard.writeText(r.ed2k).then(() => {
alert('ED2K链接已复制到剪贴板');
}).catch(() => alert('复制失败'));
} else {
window.open(shareLink, '_blank');
}
});
}
});
if (results.length > 0) {
exportBtn.style.display = 'inline-block';
} else {
exportBtn.style.display = 'none';
}
}
startBtn.addEventListener('click', async () => {
if (isProcessing) {
isCancelled = true;
startBtn.textContent = '开始识别';
return;
}
const content = textarea.value.trim();
if (!content) {
showBatchRecognizeStatus('请输入分享内容', '#f44336');
return;
}
const lines = content.split('\n').filter(line => line.trim() !== '');
if (lines.length === 0) {
showBatchRecognizeStatus('未找到有效的分享内容', '#f44336');
return;
}
const inputContainer = batchRecognizeContainer.querySelector('#batch-recognize-input-container');
const controlsContainer = batchRecognizeContainer.querySelector('.batch-recognize-controls');
const controlsMain = controlsContainer.querySelector('.batch-recognize-controls-main');
inputContainer.style.display = 'none';
if (controlsMain) {
controlsMain.style.display = 'none';
}
controlsContainer.style.display = 'flex';
resultDiv.style.display = 'block';
resultDiv.innerHTML = '<div class="batch-recognize-ready-message">准备开始批量识别...</div>';
isProcessing = true;
isCancelled = false;
window.__br_processing = true;
window.__br_cancel = () => { isCancelled = true; };
startBtn.style.display = 'none';
progressBtn.textContent = '取消识别';
progressBtn.style.display = 'inline-block';
progressBtn.disabled = false;
progressBtn.style.opacity = '';
progressBtn.style.cursor = '';
totalItems = 0;
processedItems = 0;
successCount = 0;
failedCount = 0;
skippedCount = 0;
results = [];
progressWrap.style.display = 'block';
exportBtn.style.display = 'none';
showFullProgressStatus('正在解析输入内容...', '#2196f3');
const batchSize = Math.max(1, Math.min(1000, parseInt(batchSizeInput.value) || 20));
const concurrency = Math.max(1, Math.min(50, parseInt(concurrencyInput ? concurrencyInput.value : '5') || 5));
const verifyMethod = verifyMethodSelect.value;
const isPerf = performanceSwitch && performanceSwitch.checked;
try {
const parsedItems = parseInputLines(lines);
if (parsedItems.length === 0) {
showFullProgressStatus('未识别到有效的分享链接或格式', '#f44336');
switchToInputMode();
isProcessing = false;
return;
}
totalItems = parsedItems.length;
updateProgress();
showFullProgressStatus(`开始处理 ${totalItems} 条内容...`, '#2196f3');
currentSessionId = `br-${Date.now()}`;
sessionCount = 0;
try { GM_setValue(`batchRecognizeMeta:${currentSessionId}`, { startedAt: Date.now(), count: 0 }); } catch (e) {}
const allItemsForDedup = getAllStorageItems();
const ctx = {
existingEd2k: new Set(allItemsForDedup.filter(i => i.ed2k).map(i => i.ed2k)),
existingMagnet: new Set(allItemsForDedup.filter(i => i.magnet).map(i => i.magnet)),
existingShare: new Set(allItemsForDedup.filter(i => i.shareCode).map(i => i.shareCode)),
};
const mapLimit = async (arr, limit, fn) => {
const ret = [];
let idx = 0;
const workers = new Array(Math.min(limit, arr.length)).fill(0).map(async () => {
while (idx < arr.length && !isCancelled) {
const current = arr[idx++];
const r = await fn(current);
ret.push(r);
}
});
await Promise.all(workers);
return ret;
};
let lastRenderTime = 0;
let desiredConcurrency = concurrency;
const maxConcurrency = concurrency;
const minConcurrency = 1;
let winDurations = [];
let winErrors = 0;
const winMax = 50;
const sleep = (ms) => new Promise(r => setTimeout(r, ms));
const recognizeWithRetry = async (item) => {
const maxRetries = 2;
let attempt = 0;
let lastResult = null;
while (!isCancelled) {
const t0 = performance.now();
try {
const r = await processRecognizeItem(item, verifyMethod, ctx);
const dt = performance.now() - t0;
winDurations.push(dt);
if (winDurations.length > winMax) winDurations.shift();
if (!r || !r.success) {
winErrors++;
}
lastResult = r;
return r;
} catch (err) {
const dt = performance.now() - t0;
winDurations.push(dt);
if (winDurations.length > winMax) winDurations.shift();
winErrors++;
lastResult = { success: false, msg: (err && err.message) ? err.message : '异常' };
}
if (attempt >= maxRetries) return lastResult;
const backoff = (200 * Math.pow(2, attempt)) + Math.floor(Math.random() * 100);
attempt++;
await sleep(backoff);
}
return lastResult;
};
for (let i = 0; i < parsedItems.length; i += batchSize) {
if (isCancelled) break;
const batch = parsedItems.slice(i, i + batchSize);
await mapLimit(batch, desiredConcurrency, async (item) => {
if (isCancelled) return;
const result = await recognizeWithRetry(item);
results.push(result);
if (isPerf && results.length > 1000) {
const drop = results.length - 1000;
results.splice(0, drop);
resultsBaseIndex += drop;
}
try {
sessionCount += 1;
GM_setValue(`batchRecognizeResult:${currentSessionId}:${sessionCount}`, result);
GM_setValue(`batchRecognizeMeta:${currentSessionId}`, { startedAt: Date.now(), count: sessionCount });
} catch (e) {}
processedItems++;
if (result && result.success && item && item.shareCode && ctx && ctx.existingShare) {
ctx.existingShare.add(item.shareCode);
}
if (result.success) successCount++;
else if (result.skipped) skippedCount++;
else failedCount++;
updateProgress();
const now = performance.now();
if (!isPerf) {
if (results.length <= 50 || (results.length <= 200 && results.length % 5 === 0) || (results.length > 200 && results.length % 20 === 0)) {
showResult();
}
} else {
if (now - lastRenderTime > 450) {
showResult();
lastRenderTime = now;
}
if (processedItems % 50 === 0) {
await new Promise(r => setTimeout(r, 0));
}
}
return result;
});
if (winDurations.length > 10) {
const avg = winDurations.reduce((a,b)=>a+b,0) / winDurations.length;
const errRate = winErrors / winDurations.length;
if (errRate > 0.15 || avg > 1500) {
desiredConcurrency = Math.max(minConcurrency, desiredConcurrency - 1);
} else if (errRate < 0.05 && avg < 800) {
desiredConcurrency = Math.min(maxConcurrency, desiredConcurrency + 1);
}
winDurations = [];
winErrors = 0;
}
if (i + batchSize < parsedItems.length && !isCancelled) {
await new Promise(resolve => setTimeout(resolve, isPerf ? 0 : 50));
}
}
if (!isCancelled) {
showResult();
showFullProgressStatus(`识别完成: 共${totalItems}条`, '#4caf50');
exportBtn.style.display = 'inline-block';
} else {
showFullProgressStatus('识别已取消', '#ff9800');
showResult();
exportBtn.style.display = 'inline-block';
}
try { localStorage.setItem('batchRecognizeResults', JSON.stringify(results.slice(-1000))); } catch (e) {}
progressBtn.textContent = '返回识别';
progressBtn.style.display = 'inline-block';
progressBtn.disabled = false;
progressBtn.style.opacity = '';
progressBtn.style.cursor = '';
window.__br_processing = false;
window.__br_cancel = null;
} catch (e) {
console.error(e);
showFullProgressStatus('识别发生异常', '#f44336');
switchToInputMode();
isProcessing = false;
window.__br_processing = false;
window.__br_cancel = null;
return;
}
isProcessing = false;
});
exportBtn.addEventListener('click', async () => {
try {
const meta = await GM_getValue(`batchRecognizeMeta:${currentSessionId}`, { count: resultsBaseIndex + results.length });
const total = meta && meta.count ? meta.count : (resultsBaseIndex + results.length);
if (!total || total <= 0) {
alert('没有可导出的结果');
return;
}
const header = ['标题','分享码/哈希','提取码','链接','文件大小','成功','消息'];
const lines = [header.join(',')];
const chunk = 5000;
for (let start = 1; start <= total; start += chunk) {
const end = Math.min(total, start + chunk - 1);
for (let i = start; i <= end; i++) {
const r = await GM_getValue(`batchRecognizeResult:${currentSessionId}:${i}`, null);
if (!r) continue;
const title = (r.title || r.shareTitle || '').replace(/"/g, '""');
const codeOrHash = r.shareCode || r.hash || '';
const password = r.password || '';
const fullLink = r.shareLink || '';
const fileSize = r.fileSize || '';
const succ = r.success ? '1' : '0';
const msg = (r.msg || '').replace(/"/g, '""');
lines.push(`"${title}","${codeOrHash}","${password}","${fullLink}","${fileSize}","${succ}","${msg}"`);
}
await new Promise(r => setTimeout(r, 0));
}
const blob = new Blob(["\ufeff" + lines.join('\n')], { type: 'text/csv;charset=utf-8;' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `batch-recognize-${currentSessionId || Date.now()}.csv`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
} catch (err) {
console.error('导出失败:', err);
alert('导出失败,请重试');
}
});
progressWrap.style.display = 'none';
exportBtn.style.display = 'none';
resultDiv.style.display = 'none';
progressBtn.style.display = 'none';
try {
const stored = JSON.parse(localStorage.getItem('batchRecognizeResults') || '[]');
if (!window.__br_processing && Array.isArray(stored) && stored.length > 0) {
results = stored;
const inputContainer2 = batchRecognizeContainer.querySelector('#batch-recognize-input-container');
const controlsContainer2 = batchRecognizeContainer.querySelector('.batch-recognize-controls');
const controlsMain2 = controlsContainer2 ? controlsContainer2.querySelector('.batch-recognize-controls-main') : null;
if (inputContainer2) inputContainer2.style.display = 'none';
if (controlsMain2) controlsMain2.style.display = 'none';
if (controlsContainer2) controlsContainer2.style.display = 'flex';
resultDiv.style.display = 'block';
progressWrap.style.display = 'block';
exportBtn.style.display = 'inline-block';
startBtn.style.display = 'none';
backBtn.style.display = 'inline-block';
const successCount2 = results.filter(r => r && r.success).length;
const failedCount2 = results.filter(r => r && !r.success && !r.skipped).length;
const skippedCount2 = results.filter(r => r && r.skipped).length;
const totalItems2 = results.length;
const statusHtml2 = `<span class="batch-recognize-status-display">
<span class="status-label">总数: ${totalItems2}项</span>
<span class="status-separator">|</span>
<span class="status-success">成功: ${successCount2}项</span>
<span class="status-separator">|</span>
<span class="status-warning">跳过: ${skippedCount2}项</span>
<span class="status-separator">|</span>
<span class="status-error">失败: ${failedCount2}项</span>
</span>`;
progressText.innerHTML = statusHtml2;
const reversedResults2 = [...results].reverse();
resultDiv.innerHTML = reversedResults2.map((r, index) => {
const originalIndex = results.length - 1 - index;
const shareLink = r.shareLink || (r.ed2k ? r.ed2k : `https://115cdn.com/s/${r.shareCode}?password=${r.password}`);
const title = r.title || r.shareTitle || '无标题';
const fileSizeTag = r.fileSize && r.fileSize > 0 ? `<span class="batch-recognize-file-size">${formatFileSize(r.fileSize)}</span>` : '';
let statusClass = 'error';
let statusText = r.msg || '识别失败';
if (r.success) { statusClass = 'success'; statusText = r.msg || '识别成功'; }
else if (r.skipped) { statusClass = 'warning'; statusText = r.msg || '已跳过'; }
return `<div class="batch-result-item compact-layout ${statusClass}" data-index="${originalIndex}">
<div class="batch-result-item-title">
<div>
${fileSizeTag}
<span class="batch-recognize-file-name" title="${title}">${title}</span>
</div>
<div class="batch-result-item-actions">
<button class="storage-item-btn copy-btn">复制</button>
${r.success ? '<button class="storage-item-btn open-btn">打开</button>' : ''}
</div>
</div>
<div class="batch-result-item-details">
<span class="batch-result-link" title="点击打开链接" data-link="${shareLink}">${shareLink}</span>
<span class="batch-result-status ${statusClass}">${statusText}</span>
</div>
</div>`;
}).join('');
reversedResults2.forEach((r, displayIndex) => {
const originalIndex = results.length - 1 - displayIndex;
const item = resultDiv.querySelector(`[data-index="${originalIndex}"]`);
if (!item) return;
const copyBtn = item.querySelector('.copy-btn');
const linkSpan = item.querySelector('.batch-result-link');
const shareLink = r.shareLink || (r.ed2k ? r.ed2k : `https://115cdn.com/s/${r.shareCode}?password=${r.password}`);
const title = r.title || r.shareTitle || '无标题';
if (copyBtn) {
copyBtn.addEventListener('click', () => {
if (copyBtn._copyTimer) clearTimeout(copyBtn._copyTimer);
const enableMagnetTitleCopy = GM_getValue('enableMagnetTitleCopy', false);
let text;
if (r.magnet && enableMagnetTitleCopy && title) {
text = `${title}\n${r.magnet}`;
} else if (r.ed2k) {
text = r.ed2k;
} else {
const enableShareTitleCopy = GM_getValue('enableShareTitleCopy', false);
if (enableShareTitleCopy && title) {
text = `${title}\n${shareLink}`;
} else {
text = `${shareLink}#\n${title}`;
}
}
navigator.clipboard.writeText(text).then(() => {
copyBtn.textContent = '已复制';
copyBtn.classList.add('copied');
copyBtn._copyTimer = setTimeout(() => {
copyBtn.textContent = '复制';
copyBtn.classList.remove('copied');
copyBtn._copyTimer = null;
}, 1000);
}).catch(() => alert('复制失败'));
});
}
if (linkSpan) {
linkSpan.addEventListener('click', () => {
if (r.ed2k) {
navigator.clipboard.writeText(r.ed2k).then(() => {
alert('ED2K链接已复制到剪贴板');
}).catch(() => alert('复制失败'));
} else {
window.open(shareLink, '_blank');
}
});
}
const openBtn = item.querySelector('.open-btn');
if (openBtn) {
openBtn.addEventListener('click', () => {
if (r.ed2k) {
navigator.clipboard.writeText(r.ed2k).then(() => {
alert('ED2K链接已复制到剪贴板');
}).catch(() => alert('复制失败'));
} else {
window.open(shareLink, '_blank');
}
});
}
});
}
} catch (e) {}
updateProgress();
}
function renderBatchSharePage() {
if (batchShareContainer.querySelector('#batch-share-flex-row')) {
const batchShareSettings = JSON.parse(localStorage.getItem('batchShareSettings') || '{}');
batchShareContainer.querySelector('#batch-share-expire').value = batchShareSettings.expire || '-1';
batchShareContainer.querySelector('#batch-share-limit').value = batchShareSettings.limit || 0;
batchShareContainer.querySelector('#batch-share-code').value = batchShareSettings.code || '';
batchShareContainer.querySelector('#batch-share-delay').value = batchShareSettings.delay || 1000;
batchShareContainer.querySelector('#batch-share-traffic').value = batchShareSettings.traffic || 0;
batchShareContainer.querySelector('#batch-share-auto-fill').checked = batchShareSettings.randomExtractCode || false;
batchShareContainer.querySelector('#batch-share-anonymous').checked = batchShareSettings.anonymous !== false;
batchShareContainer.querySelector('#batch-share-auto-save').checked = batchShareSettings.autoSave !== false;
batchShareContainer.querySelector('#batch-share-get-size').checked = batchShareSettings.getSize || false;
const apiGetSizeEl = batchShareContainer.querySelector('#batch-share-api-get-size');
if (apiGetSizeEl) apiGetSizeEl.checked = batchShareSettings.useApiGetSize || false;
const fileListDiv = batchShareContainer.querySelector('#batch-share-file-list');
const fileCountSpan = batchShareContainer.querySelector('#batch-share-file-count');
const container = batchShareContainer.querySelector('#batch-share-file-list-container');
const progressWrap = batchShareContainer.querySelector('#batch-share-progress');
if (!isSharing && fileListDiv && fileCountSpan && container) {
fileListDiv.style.display = 'none';
fileCountSpan.textContent = '';
container.innerHTML = '';
} else if (isSharing && results.length > 0) {
if (fileListDiv) fileListDiv.style.display = 'block';
if (fileCountSpan) fileCountSpan.textContent = `共 ${results.length} 个文件/文件夹`;
if (container) {
container.innerHTML = '';
results.forEach((result, index) => {
const fileItem = document.createElement('div');
fileItem.className = `batch-result-item compact-layout batch-share-result-item ${result.success ? 'success' : 'error'}`;
fileItem.setAttribute('data-index', index);
fileItem.setAttribute('data-success', result.success);
fileItem.setAttribute('data-filename', result.fileName);
fileItem.setAttribute('data-filesize', result.fileSize || 0);
fileItem.setAttribute('data-sharelink', result.shareLink || '');
fileItem.setAttribute('data-extractcode', result.extractCode || '');
fileItem.setAttribute('data-msg', result.msg || '');
fileItem.innerHTML = `
<div class="batch-result-item-title">
<span class="batch-share-file-type"></span>
<span class="batch-share-file-name"></span>
<div class="batch-result-item-actions"></div>
</div>
<div class="batch-result-item-details batch-share-details-container">
<span class="file-size"></span>
<span class="batch-share-result-link"></span>
<span class="batch-result-status batch-share-status-auto"></span>
</div>
`;
const titleDiv = fileItem.querySelector('.batch-result-item-title');
const detailsDiv = fileItem.querySelector('.batch-result-item-details');
const statusDiv = fileItem.querySelector('.batch-result-item-status');
const fileType = getFileTypeDisplayName(getFileType(result.fileName, result.fileType));
titleDiv.querySelector('.batch-share-file-type').textContent = fileType;
titleDiv.querySelector('.batch-share-file-name').textContent = result.fileName;
titleDiv.querySelector('.batch-share-file-name').title = result.fileName;
detailsDiv.querySelector('.file-size').textContent = result.fileSize ? formatFileSize(result.fileSize) : '-';
if (result.success) {
const fullLink = `${result.shareLink}${result.extractCode ? `?password=${result.extractCode}` : ''}`;
const linkSpan = detailsDiv.querySelector('.batch-share-result-link');
linkSpan.textContent = fullLink;
linkSpan.title = '点击打开链接';
linkSpan.onclick = () => {
window.open(fullLink, '_blank');
};
const statusSpan = detailsDiv.querySelector('.batch-result-status');
if (statusSpan) {
statusSpan.textContent = '分享成功';
statusSpan.className = 'batch-result-status success';
}
} else {
const statusSpan = detailsDiv.querySelector('.batch-result-status');
if (statusSpan) {
statusSpan.textContent = result.msg;
statusSpan.className = 'batch-share-error-msg';
}
}
container.appendChild(fileItem);
});
}
}
if (!isSharing && progressWrap) {
progressWrap.classList.remove('show');
const statusDisplay = batchShareContainer.querySelector('#batch-share-status-display');
if (statusDisplay) {
statusDisplay.style.display = 'none';
}
} else if (isSharing && progressWrap) {
progressWrap.classList.add('show');
}
const statusDisplay = batchShareContainer.querySelector('#batch-share-status-display');
if (statusDisplay && !isSharing) {
statusDisplay.style.display = 'none';
}
setTimeout(() => {
const checkFileSelection = async () => {
const iframe = document.querySelector("iframe");
const iframeWindow = iframe?.contentWindow || unsafeWindow;
const selectDOM = iframeWindow?.document?.querySelectorAll("div.list-contents > ul li.selected");
if (!selectDOM || selectDOM.length === 0) {
if (!isSharing) {
const fileListDiv = batchShareContainer.querySelector('#batch-share-file-list');
const fileCountSpan = batchShareContainer.querySelector('#batch-share-file-count');
const container = batchShareContainer.querySelector('#batch-share-file-list-container');
const progressWrap = batchShareContainer.querySelector('#batch-share-progress');
if (fileListDiv) fileListDiv.style.display = 'none';
if (fileCountSpan) fileCountSpan.textContent = '';
if (container) container.innerHTML = '';
if (progressWrap) progressWrap.classList.remove('show');
}
return;
}
const getSizeEnabled = batchShareContainer.querySelector('#batch-share-get-size').checked;
const files = [];
for (const itemDOM of selectDOM) {
const fileType = itemDOM.getAttribute("file_type") === "0" ? "folder" : "file";
const id = fileType === "folder" ? itemDOM.getAttribute("cate_id") : itemDOM.getAttribute("file_id");
const fileName = itemDOM.getAttribute("title");
let fileSizeBytes = 0;
let fileSizeDisplay = "";
if (getSizeEnabled) {
if (fileType === "folder") {
try {
const response = await new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: "GET",
url: `https://webapi.115.com/category/get?cid=${id}`,
onload: r => {
try {
const data = JSON.parse(r.responseText);
resolve(data);
} catch(e) { reject(e); }
},
onerror: e => reject(e)
});
});
if (response && response.size) {
const sizeStr = response.size;
const sizeMatch = sizeStr.match(/^([\d.]+)([KMGT]?B)$/i);
if (sizeMatch) {
const value = parseFloat(sizeMatch[1]);
const unit = sizeMatch[2].toUpperCase();
const multipliers = { 'B': 1, 'KB': 1024, 'MB': 1024*1024, 'GB': 1024*1024*1024, 'TB': 1024*1024*1024*1024 };
fileSizeBytes = Math.round(value * multipliers[unit]);
fileSizeDisplay = sizeStr;
}
}
} catch (error) {
console.error('获取文件夹大小失败:', error);
fileSizeDisplay = "获取失败";
}
} else {
const size = itemDOM.getAttribute("file_size");
fileSizeBytes = size ? Number(size) : 0;
fileSizeDisplay = fileSizeBytes ? formatFileSize(fileSizeBytes) : "";
}
} else {
fileSizeDisplay = "-";
}
files.push({
id,
fileName,
fileSize: fileSizeBytes,
fileSizeDisplay,
fileType,
status: "ready"
});
}
if (files.length > 0) {
const fileListDiv = batchShareContainer.querySelector('#batch-share-file-list');
const fileCountSpan = batchShareContainer.querySelector('#batch-share-file-count');
const container = batchShareContainer.querySelector('#batch-share-file-list-container');
if (fileListDiv && fileCountSpan && container) {
fileListDiv.style.display = 'block';
fileCountSpan.textContent = `共 ${files.length} 个`;
container.innerHTML = '';
files.forEach((file, index) => {
const fileType = getFileType(file.fileName, file.fileType);
const typeDisplayName = getFileTypeDisplayName(fileType);
const typeSVG = getFileTypeSVG(fileType);
const fileItem = document.createElement('div');
fileItem.className = 'batch-result-item compact-layout batch-share-file-item';
fileItem.setAttribute('data-index', index);
fileItem.innerHTML = `
<div class="batch-result-item-title">
<span class="batch-share-file-type">${typeDisplayName}</span>
<span class="batch-share-file-name" title="${file.fileName}">${file.fileName}</span>
</div>
<div class="batch-result-item-details">
<span class="file-size">${file.fileSizeDisplay || '-'}</span>
<span class="batch-share-status-pending">待分享</span>
</div>
<div class="batch-result-item-actions">
<span class="batch-share-status-preparing">准备中</span>
</div>
`;
container.appendChild(fileItem);
});
}
}
};
checkFileSelection();
}, 100);
return;
}
const batchShareSettings = JSON.parse(localStorage.getItem('batchShareSettings') || '{}');
batchShareContainer.innerHTML = `
<div id="batch-share-flex-row" class="batch-share-flex-row">
<div class="batch-share-flex-column">
<label class="label batch-share-label">分享时长</label>
<select id="batch-share-expire" class="input batch-share-input">
<option value="-1">长期</option>
<option value="1">1天</option>
<option value="3">3天</option>
<option value="7">7天</option>
<option value="15">15天</option>
</select>
</div>
<div class="batch-share-flex-column">
<label class="label batch-share-label">接收次数</label>
<input type="number" id="batch-share-limit" class="input batch-share-input" value="${batchShareSettings.limit||0}" min="0" placeholder="0为无限制">
</div>
<div class="batch-share-flex-column">
<label class="label batch-share-label">提取码</label>
<input type="text" id="batch-share-code" class="input batch-share-input" placeholder="可选" maxlength="4" value="${batchShareSettings.code||''}">
</div>
<div class="batch-share-flex-column">
<label class="label batch-share-label">延迟(毫秒)</label>
<input type="number" id="batch-share-delay" class="input batch-share-input" value="${batchShareSettings.delay||1000}" min="100">
</div>
<div class="batch-share-flex-column">
<label class="label batch-share-label">免登流量(㎅)</label>
<input type="number" id="batch-share-traffic" class="input batch-share-input" value="${batchShareSettings.traffic||0}" min="0" placeholder="0为无限制">
</div>
</div>
<div class="batch-share-layout-container">
<div class="batch-share-switches-container">
<div class="batch-share-switch-group">
<label class="label">随机提取码</label>
<span class="element-block-switch element-block-switch-clickable">
<input type="checkbox" id="batch-share-auto-fill" ${batchShareSettings.randomExtractCode?"checked":""} class="batch-share-checkbox">
<span class="element-block-slider"></span>
</span>
</div>
<div class="batch-share-switch-group">
<label class="label">免登录(不可用)下载</label>
<span class="element-block-switch element-block-switch-clickable">
<input type="checkbox" id="batch-share-anonymous" ${(batchShareSettings.anonymous===undefined||batchShareSettings.anonymous)?"checked":""} class="batch-share-checkbox">
<span class="element-block-slider"></span>
</span>
</div>
<div class="batch-share-switch-group">
<label class="label">存储到管理页</label>
<span class="element-block-switch element-block-switch-clickable">
<input type="checkbox" id="batch-share-auto-save" ${(batchShareSettings.autoSave===undefined||batchShareSettings.autoSave)?"checked":""} class="batch-share-checkbox">
<span class="element-block-slider"></span>
</span>
</div>
<div class="batch-share-switch-group">
<label class="label">API获取大小</label>
<span class="element-block-switch element-block-switch-clickable">
<input type="checkbox" id="batch-share-api-get-size" ${batchShareSettings.useApiGetSize?"checked":""} class="batch-share-checkbox">
<span class="element-block-slider"></span>
</span>
</div>
<div class="batch-share-switch-group">
<label class="label">获取大小</label>
<span class="element-block-switch element-block-switch-clickable">
<input type="checkbox" id="batch-share-get-size" ${batchShareSettings.getSize?"checked":""} class="batch-share-checkbox">
<span class="element-block-slider"></span>
</span>
</div>
</div>
<div class="batch-share-buttons-container">
<button id="batch-share-refresh-btn" class="btn-primary batch-recognize-start-btn">刷新列表</button>
<button id="batch-share-toggle-btn" class="btn-primary batch-recognize-start-btn">开始分享</button>
</div>
</div>
<!-- 文件列表显示区域 -->
<div id="batch-share-file-list" class="batch-share-file-list">
<div class="batch-share-file-list-header">
<span class="storage-item-title">已选文件列表</span>
<span id="batch-share-file-count" class="batch-share-file-count"></span>
</div>
<div id="batch-share-file-list-container" class="batch-share-file-list-container">
</div>
</div>
<div id="batch-share-progress" class="progress-container batch-recognize-progress batch-share-progress">
<div class="progress-header batch-recognize-progress-header flex-between">
<span id="batch-share-progress-text"><span class="batch-recognize-progress-status">
<span class="status-progress">进度: 0/0 (0%)</span>
<span class="status-separator">|</span>
<span class="status-success">成功: 0</span>
<span class="status-separator">|</span>
<span class="status-failed">失败: 0</span>
</span></span>
<div class="batch-share-progress-actions flex-gap">
<button id="batch-share-copy-all-btn" class="storage-import-export-btn batch-recognize-export-btn batch-share-copy-all-btn btn-small">复制全部</button>
<button id="batch-share-export-btn" class="storage-import-export-btn batch-recognize-export-btn batch-share-export-btn btn-small" data-text="导出结果">导出结果</button>
</div>
</div>
<div class="progress-bar" id="batch-share-progress-bar"></div>
<div id="batch-share-status-display" class="batch-share-status-display"></div>
</div>
`;
batchShareContainer.querySelector('#batch-share-expire').value = batchShareSettings.expire || '-1';
batchShareContainer.querySelector('#batch-share-expire').addEventListener('change', saveSettings);
batchShareContainer.querySelector('#batch-share-limit').addEventListener('input', saveSettings);
batchShareContainer.querySelector('#batch-share-code').addEventListener('input', saveSettings);
batchShareContainer.querySelector('#batch-share-delay').addEventListener('input', saveSettings);
batchShareContainer.querySelector('#batch-share-traffic').addEventListener('input', saveSettings);
const autoFillCheckbox = batchShareContainer.querySelector('#batch-share-auto-fill');
const anonymousCheckbox = batchShareContainer.querySelector('#batch-share-anonymous');
const autoSaveCheckbox = batchShareContainer.querySelector('#batch-share-auto-save');
const apiGetSizeCheckbox = batchShareContainer.querySelector('#batch-share-api-get-size');
const getSizeCheckbox = batchShareContainer.querySelector('#batch-share-get-size');
if (autoFillCheckbox) {
autoFillCheckbox.addEventListener('change', () => {
saveSettings();
updateCodeInputState();
});
}
if (anonymousCheckbox) {
anonymousCheckbox.addEventListener('change', saveSettings);
}
if (autoSaveCheckbox) {
autoSaveCheckbox.addEventListener('change', saveSettings);
}
if (apiGetSizeCheckbox) {
apiGetSizeCheckbox.addEventListener('change', saveSettings);
}
if (getSizeCheckbox) {
getSizeCheckbox.addEventListener('change', () => {
saveSettings();
const container = batchShareContainer.querySelector('#batch-share-file-list-container');
if (container && container.children.length > 0) {
const currentFiles = Array.from(container.children).map(item => {
const fileName = item.querySelector('.batch-share-file-name').textContent;
const fileSizeSpan = item.querySelector('.file-size');
const fileSizeDisplay = fileSizeSpan ? fileSizeSpan.textContent : '';
return {
fileName: fileName,
fileSizeDisplay: fileSizeDisplay
};
});
showFileList(currentFiles, true);
}
});
}
const switchElements = batchShareContainer.querySelectorAll('.element-block-switch');
switchElements.forEach(switchElement => {
switchElement.addEventListener('click', (e) => {
const checkbox = switchElement.querySelector('input[type="checkbox"]');
if (checkbox) {
checkbox.checked = !checkbox.checked;
checkbox.dispatchEvent(new Event('change', { bubbles: true }));
}
});
});
function updateCodeInputState() {
const codeInput = batchShareContainer.querySelector('#batch-share-code');
if (autoFillCheckbox.checked) {
codeInput.disabled = true;
codeInput.classList.add('batch-share-btn-disabled');
codeInput.classList.remove('batch-share-btn-enabled');
codeInput.placeholder = '随机生成';
} else {
codeInput.disabled = false;
codeInput.classList.add('batch-share-btn-enabled');
codeInput.classList.remove('batch-share-btn-disabled');
codeInput.placeholder = '可选';
}
}
updateCodeInputState();
function saveSettings() {
localStorage.setItem('batchShareSettings', JSON.stringify({
expire: batchShareContainer.querySelector('#batch-share-expire').value,
limit: batchShareContainer.querySelector('#batch-share-limit').value,
code: batchShareContainer.querySelector('#batch-share-code').value,
delay: batchShareContainer.querySelector('#batch-share-delay').value,
traffic: batchShareContainer.querySelector('#batch-share-traffic').value,
randomExtractCode: batchShareContainer.querySelector('#batch-share-auto-fill').checked,
anonymous: batchShareContainer.querySelector('#batch-share-anonymous').checked,
autoSave: batchShareContainer.querySelector('#batch-share-auto-save').checked,
getSize: batchShareContainer.querySelector('#batch-share-get-size').checked,
useApiGetSize: (batchShareContainer.querySelector('#batch-share-api-get-size') && batchShareContainer.querySelector('#batch-share-api-get-size').checked) || false
}));
}
const flexRow = batchShareContainer.querySelector('#batch-share-flex-row');
const fileListContainer = batchShareContainer.querySelector('#batch-share-file-list-container');
if (windowElement.classList.contains('maximized')) {
flexRow.classList.add('batch-share-flex-row-maximized');
if (fileListContainer) {
fileListContainer.classList.add('batch-share-file-list-maximized');
fileListContainer.classList.remove('batch-share-file-list-normal');
}
} else {
flexRow.classList.remove('batch-share-flex-row-maximized');
if (fileListContainer) {
fileListContainer.classList.add('batch-share-file-list-normal');
fileListContainer.classList.remove('batch-share-file-list-maximized');
}
}
const expireSelect = batchShareContainer.querySelector('#batch-share-expire');
const limitInput = batchShareContainer.querySelector('#batch-share-limit');
const codeInput = batchShareContainer.querySelector('#batch-share-code');
const delayInput = batchShareContainer.querySelector('#batch-share-delay');
const trafficInput = batchShareContainer.querySelector('#batch-share-traffic');
const refreshBtn = batchShareContainer.querySelector('#batch-share-refresh-btn');
const toggleBtn = batchShareContainer.querySelector('#batch-share-toggle-btn');
const progressBar = batchShareContainer.querySelector('#batch-share-progress-bar');
const progressWrap = batchShareContainer.querySelector('#batch-share-progress');
const progressText = batchShareContainer.querySelector('#batch-share-progress-text');
const exportBtn = batchShareContainer.querySelector('#batch-share-export-btn');
const fileListDiv = batchShareContainer.querySelector('#batch-share-file-list');
const fileCountSpan = batchShareContainer.querySelector('#batch-share-file-count');
let results = [];
let isSharing = false;
let isCancelling = false;
let totalItems = 0;
let processedItems = 0;
let successCount = 0;
let failedCount = 0;
async function getSelectedFiles() {
const iframe = document.querySelector("iframe");
const iframeWindow = iframe?.contentWindow || unsafeWindow;
const selectDOM = iframeWindow?.document?.querySelectorAll("div.list-contents > ul li.selected");
if (!selectDOM || selectDOM.length === 0) {
return [];
}
const getSizeEnabled = batchShareContainer.querySelector('#batch-share-get-size').checked;
const files = [];
for (const itemDOM of selectDOM) {
const fileType = itemDOM.getAttribute("file_type") === "0" ? "folder" : "file";
const id = fileType === "folder" ? itemDOM.getAttribute("cate_id") : itemDOM.getAttribute("file_id");
const fileName = itemDOM.getAttribute("title");
let fileSizeBytes = 0;
let fileSizeDisplay = "";
if (fileType !== "folder") {
const size = itemDOM.getAttribute("file_size");
fileSizeBytes = size ? Number(size) : 0;
if (getSizeEnabled) {
fileSizeDisplay = fileSizeBytes ? formatFileSize(fileSizeBytes) : "";
} else {
fileSizeDisplay = "-";
}
} else if (getSizeEnabled) {
try {
const response = await new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: "GET",
url: `https://webapi.115.com/category/get?cid=${id}`,
onload: r => {
try {
const data = JSON.parse(r.responseText);
resolve(data);
} catch(e) { reject(e); }
},
onerror: e => reject(e)
});
});
if (response && response.size) {
const sizeStr = response.size;
const sizeMatch = sizeStr.match(/^([\d.]+)([KMGT]?B)$/i);
if (sizeMatch) {
const value = parseFloat(sizeMatch[1]);
const unit = sizeMatch[2].toUpperCase();
const multipliers = { 'B': 1, 'KB': 1024, 'MB': 1024*1024, 'GB': 1024*1024*1024, 'TB': 1024*1024*1024*1024 };
fileSizeBytes = Math.round(value * multipliers[unit]);
fileSizeDisplay = sizeStr;
}
}
} catch (error) {
console.error('获取文件夹大小失败:', error);
fileSizeDisplay = "获取失败";
}
} else {
fileSizeDisplay = "-";
}
files.push({
id,
fileName,
fileSize: fileSizeBytes,
fileSizeDisplay,
fileType,
status: "ready"
});
}
return files;
}
function getFileType(fileName, fileType) {
if (fileType === 'folder') return 'folder';
const ext = fileName.split('.').pop()?.toLowerCase();
if (!ext) return 'document';
const imageExts = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg', 'tif', 'tiff', 'tga', 'psd', 'iconic'];
const documentExts = ['pdf', 'doc', 'docx', 'xls', 'xlsx', 'xltm', 'ppt', 'pptx', 'txt', 'rtf', 'xml', 'html', 'htm', 'json', 'js', 'py', 'tpl', 'idx', 'log', 'ini', 'torrent', 'ssa', 'str', 'reg'];
const softwareExts = ['exe', 'msi', 'dmg', 'pkg', 'deb', 'rpm', 'apk', 'bat'];
const audioExts = ['mp3', 'wav', 'flac', 'aac', 'ogg', 'wma', 'm4a'];
const videoExts = ['mp4', 'avi', 'mkv', 'mov', 'wmv', 'flv', 'webm', 'm4v', 'vob', 'ts', 'mts', 'm2ts', '3gp', 'mod', 'dv', 'swf'];
const compressExts = ['zip', 'rar', '7z', 'tar', 'gz', 'xz', 'bz2', 'iso'];
if (imageExts.includes(ext)) return 'image';
if (documentExts.includes(ext)) return 'document';
if (softwareExts.includes(ext)) return 'software';
if (audioExts.includes(ext)) return 'audio';
if (videoExts.includes(ext)) return 'video';
if (compressExts.includes(ext)) return 'compress';
return 'other';
}
function getFileTypeSVG(type, fileName = '') {
const base = 'https://cdnres.115.com/site/static/style_v10.0/file/images/file_type';
const imageExts = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg', 'tif', 'tiff'];
const documentExts = ['pdf', 'doc', 'docx', 'xls', 'xlsx', 'xltm', 'ppt', 'pptx', 'txt', 'rtf', 'chm', 'html', 'htm', 'json', 'idx', 'log', 'ini', 'torrent', 'tpl', 'ssa', 'str', 'reg'];
const softwareExts = ['exe', 'msi', 'dmg', 'pkg', 'deb', 'rpm', 'apk', 'bat'];
const audioExts = ['mp3', 'wav', 'flac', 'aac', 'ogg', 'wma', 'm4a', 'dts'];
const videoExts = ['mp4', 'avi', 'mkv', 'mov', 'wmv', 'flv', 'webm', 'm4v', 'vob', 'ts', 'mts', 'm2ts', '3gp', 'mod', 'dv', 'swf'];
const compressExts = ['zip', 'rar', '7z', 'tar', 'gz', 'xz', 'bz2', 'iso'];
let ext = '';
try {
const name = (fileName || '').split('/').pop();
const dot = name.lastIndexOf('.');
ext = dot >= 0 ? name.slice(dot + 1).toLowerCase() : '';
} catch (e) { ext = ''; }
const inSet = (set, e) => Array.isArray(set) && set.includes(e);
let url = `${base}/other/unknown.svg`;
switch (type) {
case 'folder':
url = `${base}/folder/folder.svg`;
break;
case 'image':
if (ext === 'psd') {
url = `${base}/source/psd.svg`;
} else if (ext === 'iconic') {
url = `${base}/image/other_pic.svg`;
} else {
url = inSet(imageExts, ext) ? `${base}/image/${ext}.svg` : `${base}/image/other_pic.svg`;
}
break;
case 'document':
if (ext === 'xml' || ext === 'py' || ext === 'js' || ext === 'tpl') {
url = `${base}/code/code.svg`;
} else if (ext === 'html') {
url = `${base}/code/html.svg`;
} else if (ext === 'htm') {
url = `${base}/code/htm.svg`;
} else if (ext === 'xltm') {
url = `${base}/document/xls.svg`;
} else if (ext === 'ini') {
url = `${base}/document/ini.svg?_vh=c7c4575_89`;
} else {
url = `${base}/document/${inSet(documentExts, ext) ? ext : 'document'}.svg`;
}
break;
case 'software':
url = `${base}/application/${inSet(softwareExts, ext) ? ext : 'exe'}.svg`;
break;
case 'audio':
url = `${base}/audio/${inSet(audioExts, ext) ? ext : 'audio'}.svg`;
break;
case 'video':
url = `${base}/video/${inSet(videoExts, ext) ? ext : 'video'}.svg`;
break;
case 'compress':
if (ext === 'xz') {
url = `${base}/archive/rar.svg`;
} else {
url = `${base}/archive/${inSet(compressExts, ext) ? ext : 'archive'}.svg`;
}
break;
default:
url = `${base}/other/unknown.svg`;
}
return `<img class="batch-share-file-icon" src="${url}" alt="${type}">`;
}
function getFileTypeDisplayName(type) {
const typeMap = {
'folder': '文件夹',
'image': '图片',
'document': '文档',
'software': '软件',
'audio': '音频',
'video': '视频',
'compress': '压缩包',
'other': '其他'
};
return typeMap[type] || '文件';
}
function setupFileSelectionWatcher() {
let lastSelectedCount = 0;
let lastSelectedFiles = '';
async function checkFileSelection() {
if (isSharing) {
return;
}
const files = await getSelectedFiles();
const currentCount = files.length;
const currentFiles = JSON.stringify(files.map(f => ({id: f.id, fileName: f.fileName})));
const hasSelectionChanged = currentCount !== lastSelectedCount || currentFiles !== lastSelectedFiles;
const activeTab = document.querySelector('.storage-tab.active');
if (activeTab && activeTab.getAttribute('data-tab') === 'batchshare' && hasSelectionChanged) {
showBatchShareStatus('正在获取文件信息...', '#4285f4');
}
lastSelectedCount = currentCount;
lastSelectedFiles = currentFiles;
if (activeTab && activeTab.getAttribute('data-tab') === 'batchshare') {
if (currentCount === 0) {
showBatchShareStatus('请先选择要分享的文件', '#f44336');
results = [];
const container = batchShareContainer.querySelector('#batch-share-file-list-container');
const progressWrap = batchShareContainer.querySelector('#batch-share-progress');
const fileListDiv = batchShareContainer.querySelector('#batch-share-file-list');
const fileCountSpan = batchShareContainer.querySelector('#batch-share-file-count');
if (container) container.innerHTML = '';
if (fileListDiv) fileListDiv.style.display = 'none';
if (fileCountSpan) fileCountSpan.textContent = '';
if (progressWrap) progressWrap.classList.remove('show');
} else {
const previousFiles = JSON.stringify(results.map(r => ({id: r.id, fileName: r.fileName})));
if (currentFiles !== previousFiles) {
results = [];
showFileList(files, true);
} else {
const fileCountSpan = batchShareContainer.querySelector('#batch-share-file-count');
if (fileCountSpan) {
fileCountSpan.textContent = `共 ${currentCount} 项`;
}
}
}
}
}
const iframe = document.querySelector("iframe");
if (iframe && iframe.contentWindow) {
try {
const debouncedCheck = debounce(() => checkFileSelection(), 150);
iframe.contentWindow.document.addEventListener('click', function(e) {
if (e.target.closest('li') || e.target.closest('.list-contents')) {
debouncedCheck();
}
});
iframe.contentWindow.document.addEventListener('keydown', function(e) {
if (e.key === ' ' || e.key === 'Enter' || e.key === 'Escape' || e.ctrlKey || e.metaKey) {
debouncedCheck();
}
});
iframe.contentWindow.document.addEventListener('selectionchange', function(e) {
debouncedCheck();
});
iframe.contentWindow.document.addEventListener('change', function(e) {
debouncedCheck();
});
iframe.contentWindow.document.addEventListener('input', function(e) {
debouncedCheck();
});
const observer = new MutationObserver(function(mutations) {
const hasRelevantChanges = mutations.some(mutation => {
return mutation.type === 'attributes' &&
(mutation.attributeName === 'class' || mutation.attributeName === 'data-selected') ||
mutation.type === 'childList' &&
(mutation.target.closest('li') || mutation.target.closest('.list-contents'));
});
if (hasRelevantChanges) {
debouncedCheck();
}
});
observer.observe(iframe.contentWindow.document.body, {
childList: true,
subtree: true,
attributes: true,
attributeFilter: ['class', 'data-selected']
});
} catch (e) {
console.error('设置文件选择监听器失败:', e);
}
}
checkFileSelection();
}
function showFileList(files, clearContainer = true) {
if (isSharing) {
return;
}
const fileListDiv = batchShareContainer.querySelector('#batch-share-file-list');
const fileCountSpan = batchShareContainer.querySelector('#batch-share-file-count');
const container = batchShareContainer.querySelector('#batch-share-file-list-container');
if (!files || files.length === 0) {
if (fileListDiv) fileListDiv.style.display = 'none';
if (fileCountSpan) fileCountSpan.textContent = '';
if (container) container.innerHTML = '';
return;
}
if (fileListDiv) fileListDiv.style.display = 'block';
if (fileCountSpan) fileCountSpan.textContent = `共 ${files.length} 个文件/文件夹`;
if (clearContainer && container) {
container.innerHTML = '';
}
if (windowElement.classList.contains('maximized')) {
container.style.maxHeight = 'calc(100vh - 400px)';
container.style.height = 'calc(100vh - 400px)';
} else {
container.style.maxHeight = '290px';
container.style.height = '';
}
const getSizeCheckbox = batchShareContainer.querySelector('#batch-share-get-size');
const showFileSize = getSizeCheckbox ? getSizeCheckbox.checked : false;
files.forEach((file, index) => {
const fileType = getFileType(file.fileName, file.fileType);
const typeSVG = getFileTypeSVG(fileType, file.fileName);
const fileSizeDisplay = showFileSize && file.fileSizeDisplay ?
`<span class="file-size">${file.fileSizeDisplay}</span>` : '';
const fileItem = document.createElement('div');
fileItem.className = 'batch-result-item compact-layout';
fileItem.setAttribute('data-index', index);
fileItem.innerHTML = `
<div class="batch-result-item-title">
${typeSVG}
<span class="batch-share-file-name" title="${file.fileName}">${file.fileName}</span>
</div>
<div class="batch-result-item-details">
${fileSizeDisplay}
<span class="batch-share-status-pending">待分享</span>
</div>
<div class="batch-result-item-actions">
<span class="batch-share-status-preparing">准备中</span>
</div>
`;
container.appendChild(fileItem);
});
}
function updateFileListStatus(index, status, shareLink = '', extractCode = '', info = '') {
const container = batchShareContainer.querySelector('#batch-share-file-list-container');
const fileItem = container.querySelector(`[data-index="${index}"]`);
if (fileItem) {
const titleDiv = fileItem.querySelector('.batch-result-item-title');
const detailsDiv = fileItem.querySelector('.batch-result-item-details');
const actionsDiv = fileItem.querySelector('.batch-result-item-actions');
const existingIconDiv = titleDiv.querySelector('img.batch-share-file-icon');
const fileName = titleDiv.querySelector('.batch-share-file-name').textContent;
const iconHTML = existingIconDiv ? existingIconDiv.outerHTML : getFileTypeSVG(getFileType(fileName, ''), fileName);
const getSizeCheckbox = batchShareContainer.querySelector('#batch-share-get-size');
const showFileSize = getSizeCheckbox ? getSizeCheckbox.checked : false;
const currentFileSizeSpan = detailsDiv.querySelector('.file-size');
const currentFileSize = currentFileSizeSpan ? currentFileSizeSpan.textContent : '';
const fileSizeDisplay = showFileSize && currentFileSize ?
`<span class="file-size">${currentFileSize}</span>` : '';
if (status === 'success') {
fileItem.className = 'batch-result-item compact-layout success';
const fullLink = `${shareLink}${extractCode ? `?password=${extractCode}` : ''}`;
titleDiv.innerHTML = `
${iconHTML}
<span class="batch-share-file-name" title="${fileName}">${fileName}</span>
<div class="batch-share-actions-container">
<button class="storage-item-btn copy-btn batch-share-copy-btn batch-share-action-btn">复制</button>
<button class="storage-item-btn open-btn batch-share-action-btn">打开</button>
</div>
`;
detailsDiv.innerHTML = `
${fileSizeDisplay}
<span class="batch-share-result-link" title="点击打开链接" onclick="window.open('${fullLink}', '_blank')">${fullLink}</span>
<span class="batch-result-status success batch-share-status-success">分享成功</span>
`;
actionsDiv.innerHTML = '';
const copyBtn = titleDiv.querySelector('.copy-btn');
const openBtn = titleDiv.querySelector('.open-btn');
if (copyBtn) {
copyBtn.addEventListener('click', () => {
if (copyBtn._copyTimer) clearTimeout(copyBtn._copyTimer);
const title = fileName;
const text = `${fullLink}#\n${title}`;
navigator.clipboard.writeText(text).then(() => {
const originalText = copyBtn.textContent;
copyBtn.textContent = '已复制';
copyBtn.classList.add('copied');
copyBtn._copyTimer = setTimeout(() => {
copyBtn.textContent = originalText;
copyBtn.classList.remove('copied');
copyBtn._copyTimer = null;
}, 1000);
}).catch(() => alert('复制失败'));
});
}
if (openBtn) {
openBtn.addEventListener('click', () => {
window.open(fullLink, '_blank');
});
}
} else if (status === 'error') {
fileItem.className = 'batch-result-item compact-layout error';
titleDiv.innerHTML = `
${iconHTML}
<span class="batch-share-file-name" title="${fileName}">${fileName}</span>
`;
detailsDiv.innerHTML = `
${fileSizeDisplay}
<span class="batch-share-status-failed">分享失败</span>
`;
actionsDiv.innerHTML = `<span class="batch-share-error-msg">${info}</span>`;
} else if (status === 'processing') {
fileItem.className = 'batch-result-item compact-layout';
titleDiv.innerHTML = `
${iconHTML}
<span class="batch-share-file-name" title="${fileName}">${fileName}</span>
`;
detailsDiv.innerHTML = `
${fileSizeDisplay}
<span class="batch-share-status-processing">处理中</span>
`;
actionsDiv.innerHTML = '<span class="batch-share-status-sharing">分享中</span>';
}
}
}
function updateProgress() {
const progress = totalItems > 0 ? (processedItems / totalItems) * 100 : 0;
progressBar.style.width = `${progress}%`;
const expireText = expireSelect.value === '-1' ? '永久' : `${expireSelect.value}天`;
const codeText = autoFillCheckbox.checked ? '随机' : '自定义';
const delayText = `${delayInput.value}ms`;
const progressHtml = `<span class="batch-recognize-progress-status">
<span class="status-progress">进度: ${processedItems}/${totalItems} (${Math.round(progress)}%)</span>
<span class="status-separator">|</span>
<span class="status-success">成功: ${successCount}</span>
<span class="status-separator">|</span>
<span class="status-failed">失败: ${failedCount}</span>
</span>`;
if (progressText) {
progressText.innerHTML = progressHtml;
}
}
function showBatchShareStatus(msg, color, stats = null) {
const statusDisplay = batchShareContainer.querySelector('#batch-share-status-display');
if (stats) {
const statsMatch = stats.match(/成功:\s*(\d+)\s*\|\s*失败:\s*(\d+)/);
if (statsMatch) {
const successCount = parseInt(statsMatch[1]);
const failCount = parseInt(statsMatch[2]);
const totalCount = successCount + failCount;
const statusHtml = `<span class="batch-recognize-progress-status">
<span class="status-label">总数: ${totalCount}项</span>
<span class="status-separator">|</span>
<span class="status-success">成功: ${successCount}项</span>
<span class="status-separator">|</span>
<span class="status-failed">失败: ${failCount}项</span>
</span>`;
const currentProgressHtml = progressText.innerHTML;
if (currentProgressHtml && !currentProgressHtml.includes('总数:')) {
progressText.innerHTML = currentProgressHtml + '<br>' + statusHtml;
} else {
progressText.innerHTML = statusHtml;
}
if (statusDisplay) {
statusDisplay.style.display = 'none';
}
} else {
if (statusDisplay) {
statusDisplay.style.display = 'none';
}
}
} else {
if (statusDisplay) {
statusDisplay.innerHTML = `<span class="status-display-text">${msg}</span>`;
if (color === '#4caf50') {
statusDisplay.style.background = 'rgba(76, 175, 80, 0.9)';
} else if (color === '#f44336') {
statusDisplay.style.background = 'rgba(244, 67, 54, 0.9)';
} else if (color === '#ff9800') {
statusDisplay.style.background = 'rgba(255, 152, 0, 0.9)';
} else {
statusDisplay.style.background = 'rgba(66, 133, 244, 0.9)';
}
statusDisplay.style.display = 'block';
}
}
}
function generateRandomExtractCode() {
let code = '';
for (let i = 0; i < 4; i++) {
code += DEFAULT_CHARS[Math.floor(Math.random() * DEFAULT_CHARS.length)];
}
return code;
}
async function getShareInfoByCode(shareCode) {
try {
const response = await new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: 'GET',
url: `https://115cdn.com/webapi/share/snap?share_code=${shareCode}`,
onload: r => {
try {
const data = JSON.parse(r.responseText);
resolve(data);
} catch(e) { reject(e); }
},
onerror: e => reject(e)
});
});
if (response && response.state && response.data) {
const shareInfo = response.data.shareinfo || response.data;
const shareState = shareInfo.share_state;
const forbidReason = shareInfo.forbid_reason;
if (response.state === false || response.error_code || response.error_msg || forbidReason || shareState === -1) {
return {
shareTitle: '',
fileSize: 0,
expireTime: -1,
autoRenewal: '0',
isValid: false,
errorMsg: forbidReason || response.error_msg || '分享已取消或无效'
};
}
return {
shareTitle: processShareTitle({data: {shareinfo: shareInfo, list: response?.data?.list || []}}),
fileSize: parseInt(shareInfo.file_size || '0'),
expireTime: shareInfo.expire_time || -1,
autoRenewal: String(shareInfo.auto_renewal || '0'),
isValid: true
};
}
return null;
} catch (error) {
console.error('获取分享信息失败:', error);
return null;
}
}
async function batchShare() {
if (isSharing) {
return;
}
isSharing = true;
toggleBtn.disabled = true;
toggleBtn.classList.add('batch-share-btn-disabled');
toggleBtn.textContent = '正在分享...';
refreshBtn.disabled = true;
refreshBtn.classList.add('batch-share-btn-disabled');
refreshBtn.textContent = '刷新列表中...';
showBatchShareStatus('正在准备分享...', '#4285f4');
try {
const files = await getSelectedFiles();
if (!files.length) {
showBatchShareStatus('请先选择要分享的文件', '#f44336');
return;
}
const currentFiles = JSON.stringify(files.map(f => ({id: f.id, fileName: f.fileName})));
const previousFiles = JSON.stringify(results.map(r => ({id: r.id, fileName: r.fileName})));
if (!isSharing) {
if (currentFiles !== previousFiles) {
results = [];
showFileList(files, true);
} else {
showFileList(files, false);
}
}
const shareConfig = {
expireTime: parseInt(expireSelect.value),
customCode: codeInput.value,
acceptLimit: parseInt(limitInput.value) || 0,
shareDelay: parseInt(delayInput.value) || 1000,
randomExtractCode: autoFillCheckbox.checked,
allowAnonymousDownload: anonymousCheckbox.checked,
anonymousDownloadTraffic: parseInt(trafficInput.value) || 0
};
totalItems = files.length;
processedItems = 0;
successCount = 0;
failedCount = 0;
isCancelling = false;
progressWrap.style.display = 'block';
progressBar.style.width = '0%';
updateProgress();
toggleBtn.textContent = '取消分享';
toggleBtn.disabled = false;
toggleBtn.classList.remove('batch-share-btn-disabled');
for (let i = 0; i < files.length; i++) {
if (isCancelling) {
showBatchShareStatus('分享已取消', '#f44336');
break;
}
const file = files[i];
processedItems = i + 1;
updateProgress();
showBatchShareStatus(`正在分享 ${i+1}/${files.length}: ${file.fileName}`, '#4285f4');
updateFileListStatus(i, 'processing');
try {
const formDataFirst = new URLSearchParams();
const { user_id } = unsafeWindow || {};
formDataFirst.append("user_id", user_id);
formDataFirst.append("file_ids", file.id + "");
formDataFirst.append("ignore_warn", "1");
formDataFirst.append("is_asc", "0");
formDataFirst.append("order", "user_ptime");
const resultOne = await new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: "POST",
url: 'https://webapi.115.com/share/send',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
data: formDataFirst.toString(),
onload: r => {
try {
const data = JSON.parse(r.responseText);
resolve(data);
} catch(e) { reject(e); }
},
onerror: e => reject(e)
});
});
if (!resultOne || resultOne.error) {
throw new Error(resultOne?.error || '创建分享失败');
}
await new Promise(res => setTimeout(res, shareConfig.shareDelay));
const formDataSecond = new URLSearchParams();
const share_code = resultOne.data.share_code;
formDataSecond.append("share_code", share_code);
formDataSecond.append("auto_fill_recvcode", "0");
formDataSecond.append("receive_user_limit", shareConfig.acceptLimit || "");
formDataSecond.append("share_duration", shareConfig.expireTime);
let finalExtractCode = shareConfig.customCode;
if (!finalExtractCode && shareConfig.randomExtractCode) {
finalExtractCode = generateRandomExtractCode();
}
if (finalExtractCode && finalExtractCode !== "") {
formDataSecond.append("receive_code", finalExtractCode);
formDataSecond.append("is_custom_code", "1");
}
const resultTwo = await new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: "POST",
url: 'https://webapi.115.com/share/updateshare',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
data: formDataSecond.toString(),
onload: r => {
try {
const data = JSON.parse(r.responseText);
resolve(data);
} catch(e) { reject(e); }
},
onerror: e => reject(e)
});
});
if (!resultTwo || resultTwo.error) {
throw new Error(resultTwo?.error || '更新分享设置失败');
}
await new Promise(res => setTimeout(res, shareConfig.shareDelay));
if (shareConfig.allowAnonymousDownload && unsafeWindow?.USER_PERMISSION?.is_vip) {
const formDataThird = new URLSearchParams();
formDataThird.append("share_code", share_code);
formDataThird.append("skip_login", "1");
formDataThird.append("skip_login_down_flow_limit", shareConfig.anonymousDownloadTraffic);
try {
await new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: "POST",
url: 'https://webapi.115.com/share/skip_login_down',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
data: formDataThird.toString(),
onload: r => {
try {
const data = JSON.parse(r.responseText);
resolve(data);
} catch(e) { reject(e); }
},
onerror: e => reject(e)
});
});
} catch (error) {
console.error('设置免登录(不可用)下载失败:', error);
}
} else if (!shareConfig.allowAnonymousDownload && unsafeWindow?.USER_PERMISSION?.is_vip) {
const formDataThird = new URLSearchParams();
formDataThird.append("share_code", share_code);
formDataThird.append("skip_login", "0");
formDataThird.append("skip_login_down_flow_limit", "0");
try {
await new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: "POST",
url: 'https://webapi.115.com/share/skip_login_down',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
data: formDataThird.toString(),
onload: r => {
try {
const data = JSON.parse(r.responseText);
resolve(data);
} catch(e) { reject(e); }
},
onerror: e => reject(e)
});
});
} catch (error) {
console.error('设置免登录(不可用)下载失败:', error);
}
}
const { data = {} } = resultOne || {};
if (data.share_url) {
const result = {
fileName: file.fileName,
fileSize: file.fileSize,
fileType: file.fileType,
type: getFileType(file.fileName, file.fileType),
shareLink: data.share_url,
extractCode: finalExtractCode || data.receive_code || "",
expireTime: shareConfig.expireTime,
acceptLimit: shareConfig.acceptLimit,
anonymousDownloadTraffic: shareConfig.anonymousDownloadTraffic,
allowAnonymousDownload: shareConfig.allowAnonymousDownload,
success: true,
msg: "分享成功",
shareCode: data.share_code
};
results.push(result);
successCount++;
updateProgress();
updateFileListStatus(i, 'success', data.share_url, finalExtractCode || data.receive_code || "");
const autoSaveEnabled = batchShareContainer.querySelector('#batch-share-auto-save').checked;
if (autoSaveEnabled) {
try {
const localFileSize = parseInt(file.fileSize) || 0;
const useApiGetSize = (batchShareContainer.querySelector('#batch-share-api-get-size') && batchShareContainer.querySelector('#batch-share-api-get-size').checked) || false;
if (useApiGetSize) {
try {
const shareInfo = await getShareInfoByCode(data.share_code);
if (shareInfo && shareInfo.isValid) {
saveToStorage(
data.share_code,
finalExtractCode || data.receive_code || "",
`[批量分享] ${file.fileName}`,
shareInfo.shareTitle || file.fileName,
shareInfo.expireTime || shareConfig.expireTime,
shareInfo.fileSize || localFileSize,
shareInfo.autoRenewal || '0',
'',
''
);
} else {
if (shareInfo && !shareInfo.isValid) {
console.log(`分享无效: ${shareInfo.errorMsg}`);
}
saveToStorage(
data.share_code,
finalExtractCode || data.receive_code || "",
`[批量分享] ${file.fileName}`,
file.fileName,
shareConfig.expireTime,
localFileSize,
'0',
'',
''
);
}
} catch (apiError) {
console.error('通过API获取分享信息失败:', apiError);
saveToStorage(
data.share_code,
finalExtractCode || data.receive_code || "",
`[批量分享] ${file.fileName}`,
file.fileName,
shareConfig.expireTime,
localFileSize,
'0',
'',
''
);
}
} else {
saveToStorage(
data.share_code,
finalExtractCode || data.receive_code || "",
`[批量分享] ${file.fileName}`,
file.fileName,
shareConfig.expireTime,
localFileSize,
'0',
'',
''
);
}
} catch (error) {
console.error('存储到管理页失败:', error);
saveToStorage(
data.share_code,
finalExtractCode || data.receive_code || "",
`[批量分享] ${file.fileName}`,
file.fileName,
shareConfig.expireTime,
0,
'0',
'',
''
);
}
}
} else {
results.push({
fileName: file.fileName,
fileSize: file.fileSize,
fileType: file.fileType,
type: getFileType(file.fileName, file.fileType),
success: false,
msg: "分享失败: 未获取到分享链接"
});
failedCount++;
updateProgress();
updateFileListStatus(i, 'error', '', '', "未获取到分享链接");
}
} catch (error) {
console.error("分享文件失败:", file.fileName, error);
results.push({
fileName: file.fileName,
fileSize: file.fileSize,
fileType: file.fileType,
type: getFileType(file.fileName, file.fileType),
success: false,
msg: "分享失败: " + (error.message || "未知错误")
});
failedCount++;
updateProgress();
updateFileListStatus(i, 'error', '', '', error.message || "未知错误");
}
await new Promise(res => setTimeout(res, 100));
}
const stats = `成功: ${successCount} | 失败: ${failedCount}`;
showBatchShareStatus(`分享完成!`, successCount===files.length?'#4caf50':'#f44336', stats);
const exportBtn = batchShareContainer.querySelector('#batch-share-export-btn');
const copyAllBtn = batchShareContainer.querySelector('#batch-share-copy-all-btn');
const hasResults = results.length > 0;
if (exportBtn) exportBtn.style.display = hasResults ? 'inline-block' : 'none';
if (copyAllBtn) copyAllBtn.style.display = hasResults ? 'inline-block' : 'none';
updateProgress();
const statusDisplay = batchShareContainer.querySelector('#batch-share-status-display');
if (statusDisplay) {
statusDisplay.style.display = 'none';
}
isSharing = false;
toggleBtn.textContent = '开始分享';
toggleBtn.disabled = false;
toggleBtn.classList.remove('batch-share-btn-disabled');
if (results.length > 0) {
fileListDiv.style.display = 'block';
}
} catch (error) {
console.error('批量分享失败:', error);
showBatchShareStatus('批量分享失败: ' + (error.message || '未知错误'), '#f44336');
} finally {
const statusDisplay = batchShareContainer.querySelector('#batch-share-status-display');
if (statusDisplay) {
statusDisplay.style.display = 'none';
}
isSharing = false;
toggleBtn.disabled = false;
toggleBtn.classList.remove('batch-share-btn-disabled');
toggleBtn.textContent = '开始分享';
refreshBtn.disabled = false;
refreshBtn.classList.remove('batch-share-btn-disabled');
refreshBtn.textContent = '刷新列表';
}
}
refreshBtn.onclick = async function() {
if (refreshBtn.disabled) {
return;
}
refreshBtn.disabled = true;
refreshBtn.classList.add('batch-share-btn-disabled');
refreshBtn.textContent = '刷新列表中...';
try {
showBatchShareStatus('正在刷新文件列表...', '#4285f4');
progressWrap.style.display = 'none';
const exportBtn = batchShareContainer.querySelector('#batch-share-export-btn');
const copyAllBtn = batchShareContainer.querySelector('#batch-share-copy-all-btn');
if (exportBtn) exportBtn.style.display = 'none';
if (copyAllBtn) copyAllBtn.style.display = 'none';
const files = await getSelectedFiles();
if (files.length === 0) {
showBatchShareStatus('请先选择要分享的文件', '#f44336');
const container = batchShareContainer.querySelector('#batch-share-file-list-container');
container.innerHTML = '';
results = [];
fileListDiv.style.display = 'none';
fileCountSpan.textContent = '';
} else {
const currentFiles = JSON.stringify(files.map(f => ({id: f.id, fileName: f.fileName})));
const previousFiles = JSON.stringify(results.map(r => ({id: r.id, fileName: r.fileName})));
if (!isSharing) {
if (currentFiles !== previousFiles) {
results = [];
showFileList(files, true);
} else {
showFileList(files, false);
}
}
}
} catch (error) {
console.error('刷新文件列表失败:', error);
showBatchShareStatus('刷新文件列表失败: ' + (error.message || '未知错误'), '#f44336');
} finally {
const statusDisplay = batchShareContainer.querySelector('#batch-share-status-display');
if (statusDisplay) {
statusDisplay.style.display = 'none';
}
refreshBtn.disabled = false;
refreshBtn.classList.remove('batch-share-btn-disabled');
refreshBtn.textContent = '刷新列表';
}
};
toggleBtn.onclick = function() {
if (isSharing) {
if (confirm('确定要取消分享吗?')) {
isCancelling = true;
showBatchShareStatus('正在取消分享...', '#f44336');
const container = batchShareContainer.querySelector('#batch-share-file-list-container');
const fileItems = container.querySelectorAll('.batch-result-item');
fileItems.forEach((fileItem) => {
const titleDiv = fileItem.querySelector('.batch-result-item-title');
const detailsDiv = fileItem.querySelector('.batch-result-item-details');
const actionsDiv = fileItem.querySelector('.batch-result-item-actions');
if (titleDiv.innerHTML.includes('svg')) {
fileItem.className = 'batch-result-item compact-layout error';
titleDiv.innerHTML = `
<span class="batch-share-file-type">${titleDiv.querySelector('span:nth-child(2)').textContent}</span>
<span class="batch-share-file-name" title="${titleDiv.querySelector('span:last-child').textContent}">${titleDiv.querySelector('span:last-child').textContent}</span>
`;
detailsDiv.innerHTML = `
<span class="file-size">${detailsDiv.querySelector('span').textContent.split(': ')[1]}</span>
<span class="batch-share-status-cancelled">已取消</span>
`;
actionsDiv.innerHTML = '<span class="batch-share-error-msg">已取消</span>';
}
});
setTimeout(() => {
isSharing = false;
toggleBtn.textContent = '开始分享';
toggleBtn.disabled = false;
toggleBtn.classList.remove('batch-share-btn-disabled');
const statusDisplay = batchShareContainer.querySelector('#batch-share-status-display');
if (statusDisplay) {
statusDisplay.style.display = 'none';
}
const exportBtn = batchShareContainer.querySelector('#batch-share-export-btn');
const copyAllBtn = batchShareContainer.querySelector('#batch-share-copy-all-btn');
if (results.length > 0) {
if (exportBtn) exportBtn.style.display = 'inline-block';
if (copyAllBtn) copyAllBtn.style.display = 'inline-block';
const stats = `成功: ${successCount} | 失败: ${failedCount}`;
showBatchShareStatus('分享已取消', '#ff9800', stats);
} else {
showBatchShareStatus('分享已取消', '#f44336');
}
updateProgress();
}, 500);
}
} else {
batchShare();
}
};
exportBtn.onclick = function() {
if (!results.length) {
alert('没有分享结果可导出');
return;
}
const csvContent = [
['标题', '链接', '大小', '状态', '信息'],
...results.map(result => [
result.fileName,
result.success ? `${result.shareLink}${result.extractCode ? `?password=${result.extractCode}` : ''}` : '',
result.fileSize ? formatFileSize(result.fileSize) : '-',
result.success ? '成功' : (result.msg === '已取消' ? '已取消' : '失败'),
result.success ? '分享成功' : (result.msg === '已取消' ? '分享已取消' : result.msg)
])
].map(row => row.map(cell => `"${cell}"`).join(',')).join('\n');
const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
const link = document.createElement('a');
const url = URL.createObjectURL(blob);
link.setAttribute('href', url);
const now = new Date();
const year = now.getFullYear();
const month = String(now.getMonth() + 1).padStart(2, '0');
const day = String(now.getDate()).padStart(2, '0');
const hours = String(now.getHours()).padStart(2, '0');
const minutes = String(now.getMinutes()).padStart(2, '0');
const seconds = String(now.getSeconds()).padStart(2, '0');
link.setAttribute('download', `115分享结果_${year}-${month}-${day}_${hours}-${minutes}-${seconds}.csv`);
link.style.visibility = 'hidden';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
showBatchShareStatus('结果已导出为CSV文件', '#4caf50');
};
const copyAllBtn = batchShareContainer.querySelector('#batch-share-copy-all-btn');
if (copyAllBtn) {
copyAllBtn.onclick = function() {
if (!results.length) {
alert('没有分享结果可复制');
return;
}
const successfulResults = results.filter(result => result.success);
if (successfulResults.length === 0) {
alert('没有成功的分享结果可复制');
return;
}
const copyText = successfulResults.map(result => {
const fullLink = `${result.shareLink}${result.extractCode ? `?password=${result.extractCode}` : ''}`;
return `${result.fileName}\n${fullLink}`;
}).join('\n\n');
navigator.clipboard.writeText(copyText).then(() => {
const originalText = copyAllBtn.textContent;
copyAllBtn.textContent = '已复制';
copyAllBtn.classList.add('copied');
setTimeout(() => {
copyAllBtn.textContent = originalText;
copyAllBtn.classList.remove('copied');
}, 1000);
showBatchShareStatus(`已复制 ${successfulResults.length} 个分享链接`, '#4caf50');
}).catch(() => {
alert('复制失败');
});
};
}
setupFileSelectionWatcher();
}
const originalSetPosition = windowDrag.setPosition;
windowDrag.setPosition = function(x, y) {
if (typeof y === 'number' && y < 0) y = 0;
return originalSetPosition.call(this, x, y);
};
setupMaximizeButton();
setupProTagEdit();
})();