${showOwner && face ? `

` : ''}
${showOwner ? `用户ID:
${userId}` : ''}
${showOwner ? `用户名:
${userName}` : ''}
分享链接:
https://115cdn.com/s/${shareInfo.shareCode}
${errorMsg}
`;
ensurePasswordChecks();
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 = `
`;
ensurePasswordChecks();
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) {}
if (receiveCodeElement) {
shareDetails.innerHTML = `
${showOwner && shareInfo.face ? `

` : ''}
${showOwner ? `用户ID:
${shareInfo.userId}` : ''}
${showOwner ? `用户名:
${userName}` : ''}
分享链接:
${(() => { const base=`https://115cdn.com/s/${shareInfo.shareCode}`; const pwd=(receiveCode && receiveCode !== '未找到') ? `?password=${encodeURIComponent(receiveCode)}` : ''; const url=base + pwd; return `
${url}`; })()}
访问密码:
${receiveCode}
文件大小:
${formatFileSize(shareInfo.fileSize)}
${shareInfo.autoRenewal === '1' ? '
自动续期' : ''}
无需访问码
`;
} else {
shareDetails.innerHTML = `
${showOwner && shareInfo.face ? `

` : ''}
${showOwner ? `用户ID:
${shareInfo.userId}` : ''}
${showOwner ? `用户名:
${userName}` : ''}
分享链接:
${(() => { const base=`https://115cdn.com/s/${shareInfo.shareCode}`; const pwd=(typeof __verifiedOK !== 'undefined' && __verifiedOK && __verifiedPassword) ? `?password=${encodeURIComponent(__verifiedPassword)}` : ''; const url=base + pwd; return `
${url}`; })()}
请输入访问码
`;
try { checkStoredPassword(); } catch (e) {}
try { setupManualPasswordDetection(); } catch (e) {}
}
saveToStorage(shareInfo.shareCode, receiveCode, '自动保存的访问码', shareInfo.shareTitle, shareInfo.expireTime, shareInfo.fileSize, shareInfo.autoRenewal, '');
if (!__verificationCompleted) { try { autoFillPassword(receiveCode); } catch (e) {} }
ensurePasswordChecks({ onlyIfNotCompleted: true });
} catch (e) {
shareDetails.innerHTML = '解析分享信息失败: ' + e.message;
}
},
onerror: function(error) {
shareDetails.innerHTML = '获取分享信息失败: ' + (error.error || '网络错误');
}
});
}
function ensurePasswordChecks(opts = {}) {
const onlyIfNotCompleted = !!(opts && opts.onlyIfNotCompleted);
if (onlyIfNotCompleted) {
if (!__verificationCompleted) { try { checkCurrentUrlPassword(); } catch (e) {} }
if (!__verificationCompleted) { try { checkStoredPassword(); } catch (e) {} }
} else {
try { checkCurrentUrlPassword(); } catch (e) {}
try { checkStoredPassword(); } catch (e) {}
}
try { setupManualPasswordDetection(); } catch (e) {}
}
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;
if (__verificationCompleted || __verifiedOK) return;
if (confirmBtn.dataset.vpaBound === '1') return;
confirmBtn.dataset.vpaBound = '1';
try {
const err = formDecode.querySelector('.error-txt');
const hideErr = () => { if (err) { err.style.display = 'none'; err.textContent = ''; } };
hideErr();
input.addEventListener('input', () => {
if (input.value && input.value.trim()) hideErr();
});
} catch (e) {}
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);
try { applySuccessUI(password); } catch (e) {}
try { setTimeout(() => { try { fetchShareInfo(); } catch (e) {} }, 600); } catch (e) {}
try {
__verifiedOK = true;
__verificationCompleted = true;
ensureErrorObserver();
suppressAccessCodeErrors(document);
const err = formDecode.querySelector('.error-txt');
if (err) { err.style.display = 'none'; err.textContent = ''; }
const err2 = formDecode.querySelector('[data="error"]');
if (err2) { err2.style.display = 'none'; err2.textContent = ''; }
confirmBtn.classList.remove('btn-gray');
confirmBtn.classList.remove('disabled');
confirmBtn.classList.remove('z-dis');
confirmBtn.removeAttribute('disabled');
confirmBtn.setAttribute('aria-disabled', 'false');
} catch (e) {}
}
});
});
}
let __autoConfirm = { key: null, inProgress: false, done: false };
let __verifiedOK = false;
let __verificationCompleted = false;
let __errorObserverStarted = false;
let __lastShareCode = null;
let __verifiedPassword = null;
function suppressAccessCodeErrors(root = document) {
try {
const candidates = [];
const q = (sel) => { try { candidates.push(...root.querySelectorAll(sel)); } catch (e) {} };
q('.form-decode .error-txt');
q('.error-txt');
q('[data="error"]');
if (__verifiedOK) {
try {
const warns = root.querySelectorAll?.('.share-status');
warns && warns.forEach(w => {
try { if (w.classList && w.classList.contains('verified-password')) return; } catch (e) {}
try {
const t = (w.textContent || '').trim();
if (t.includes('请输入访问码') || t.includes('请输入链接访问码')) {
w.remove();
}
} catch (e) {
try {
if (!(w.classList && w.classList.contains('verified-password'))) {
w.style.display = 'none';
}
} catch (e2) {}
}
});
} catch (e) {}
}
try {
const all = root.querySelectorAll('*');
for (const el of all) {
if (!el) continue;
if (el.children && el.children.length) continue;
const txt = (el.textContent || '').trim();
if (txt === '请输入访问码' || txt === '请输入链接访问码') {
candidates.push(el);
}
}
} catch (e) {}
let n = 0;
for (const el of new Set(candidates)) {
try {
const container = el.closest ? el.closest('.share-status') : null;
if (container && container.classList && container.classList.contains('verified-password')) {
continue;
}
el.style.display = 'none'; n++;
} catch (e) {}
}
return n;
} catch (e) { return 0; }
}
function ensureErrorObserver() {
if (__errorObserverStarted) return;
__errorObserverStarted = true;
try {
const obs = new MutationObserver(() => {
if (!__verifiedOK) return;
suppressAccessCodeErrors(document);
ensureShareStatusKeepAlive();
});
const target = document.documentElement || document.body;
if (target) obs.observe(target, { childList: true, subtree: true });
suppressAccessCodeErrors(document);
ensureShareStatusKeepAlive();
} catch (e) {}
}
function applySuccessUI(password) {
try {
__verifiedPassword = password;
try {
const sd = document.querySelector('.status.status-div, .status-div');
if (sd) {
sd.innerHTML = '';
sd.classList.remove('active');
sd.style.display = 'none';
}
} catch (e) {}
let warn = document.querySelector('.share-info .share-status') || document.querySelector('.share-status');
if (warn) {
let inner = null;
try { inner = warn.querySelector('.highlight-warning'); } catch (e) { inner = null; }
if (inner) {
try { inner.classList.remove('highlight-warning'); } catch (e) {}
try { inner.classList.add('verified-password-text'); } catch (e) {}
inner.textContent = `访问密码:${password}`;
} else {
const span = document.createElement('span');
span.className = 'verified-password-text';
span.textContent = `访问密码:${password}`;
try { warn.innerHTML = ''; } catch (e) { warn.textContent = ''; }
warn.appendChild(span);
}
try { warn.classList.add('verified-password'); } catch (e) {}
try { warn.style.display = ''; } catch (e) {}
try {
const textBlock = document.querySelector('.share-info .share-text');
if (textBlock) {
let link = textBlock.querySelector('a.share-link');
if (link) {
const base = (link.getAttribute('href') || link.textContent || '').split('?')[0];
const url = `${base}?password=${encodeURIComponent(password)}`;
link.setAttribute('href', url);
link.textContent = url;
} else {
const spans = textBlock.querySelectorAll('span.highlight');
for (const s of spans) {
const t = (s.textContent || '').trim();
if (t.startsWith('https://115cdn.com/s/')) {
const base = t.split('?')[0];
s.textContent = `${base}?password=${encodeURIComponent(password)}`;
break;
}
}
}
}
} catch (e) {}
return;
}
const info = document.querySelector('.share-info');
if (info) {
const div = document.createElement('div');
div.className = 'share-status verified-password';
const span = document.createElement('span');
span.className = 'verified-password-text';
span.textContent = `访问密码:${password}`;
div.appendChild(span);
info.appendChild(div);
}
try {
const textBlock = document.querySelector('.share-info .share-text');
if (textBlock) {
let link = textBlock.querySelector('a.share-link');
if (link) {
const base = (link.getAttribute('href') || link.textContent || '').split('?')[0];
const url = `${base}?password=${encodeURIComponent(password)}`;
link.setAttribute('href', url);
link.textContent = url;
} else {
const spans = textBlock.querySelectorAll('span.highlight');
for (const s of spans) {
const t = (s.textContent || '').trim();
if (t.startsWith('https://115cdn.com/s/')) {
const base = t.split('?')[0];
s.textContent = `${base}?password=${encodeURIComponent(password)}`;
break;
}
}
}
}
} catch (e) {}
} catch (e) {}
}
function ensureShareStatusKeepAlive() {
try {
if (!__verifiedOK || !__verifiedPassword) return;
let container = document.querySelector('.share-info .share-status.verified-password') || document.querySelector('.share-status.verified-password');
const expected = `访问密码:${__verifiedPassword}`;
if (container) {
let span = null;
try { span = container.querySelector('.verified-password-text'); } catch (e) { span = null; }
if (!span) {
span = document.createElement('span');
span.className = 'verified-password-text';
span.textContent = expected;
try { container.innerHTML = ''; } catch (e) { container.textContent = ''; }
container.appendChild(span);
} else {
if (span.textContent !== expected) span.textContent = expected;
}
try { container.classList.add('verified-password'); } catch (e) {}
try { container.style.display = ''; } catch (e) {}
return;
}
applySuccessUI(__verifiedPassword);
} catch (e) {}
}
function setShareStatusTransient(text) {
try {
if (__verifiedOK) return;
let container = document.querySelector('.share-info .share-status:not(.verified-password)') || document.querySelector('.share-status:not(.verified-password)');
if (!container) {
const info = document.querySelector('.share-info');
if (info) {
container = document.createElement('div');
container.className = 'share-status transient-status';
info.appendChild(container);
}
} else {
try { container.classList.remove('verified-password'); } catch (e) {}
try { container.classList.add('transient-status'); } catch (e) {}
}
if (!container) return;
const span = container.querySelector('.verified-password-text, .transient-text') || document.createElement('span');
span.className = 'transient-text';
span.textContent = text;
if (!span.parentNode) {
try { container.innerHTML = ''; } catch (e) { container.textContent = ''; }
container.appendChild(span);
}
try { container.style.display = ''; } catch (e) {}
} catch (e) {}
}
function clearTransientShareStatus() {
try {
const container = document.querySelector('.share-info .share-status.transient-status') || document.querySelector('.share-status.transient-status');
if (container && !container.classList.contains('verified-password')) {
try { container.innerHTML = ''; } catch (e) { container.textContent = ''; }
try { container.style.display = 'none'; } catch (e) {}
try { container.classList.remove('transient-status'); } catch (e) {}
}
} catch (e) {}
}
function autoFillPassword(password) {
const enableAutoConfirm = GM_getValue('enableAutoConfirm', true);
if (!enableAutoConfirm) return;
if (__verificationCompleted) 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 {
const err = (root.querySelector?.('.form-decode .error-txt') || root.querySelector?.('.error-txt'));
if (err) { err.style.display = 'none'; err.textContent = ''; }
const err2 = (root.querySelector?.('.form-decode [data="error"]') || root.querySelector?.('[data="error"]'));
if (err2) { err2.style.display = 'none'; err2.textContent = ''; }
} catch (e) {}
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 (__verificationCompleted) { __autoConfirm.done = true; return; }
try {
const formEl = confirmBtn.closest('form') || input.closest('form');
if (formEl) formEl.dispatchEvent(new Event('submit', { bubbles: true, cancelable: true }));
} catch (e) {}
if (isUnclickable(confirmBtn)) {
doClickFallback(confirmBtn);
} else {
doClickPrimary(confirmBtn);
}
__autoConfirm.done = true;
try {
const stillForm = !!(root.querySelector?.('.form-decode'));
if (stillForm) {
setTimeout(() => { if (!__verificationCompleted) { try { doClickPrimary(confirmBtn); } catch (e) {} } }, 400);
setTimeout(() => { if (!__verificationCompleted) { try { doClickFallback(confirmBtn); } catch (e) {} } }, 800);
}
} catch (e) {}
}, 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 = `
总数: ${totalItems}项
|
成功: ${successCount}项
|
跳过: ${skippedCount}项
|
失败: ${failedCount}项
`;
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 ?
`
${formatFileSize(r.fileSize)}` : '';
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 `
${fileSizeTag}
${title}
${r.success ? '' : ''}
${shareLink}
${statusText}
`;
}).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();
if (tabName === 'dedupe') renderDedupePage();
});
});
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) {
openWindowAndActivate('batchshare');
} else {
openWindowAndActivate('storage');
}
});
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:wangzijian0@vip.qq.com';
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:wangzijian0@vip.qq.com';
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:wangzijian0@vip.qq.com';
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:wangzijian0@vip.qq.com';
proTag.textContent = savedContent;
proTagInput.value = savedContent;
updateProTagStyle();
proTag.style.display = 'inline';
proTagInput.style.display = 'none';
document.removeEventListener('click', handleClickOutside);
}
});
}
function updateStatusTag() {
try {
statusTagContainer.innerHTML = '';
statusTagContainer.style.display = 'none';
} catch (e) {}
}
function updateStatsInfo() {
const strategy = strategySelect.value;
const chars = strategy.includes('digits') ? '0123456789' : allChars;
totalAttempts = Math.pow(chars.length, 4);
statsInfo.innerHTML = `
访问码组合:
${totalAttempts.toLocaleString()} 种 (字符集: ${chars.length}个)
${strategy.includes('digits') ? '0123456789' : allChars}
`;
}
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';
try { clearTransientShareStatus(); } catch (e) {}
try {
if (!correctPassword) {
let container = document.querySelector('.share-info .share-status') || document.querySelector('.share-status');
if (!container) {
const info = document.querySelector('.share-info');
if (info) {
container = document.createElement('div');
container.className = 'share-status';
info.appendChild(container);
}
}
if (container && !container.classList.contains('verified-password')) {
try { container.classList.remove('transient-status'); } catch (e) {}
try { container.style.display = ''; } catch (e) {}
try { container.innerHTML = '
请输入访问码'; } catch (e) { container.textContent = '请输入访问码'; }
}
}
} catch (e) {}
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';
setShareStatusTransient('验证中...');
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');
setShareStatusTransient('暂停中...');
}
updateStatusTag();
}
function checkStoredPasswordBeforeStart() {
setShareStatusTransient('验证中...');
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;
try {
statusDiv.innerHTML = '';
statusDiv.classList.remove('active');
statusDiv.style.display = 'none';
} catch (e) {}
try {
__verifiedOK = true;
__verificationCompleted = true;
applySuccessUI(correctPassword);
ensureErrorObserver();
suppressAccessCodeErrors(document);
} catch (e) {}
try { clearTransientShareStatus(); } catch (e) {}
updateStatusTag();
try { autoFillPassword(correctPassword); } catch (e) {}
} else {
statusDiv.innerHTML = '
存储中的访问码已失效,开始验证...
';
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 = `
后台标签/最小化/退出浏览器 都有可能导致无法完整进行验证访问码
${navigator.onLine ? '' : '
警告: 当前网络连接不稳定
'}
当前策略: ${strategyNames[strategy]}
当前验证: ${currentPassword}
已验证数: ${currentAttempt} 次 (${triedPasswords.size}个)
并发数量: ${activeRequests} / ${maxConcurrent}
剩余组合: ${remainingCombinations.toLocaleString()}
当前进度: ${progressPercent.toFixed(6)}%
本机速度: ${attemptsPerSecond.toFixed(2)} 次/秒
剩余时间: ${remainingTime > 0 ? formatTime(remainingTime) : '计算中...'}
已用时间: ${formatTime(elapsedTime / 1000)}
`;
if (correctPassword) {
statusHTML += `
正确访问码: ${correctPassword}
`;
}
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 += '
所有组合已验证完毕,未找到正确访问码
';
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 += '
所有组合已验证完毕,未找到正确访问码
';
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();
try { autoFillPassword(correctPassword); } catch (e) {}
stopBruteForce();
return;
}
if (completed === batch.length && !correctPassword) setTimeout(tryNextBatch, 0);
});
}
updateStatus();
}
function startBruteForce() {
if (!validateCharsInput(charsInput.value)) return;
setShareStatusTransient('验证中...');
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 = `删除过期
${expiredCount}`;
} 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 = `删除无效
${cancelledCount}`;
} 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 = `删除错误
${errorCount}`;
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 = '导入数据
0';
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 = '取消导入
0';
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 = '导入数据
0';
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 = '导入数据
0';
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 = `
`;
const grid = sectionElement.querySelector('.element-block-grid');
items.forEach(item => {
const itemElement = document.createElement('div');
itemElement.className = 'element-block-item';
itemElement.innerHTML = `
${item.selector}
`;
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 = '
准备中...';
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 = `
获取中 ${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 = `
准备完成 ${totalShares} 个`;
extractBtn.style.background = '#2196f3';
extractBtn.style.color = 'white';
await new Promise(resolve => setTimeout(resolve, 800));
extractBtn.innerHTML = `
开始导入...`;
extractBtn.style.background = '#ff9800';
extractBtn.style.color = 'white';
await new Promise(resolve => setTimeout(resolve, 300));
extractBtn.innerHTML = `
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 = `
导入 ${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 = `
完成 ${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 = '
提取失败';
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', true);
const enableBatchReceiveTab = GM_getValue('enableBatchReceiveTab', true);
const enableElementBlockTab = GM_getValue('enableElementBlockTab', true);
const enableBatchRecognizeTab = GM_getValue('enableBatchRecognizeTab', true);
const enableDedupeTab = GM_getValue('enableDedupeTab', true);
settingsBlockContainer.innerHTML = `
“访问码验证”显示分享者用户ID/用户名/头像(非必要建议关闭)
导入时会通过API验证访问码有效性,ED2K、磁力链无需验证
复制115分享链接同时复制标题,格式为:标题+换行+分享链接
复制磁力链同时复制标题,格式为:标题+换行+磁力链
当分享标题包含3个以上***时,尝试从文件列表获取完整标题
`;
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');
const dedupeTabSwitch = settingsBlockContainer.querySelector('#dedupe-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);
});
dedupeTabSwitch.addEventListener('change', function() {
GM_setValue('enableDedupeTab', 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://greasyfork.org/zh-CN/scripts/543416', '_blank');
}
});
}
updateTabVisibility();
}
function updateTabVisibility() {
const enableVerifyTab = GM_getValue('enableVerifyTab', true);
const enableBatchReceiveTab = GM_getValue('enableBatchReceiveTab', true);
const enableElementBlockTab = GM_getValue('enableElementBlockTab', true);
const enableBatchRecognizeTab = GM_getValue('enableBatchRecognizeTab', true);
const enableDedupeTab = GM_getValue('enableDedupeTab', 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"]');
const dedupeTab = document.querySelector('.storage-tab[data-tab="dedupe"]');
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';
}
if (dedupeTab) {
dedupeTab.style.display = enableDedupeTab ? '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 = `
`;
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 = `
转存`;
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 = `
目录: 最近接收
|
存储: 开启
|
进度: 0/0 (0%)
|
成功: 0
|
失败: 0
`;
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 = `
`;
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 = `
目录: ${directoryText}
|
存储: ${storageText}
|
延迟: ${delayText}
|
进度: ${processedItems}/${totalItems} (${Math.round(progress)}%)
|
成功: ${successCount}
|
失败: ${failedCount}
`;
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 = `
目录: ${directoryText}
|
存储: ${storageText}
|
延迟: ${delayText}
|
进度: ${successCount + failCount}/${totalCount} (${progress}%)
|
成功: ${successCount}
|
失败: ${failCount}
`;
progressText.innerHTML = progressHtml;
} else {
progressText.innerHTML = `
${stats}`;
}
} else {
progressText.innerHTML = `
${msg}`;
}
}
}
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) ?
`
${formatFileSize(fileSize)}` : '';
let statusClass = 'error';
let statusText = r.msg || '接收失败';
if (r.success) {
statusClass = 'success';
statusText = r.msg || '接收成功';
}
return `
${fileSizeTag}
${title}
${shareLink}
${statusText}
`;
}).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 = '
准备开始批量接收...
';
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 = `
`;
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 = `
`;
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 = `
`;
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;
if (progressBar) progressBar.style.width = `${progress}%`;
const batchSizeText = batchSizeInput.value;
const concurrencyText = concurrencyInput ? concurrencyInput.value : '5';
const verifyVal = verifyMethodSelect.value;
const verifyMethodText = verifyVal === 'full' ? '智能' : verifyVal === 'quick' ? '快速' : '不验证';
const isPerfOn = performanceSwitch && performanceSwitch.checked;
const parts = [];
parts.push(`
批量大小: ${batchSizeText}`);
if (concurrencyInput) {
parts.push(`
并发数: ${concurrencyText}`);
}
parts.push(`
${verifyMethodText}`);
if (isPerfOn) {
parts.push(`
性能模式`);
}
parts.push(`
进度: ${processedItems}/${totalItems} (${Math.round(progress)}%)`);
parts.push(`
成功: ${successCount}`);
parts.push(`
失败: ${failedCount}`);
parts.push(`
跳过: ${skippedCount}`);
const html = `
${parts.join('|')}`;
if (progressText) progressText.innerHTML = html;
}
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 = `
总数: ${totalCount}项
|
成功: ${successCount}项
|
跳过: ${skippedCount}项
|
失败: ${failCount}项
`;
if (progressText) progressText.innerHTML = statusHtml;
} else {
if (progressText) progressText.innerHTML = `
${stats}`;
}
} else {
if (progressText) progressText.innerHTML = `
${msg}`;
}
}
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) {
const newTitle = response.responseData?.shareTitle || item.title || '无标题';
const fileSize = parseInt(response.responseData?.fileSize || 0);
const note = newTitle ? `[批量识别] ${newTitle}` : '[批量识别]';
saveToStorage(
item.shareCode,
item.password,
note,
newTitle,
response.responseData?.expireTime || -1,
fileSize,
response.responseData?.autoRenewal || '0'
);
return {
success: true,
title: newTitle,
shareCode: item.shareCode,
password: item.password,
msg: existedBefore ? '已更新访问码' : '验证成功',
fileSize
};
}
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 = '
未识别到有效的分享内容
';
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)
? `
${formatFileSize(r.fileSize)}`
: `
未知大小`;
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 `
${fileSizeTag}
${title}
${r.success ? '' : ''}
${shareLink}
${statusText}
`;
}).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 = '
准备开始批量识别...
';
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 = `
总数: ${totalItems2}项
|
成功: ${successCount2}项
|
跳过: ${skippedCount2}项
|
失败: ${failedCount2}项
`;
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 ? `
${formatFileSize(r.fileSize)}` : '';
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 `
${fileSizeTag}
${title}
${r.success ? '' : ''}
${shareLink}
${statusText}
`;
}).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 = `
`;
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 = `
${typeDisplayName}
${file.fileName}
${file.fileSizeDisplay || '-'}
待分享
准备中
`;
container.appendChild(fileItem);
});
}
}
};
checkFileSelection();
}, 100);
return;
}
const batchShareSettings = JSON.parse(localStorage.getItem('batchShareSettings') || '{}');
batchShareContainer.innerHTML = `
`;
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 `

`;
}
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 ?
`
${file.fileSizeDisplay}` : '';
const fileItem = document.createElement('div');
fileItem.className = 'batch-result-item compact-layout';
fileItem.setAttribute('data-index', index);
fileItem.innerHTML = `
${typeSVG}
${file.fileName}
${fileSizeDisplay}
待分享
准备中
`;
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 ?
`
${currentFileSize}` : '';
if (status === 'success') {
fileItem.className = 'batch-result-item compact-layout success';
const fullLink = `${shareLink}${extractCode ? `?password=${extractCode}` : ''}`;
titleDiv.innerHTML = `
${iconHTML}
${fileName}
`;
detailsDiv.innerHTML = `
${fileSizeDisplay}
${fullLink}
分享成功
`;
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}
${fileName}
`;
detailsDiv.innerHTML = `
${fileSizeDisplay}
分享失败
`;
actionsDiv.innerHTML = `
${info}`;
} else if (status === 'processing') {
fileItem.className = 'batch-result-item compact-layout';
titleDiv.innerHTML = `
${iconHTML}
${fileName}
`;
detailsDiv.innerHTML = `
${fileSizeDisplay}
处理中
`;
actionsDiv.innerHTML = '
分享中';
}
}
}
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 = `
进度: ${processedItems}/${totalItems} (${Math.round(progress)}%)
|
成功: ${successCount}
|
失败: ${failedCount}
`;
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 = `
总数: ${totalCount}项
|
成功: ${successCount}项
|
失败: ${failCount}项
`;
const currentProgressHtml = progressText.innerHTML;
if (currentProgressHtml && !currentProgressHtml.includes('总数:')) {
progressText.innerHTML = currentProgressHtml + '
' + statusHtml;
} else {
progressText.innerHTML = statusHtml;
}
if (statusDisplay) {
statusDisplay.style.display = 'none';
}
} else {
if (statusDisplay) {
statusDisplay.style.display = 'none';
}
}
} else {
if (statusDisplay) {
statusDisplay.innerHTML = `
${msg}`;
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 = `
${titleDiv.querySelector('span:nth-child(2)').textContent}
${titleDiv.querySelector('span:last-child').textContent}
`;
detailsDiv.innerHTML = `
${detailsDiv.querySelector('span').textContent.split(': ')[1]}
已取消
`;
actionsDiv.innerHTML = '
已取消';
}
});
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();
})();