Files
sehuatang/content.js
2026-03-18 00:27:52 +08:00

3375 lines
129 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
(function() {
if (window.magnetPluginInitialized) return;
window.magnetPluginInitialized = true;
var DEBUG_MODE = localStorage.getItem('magnetDebugMode') === 'true';
function log(msg) { if (DEBUG_MODE) console.log('[MagnetPlugin] ' + msg); }
function escapeHtml(text) {
return String(text || '')
.replace(/&/g, '&')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;');
}
function ensurePanelStyles() {
if (document.getElementById('magnet-panel-style')) {
return;
}
var style = document.createElement('style');
style.id = 'magnet-panel-style';
style.textContent = [
/* === 字体引入 === */
'@import url("https://fonts.googleapis.com/css2?family=Rajdhani:wght@500;600;700&family=Noto+Sans+SC:wght@400;500;700&display=swap");',
/* === CSS 变量 === */
':root {',
' --m-bg-deep: #0a0e14;',
' --m-bg-primary: #0f1419;',
' --m-bg-secondary: #1a1f2e;',
' --m-bg-card: rgba(26, 31, 46, 0.92);',
' --m-bg-card-hover: rgba(35, 41, 58, 0.95);',
' --m-accent: #00d4aa;',
' --m-accent-glow: rgba(0, 212, 170, 0.4);',
' --m-accent-secondary: #a78bfa;',
' --m-text-primary: #f0f4f8;',
' --m-text-secondary: #8892a4;',
' --m-text-muted: #5c6578;',
' --m-border: rgba(255, 255, 255, 0.06);',
' --m-border-accent: rgba(0, 212, 170, 0.3);',
' --m-success: #10b981;',
' --m-error: #ef4444;',
' --m-warning: #f59e0b;',
' --m-font-display: "Rajdhani", "Microsoft YaHei", sans-serif;',
' --m-font-body: "Noto Sans SC", "Microsoft YaHei", sans-serif;',
' --m-radius-sm: 8px;',
' --m-radius-md: 12px;',
' --m-radius-lg: 16px;',
' --m-radius-xl: 20px;',
' --m-shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.3);',
' --m-shadow-md: 0 8px 24px rgba(0, 0, 0, 0.4);',
' --m-shadow-lg: 0 16px 48px rgba(0, 0, 0, 0.5);',
' --m-shadow-glow: 0 0 20px var(--m-accent-glow), 0 0 40px rgba(0, 212, 170, 0.15);',
'}',
/* === 悬浮球 === */
'#magnet-float-ball{',
' position:fixed;bottom:24px;right:24px;',
' width:60px;height:60px;',
' background:linear-gradient(135deg, #0f1419 0%, #1a1f2e 100%);',
' border:2px solid var(--m-accent);',
' border-radius:50%;',
' box-shadow:var(--m-shadow-glow), inset 0 0 20px rgba(0, 212, 170, 0.1);',
' z-index:2147483647;',
' cursor:pointer;',
' display:flex;align-items:center;justify-content:center;',
' color:var(--m-accent);',
' font-size:24px;',
' font-family:var(--m-font-display);',
' font-weight:700;',
' transition:all 0.3s cubic-bezier(0.4, 0, 0.2, 1);',
' animation:magnet-pulse 2.5s ease-in-out infinite;',
'}',
'#magnet-float-ball:hover{',
' transform:scale(1.08);',
' box-shadow:0 0 30px var(--m-accent-glow), 0 0 60px rgba(0, 212, 170, 0.2), inset 0 0 30px rgba(0, 212, 170, 0.15);',
'}',
'#magnet-float-ball:active{transform:scale(0.95);}',
/* === 脉冲动画 === */
'@keyframes magnet-pulse{',
' 0%, 100%{box-shadow:var(--m-shadow-glow), inset 0 0 20px rgba(0, 212, 170, 0.1);}',
' 50%{box-shadow:0 0 30px var(--m-accent-glow), 0 0 50px rgba(0, 212, 170, 0.2), inset 0 0 25px rgba(0, 212, 170, 0.15);}',
'}',
/* === 主面板 === */
'#magnet-floating-panel{',
' position:fixed;right:20px;bottom:20px;',
' width:min(800px, calc(100vw - 40px));',
' height:min(85vh, 860px);',
' background:var(--m-bg-primary);',
' border:1px solid var(--m-border-accent);',
' border-radius:var(--m-radius-xl);',
' box-shadow:var(--m-shadow-lg), 0 0 80px rgba(0, 212, 170, 0.08);',
' z-index:2147483647;',
' font-family:var(--m-font-body);',
' font-size:13px;',
' color:var(--m-text-primary);',
' display:none;flex-direction:column;',
' overflow:hidden;',
' backdrop-filter:blur(20px);',
' animation:magnet-panel-in 0.4s cubic-bezier(0.4, 0, 0.2, 1);',
'}',
'@keyframes magnet-panel-in{',
' from{opacity:0;transform:translateY(20px) scale(0.98);}',
' to{opacity:1;transform:translateY(0) scale(1);}',
'}',
/* === 面板头部 === */
'#magnet-floating-panel .magnet-panel-header{',
' display:flex;justify-content:space-between;align-items:flex-start;',
' padding:20px 24px 16px;',
' background:linear-gradient(180deg, rgba(0, 212, 170, 0.08) 0%, transparent 100%);',
' border-bottom:1px solid var(--m-border);',
' gap:16px;',
'}',
'#magnet-floating-panel .magnet-panel-brand{',
' display:flex;flex-direction:column;gap:6px;min-width:0;',
'}',
'#magnet-floating-panel .magnet-panel-title{',
' font-family:var(--m-font-display);',
' font-size:24px;font-weight:700;',
' letter-spacing:1px;',
' background:linear-gradient(135deg, var(--m-accent) 0%, #00f5c4 50%, var(--m-accent-secondary) 100%);',
' -webkit-background-clip:text;',
' -webkit-text-fill-color:transparent;',
' background-clip:text;',
'}',
'#magnet-floating-panel .magnet-panel-subtitle{',
' font-size:12px;color:var(--m-text-secondary);line-height:1.5;',
'}',
'#magnet-floating-panel .magnet-panel-head-actions{',
' display:flex;align-items:center;gap:10px;flex-wrap:wrap;justify-content:flex-end;',
'}',
/* === 切换按钮 === */
'#magnet-floating-panel .magnet-panel-switch{',
' padding:10px 18px;',
' border:1px solid var(--m-border);',
' border-radius:var(--m-radius-lg);',
' background:var(--m-bg-card);',
' color:var(--m-text-secondary);',
' cursor:pointer;',
' font-family:var(--m-font-display);',
' font-size:13px;font-weight:600;',
' letter-spacing:0.5px;',
' transition:all 0.25s cubic-bezier(0.4, 0, 0.2, 1);',
'}',
'#magnet-floating-panel .magnet-panel-switch:hover{',
' background:var(--m-bg-card-hover);',
' border-color:var(--m-accent);',
' color:var(--m-text-primary);',
'}',
'#magnet-floating-panel .magnet-panel-switch.is-active{',
' background:linear-gradient(135deg, rgba(0, 212, 170, 0.2) 0%, rgba(0, 212, 170, 0.1) 100%);',
' border-color:var(--m-accent);',
' color:var(--m-accent);',
' box-shadow:0 0 15px rgba(0, 212, 170, 0.2);',
'}',
/* === 关闭按钮 === */
'#magnet-floating-panel .magnet-panel-close{',
' width:36px;height:36px;',
' border:1px solid var(--m-border);',
' border-radius:var(--m-radius-md);',
' background:var(--m-bg-card);',
' color:var(--m-text-secondary);',
' cursor:pointer;',
' font-size:20px;line-height:1;',
' transition:all 0.2s ease;',
'}',
'#magnet-floating-panel .magnet-panel-close:hover{',
' background:rgba(239, 68, 68, 0.15);',
' border-color:var(--m-error);',
' color:var(--m-error);',
'}',
/* === 设置区域 === */
'#magnet-settings{',
' padding:16px 20px;',
' background:var(--m-bg-secondary);',
' border-bottom:1px solid var(--m-border);',
' display:flex;flex-direction:column;gap:12px;',
'}',
'#magnet-floating-panel .magnet-control-row{',
' display:flex;gap:12px;align-items:center;flex-wrap:wrap;',
'}',
'#magnet-floating-panel .magnet-control-row > *{min-width:0;}',
/* === 输入框样式 === */
'#magnet-settings input[type="text"],',
'#magnet-settings input[type="number"],',
'#magnet-settings select{',
' padding:10px 14px;',
' background:var(--m-bg-primary);',
' border:1px solid var(--m-border);',
' border-radius:var(--m-radius-md);',
' color:var(--m-text-primary);',
' font-family:var(--m-font-body);',
' font-size:13px;',
' transition:all 0.2s ease;',
'}',
'#magnet-settings input:focus,',
'#magnet-settings select:focus{',
' outline:none;',
' border-color:var(--m-accent);',
' box-shadow:0 0 0 3px rgba(0, 212, 170, 0.15);',
'}',
'#magnet-settings input::placeholder{color:var(--m-text-muted);}',
/* === 主按钮 === */
'#magnet-settings button:not(.magnet-panel-switch){',
' padding:10px 20px;',
' background:linear-gradient(135deg, var(--m-accent) 0%, #00f5c4 100%);',
' border:none;',
' border-radius:var(--m-radius-md);',
' color:var(--m-bg-deep);',
' font-family:var(--m-font-display);',
' font-size:14px;font-weight:700;',
' letter-spacing:0.5px;',
' cursor:pointer;',
' transition:all 0.25s cubic-bezier(0.4, 0, 0.2, 1);',
'}',
'#magnet-settings button:not(.magnet-panel-switch):hover{',
' transform:translateY(-2px);',
' box-shadow:0 8px 25px rgba(0, 212, 170, 0.4);',
'}',
'#magnet-settings button:not(.magnet-panel-switch):active{',
' transform:translateY(0);',
'}',
/* === 内容区域 === */
'#magnet-floating-panel .magnet-panel-content{',
' flex:1;min-height:0;',
' padding:16px 20px;',
' background:var(--m-bg-primary);',
' overflow:hidden;',
'}',
'#magnet-floating-panel .magnet-view{',
' display:none;height:100%;flex-direction:column;gap:14px;min-height:0;',
'}',
'#magnet-floating-panel .magnet-view.is-active{display:flex;}',
/* === 视图工具栏 === */
'#magnet-floating-panel .magnet-view-toolbar{',
' display:flex;justify-content:space-between;align-items:center;',
' gap:12px;padding:14px 18px;',
' background:var(--m-bg-card);',
' border:1px solid var(--m-border);',
' border-radius:var(--m-radius-lg);',
'}',
'#magnet-floating-panel .magnet-view-toolbar-actions{',
' display:flex;align-items:center;gap:10px;flex-wrap:wrap;justify-content:flex-end;',
'}',
'#magnet-floating-panel .magnet-view-title{',
' font-family:var(--m-font-display);',
' font-size:16px;font-weight:700;',
' color:var(--m-text-primary);',
' letter-spacing:0.5px;',
'}',
'#magnet-floating-panel .magnet-view-meta{',
' font-size:12px;color:var(--m-text-secondary);',
'}',
/* === 磁力列表 === */
'#magnet-list{',
' display:flex;flex-direction:column;gap:10px;',
' min-height:0;overflow-y:auto;padding-right:6px;',
' scrollbar-width:thin;scrollbar-color:var(--m-accent) var(--m-bg-secondary);',
'}',
'#magnet-list::-webkit-scrollbar{width:6px;}',
'#magnet-list::-webkit-scrollbar-track{background:var(--m-bg-secondary);border-radius:3px;}',
'#magnet-list::-webkit-scrollbar-thumb{background:var(--m-accent);border-radius:3px;}',
/* === 列表项 === */
'.magnet-item{',
' display:flex;align-items:flex-start;gap:14px;',
' padding:14px 16px;',
' background:var(--m-bg-card);',
' border:1px solid var(--m-border);',
' border-radius:var(--m-radius-lg);',
' transition:all 0.25s cubic-bezier(0.4, 0, 0.2, 1);',
' animation:magnet-item-in 0.3s ease forwards;',
' opacity:0;',
'}',
'@keyframes magnet-item-in{',
' from{opacity:0;transform:translateX(-10px);}',
' to{opacity:1;transform:translateX(0);}',
'}',
'.magnet-item:hover{',
' background:var(--m-bg-card-hover);',
' border-color:var(--m-border-accent);',
' transform:translateX(4px);',
'}',
'.magnet-title{',
' flex:1;cursor:pointer;',
' color:var(--m-text-primary);',
' min-width:0;font-size:13px;line-height:1.6;',
' word-break:break-all;font-weight:500;',
' transition:color 0.2s ease;',
'}',
'.magnet-title:hover{color:var(--m-accent);}',
/* === 复制按钮 === */
'.magnet-copy-btn{',
' padding:8px 16px;',
' background:linear-gradient(135deg, var(--m-accent) 0%, #00f5c4 100%);',
' color:var(--m-bg-deep);',
' border:none;border-radius:var(--m-radius-md);',
' cursor:pointer;',
' font-family:var(--m-font-display);',
' font-size:12px;font-weight:700;',
' white-space:nowrap;flex-shrink:0;',
' transition:all 0.2s ease;',
'}',
'.magnet-copy-btn:hover{',
' transform:scale(1.05);',
' box-shadow:0 4px 15px rgba(0, 212, 170, 0.4);',
'}',
'.magnet-copy-btn:active{transform:scale(0.98);}',
'.magnet-download-btn{',
' padding:8px 16px;',
' background:linear-gradient(135deg, #2563eb 0%, #60a5fa 100%);',
' color:#fff;',
' border:none;border-radius:var(--m-radius-md);',
' cursor:pointer;',
' font-family:var(--m-font-display);',
' font-size:12px;font-weight:700;',
' white-space:nowrap;flex-shrink:0;',
' transition:all 0.2s ease;',
'}',
'.magnet-download-btn:hover{',
' transform:scale(1.05);',
' box-shadow:0 4px 15px rgba(37, 99, 235, 0.35);',
'}',
'.magnet-download-btn:active{transform:scale(0.98);}',
/* === 缓存面板 === */
'#magnet-cache-panel{',
' flex:1;min-height:0;overflow-y:auto;padding-right:6px;',
' scrollbar-width:thin;scrollbar-color:var(--m-accent) var(--m-bg-secondary);',
'}',
'#magnet-cache-panel::-webkit-scrollbar{width:6px;}',
'#magnet-cache-panel::-webkit-scrollbar-track{background:var(--m-bg-secondary);border-radius:3px;}',
'#magnet-cache-panel::-webkit-scrollbar-thumb{background:var(--m-accent);border-radius:3px;}',
/* === 缓存网格 === */
'.magnet-cache-grid{',
' display:grid;',
' grid-template-columns:repeat(auto-fit, minmax(160px, 1fr));',
' gap:12px;margin-bottom:16px;',
'}',
'.magnet-cache-card{',
' padding:16px;',
' background:var(--m-bg-card);',
' border:1px solid var(--m-border);',
' border-radius:var(--m-radius-lg);',
' transition:all 0.2s ease;',
'}',
'.magnet-cache-card:hover{',
' border-color:var(--m-border-accent);',
'}',
'.magnet-cache-card-label{',
' font-size:11px;color:var(--m-text-muted);margin-bottom:8px;',
' text-transform:uppercase;letter-spacing:0.5px;',
'}',
'.magnet-cache-card-value{',
' font-family:var(--m-font-display);',
' font-size:22px;font-weight:700;',
' color:var(--m-accent);',
'}',
/* === 缓存区块 === */
'.magnet-cache-section{margin-top:16px;}',
'.magnet-cache-section-title{',
' font-family:var(--m-font-display);',
' font-size:13px;font-weight:700;',
' color:var(--m-text-secondary);',
' margin-bottom:10px;letter-spacing:0.5px;',
'}',
'.magnet-cache-entry{',
' padding:12px 14px;margin-top:8px;',
' background:var(--m-bg-card);',
' border:1px solid var(--m-border);',
' border-radius:var(--m-radius-md);',
' transition:all 0.2s ease;',
'}',
'.magnet-cache-entry:hover{',
' border-color:var(--m-border-accent);',
'}',
'.magnet-cache-entry-title{',
' font-size:13px;font-weight:600;',
' color:var(--m-text-primary);',
' line-height:1.5;word-break:break-all;',
'}',
'.magnet-cache-entry-meta{',
' font-size:11px;color:var(--m-text-muted);',
' margin-top:6px;line-height:1.5;',
'}',
/* === 底部 === */
'#magnet-floating-panel .magnet-panel-footer{',
' padding:16px 20px 20px;',
' background:var(--m-bg-secondary);',
' border-top:1px solid var(--m-border);',
' display:flex;flex-direction:column;gap:12px;',
'}',
/* === 状态栏 === */
'#magnet-status{',
' padding:12px 16px;border-radius:var(--m-radius-md);',
' font-size:12px;line-height:1.6;',
' background:var(--m-bg-card);',
' border:1px solid var(--m-border);',
' color:var(--m-text-secondary);',
'}',
'#magnet-status[data-type="loading"]{',
' border-color:var(--m-warning);',
' color:var(--m-warning);',
' background:rgba(245, 158, 11, 0.1);',
'}',
'#magnet-status[data-type="error"]{',
' border-color:var(--m-error);',
' color:var(--m-error);',
' background:rgba(239, 68, 68, 0.1);',
'}',
'#magnet-status[data-type="done"]{',
' border-color:var(--m-success);',
' color:var(--m-success);',
' background:rgba(16, 185, 129, 0.1);',
'}',
/* === 一键复制按钮 === */
'#magnet-copy-all{',
' width:100%;padding:14px 20px;',
' background:linear-gradient(135deg, var(--m-accent) 0%, #00f5c4 100%);',
' color:var(--m-bg-deep);',
' border:none;border-radius:var(--m-radius-lg);',
' cursor:pointer;',
' font-family:var(--m-font-display);',
' font-size:15px;font-weight:700;',
' letter-spacing:0.5px;',
' transition:all 0.25s cubic-bezier(0.4, 0, 0.2, 1);',
'}',
'#magnet-copy-all:hover{',
' transform:translateY(-2px);',
' box-shadow:0 10px 30px rgba(0, 212, 170, 0.4);',
'}',
'#magnet-copy-all:active{transform:translateY(0);}',
/* === 调试菜单 === */
'#magnet-debug-menu{',
' background:var(--m-bg-card) !important;',
' border:1px solid var(--m-border-accent) !important;',
' border-radius:var(--m-radius-md) !important;',
' box-shadow:var(--m-shadow-lg) !important;',
'}',
'#magnet-debug-menu label{',
' color:var(--m-text-primary) !important;',
'}',
'#magnet-debug-menu input[type="checkbox"]{',
' accent-color:var(--m-accent);',
'}',
/* === 响应式 === */
'@media (max-width: 900px){',
' #magnet-floating-panel{',
' right:10px;bottom:10px;',
' width:calc(100vw - 20px);',
' height:min(88vh, 800px);',
' }',
' #magnet-floating-panel .magnet-panel-header{padding:16px 18px 14px;}',
' #magnet-settings,',
' #magnet-floating-panel .magnet-panel-content,',
' #magnet-floating-panel .magnet-panel-footer{padding-left:16px;padding-right:16px;}',
'}',
/* === 空状态 === */
'.magnet-empty-state{',
' display:flex;flex-direction:column;align-items:center;justify-content:center;',
' padding:40px 20px;text-align:center;',
' color:var(--m-text-muted);',
'}',
'.magnet-empty-state-icon{',
' font-size:48px;margin-bottom:16px;opacity:0.5;',
'}',
'.magnet-empty-state-text{',
' font-size:14px;line-height:1.6;',
'}',
/* === 进度条 === */
'.magnet-progress-container{',
' width:100%;height:6px;background:var(--m-bg-secondary);border-radius:999px;overflow:hidden;',
'}',
'.magnet-progress-bar{',
' width:0%;height:100%;background:linear-gradient(90deg, var(--m-accent), #00f5c4);transition:width 0.25s ease;',
'}',
'.magnet-progress-text{',
' display:flex;justify-content:space-between;gap:12px;font-size:11px;color:var(--m-text-muted);',
'}',
/* === 结果项操作 === */
'.magnet-item-actions{',
' display:flex;gap:6px;flex-shrink:0;',
'}',
'.magnet-favorite-btn{',
' width:36px;height:36px;flex:0 0 36px;padding:0;display:inline-flex;align-items:center;justify-content:center;line-height:1;background:transparent;border:1px solid var(--m-border);border-radius:var(--m-radius-md);cursor:pointer;font-size:16px;color:var(--m-text-muted);transition:all 0.2s ease;',
'}',
'.magnet-favorite-btn:hover{',
' border-color:rgba(239,68,68,0.5);color:#ff7b8a;background:rgba(239,68,68,0.08);',
'}',
'.magnet-favorite-btn.is-favorite{',
' color:#ff4d6d;border-color:rgba(255,77,109,0.55);background:rgba(255,77,109,0.12);box-shadow:0 0 12px rgba(255,77,109,0.18);',
'}',
/* === 收藏视图 === */
'#magnet-favorites-list{',
' flex:1;min-height:0;overflow-y:auto;padding-right:6px;',
' scrollbar-width:thin;scrollbar-color:var(--m-accent) var(--m-bg-secondary);',
'}',
'#magnet-favorites-list::-webkit-scrollbar{width:6px;}',
'#magnet-favorites-list::-webkit-scrollbar-track{background:var(--m-bg-secondary);border-radius:3px;}',
'#magnet-favorites-list::-webkit-scrollbar-thumb{background:var(--m-accent);border-radius:3px;}',
'.magnet-favorite-item{',
' display:flex;align-items:center;gap:10px;padding:12px 14px;margin-bottom:8px;background:var(--m-bg-card);border:1px solid var(--m-border);border-radius:var(--m-radius-md);',
'}',
'.magnet-favorite-item:hover{',
' border-color:var(--m-border-accent);',
'}',
'.magnet-favorite-title{',
' flex:1;min-width:0;font-size:12px;color:var(--m-text-primary);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;',
'}',
'.magnet-favorite-actions{',
' display:flex;gap:6px;flex-shrink:0;',
'}',
'.magnet-favorite-copy,.magnet-favorite-remove{',
' padding:6px 10px;border:none;border-radius:8px;cursor:pointer;font-size:11px;font-weight:700;',
'}',
'.magnet-favorite-copy{',
' background:linear-gradient(135deg, var(--m-accent), #00f5c4);color:var(--m-bg-deep);',
'}',
'.magnet-favorite-remove{',
' background:rgba(239,68,68,0.14);color:var(--m-error);',
'}',
/* === 历史记录下拉 === */
'.magnet-keyword-wrap{position:relative;}',
'.magnet-history-dropdown{',
' position:absolute;left:0;right:0;top:calc(100% + 6px);background:var(--m-bg-card);border:1px solid var(--m-border);border-radius:var(--m-radius-md);box-shadow:var(--m-shadow-lg);z-index:2147483647;overflow:hidden;',
' max-height:260px;display:flex;flex-direction:column;',
'}',
'.magnet-history-list{overflow-y:auto;max-height:214px;}',
'.magnet-history-item{',
' display:flex;align-items:center;gap:8px;padding:10px 12px;font-size:12px;color:var(--m-text-primary);cursor:pointer;transition:background 0.2s ease;',
'}',
'.magnet-history-item:hover{',
' background:var(--m-bg-secondary);',
'}',
'.magnet-history-text{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}',
'.magnet-history-delete{flex:0 0 auto;border:none;background:transparent;color:var(--m-text-muted);cursor:pointer;font-size:13px;line-height:1;padding:0 0 0 4px;font-weight:500;}',
'.magnet-history-delete:hover{color:var(--m-error);}',
'.magnet-history-clear{',
' padding:10px 12px;border-top:1px solid var(--m-border);font-size:11px;color:var(--m-error);cursor:pointer;text-align:center;position:sticky;bottom:0;background:var(--m-bg-card);',
'}',
/* === 云同步 === */
'.magnet-cloud-status{display:inline-flex;align-items:center;gap:6px;margin-left:8px;}',
'.magnet-cloud-dot{width:8px;height:8px;border-radius:50%;background:#ef4444;box-shadow:0 0 8px rgba(239,68,68,.35);flex:0 0 8px;}',
'.magnet-cloud-dot.is-ok{background:#10b981;box-shadow:0 0 8px rgba(16,185,129,.35);}',
'.magnet-cloud-label{font-size:11px;color:var(--m-text-secondary);}',
'.magnet-cloud-auth-card{padding:12px 14px;background:var(--m-bg-card);border:1px solid var(--m-border);border-radius:var(--m-radius-lg);display:flex;flex-direction:column;gap:10px;}',
'.magnet-cloud-auth-title{font-size:12px;font-weight:700;color:var(--m-text-primary);}',
'.magnet-cloud-auth-meta{font-size:11px;color:var(--m-text-secondary);line-height:1.6;}',
'.magnet-cloud-auth-row{display:flex;gap:8px;flex-wrap:wrap;}',
'.magnet-cloud-auth-row input[type="password"],.magnet-cloud-auth-row input[type="text"]{flex:1;min-width:0;}',
'.magnet-cloud-secondary-btn{padding:10px 14px;background:rgba(26,31,46,0.92);color:var(--m-text-secondary);border:1px solid var(--m-border);border-radius:12px;cursor:pointer;font-size:13px;font-weight:700;}',
'.magnet-cloud-secondary-btn:hover{border-color:var(--m-border-accent);color:var(--m-text-primary);}',
'.magnet-cloud-danger-btn{padding:10px 14px;background:rgba(239,68,68,0.14);color:var(--m-error);border:1px solid rgba(239,68,68,0.25);border-radius:12px;cursor:pointer;font-size:13px;font-weight:700;}',
'.magnet-cloud-danger-btn:hover{background:rgba(239,68,68,0.22);}',
'#magnet-settings input[type="password"]{padding:10px 14px;background:var(--m-bg-primary);border:1px solid var(--m-border);border-radius:var(--m-radius-md);color:var(--m-text-primary);font-family:var(--m-font-body);font-size:13px;transition:all 0.2s ease;}'
].join('');
document.head.appendChild(style);
}
function setPanelView(viewName) {
var resultsView = document.getElementById('magnet-results-view');
var cacheView = document.getElementById('magnet-cache-view');
var favoritesView = document.getElementById('magnet-favorites-view');
var cloudView = document.getElementById('magnet-cloud-view');
var resultsBtn = document.getElementById('magnet-view-results');
var cacheBtn = document.getElementById('magnet-view-cache');
var favoritesBtn = document.getElementById('magnet-view-favorites');
var cloudBtn = document.getElementById('magnet-view-cloud');
if (!resultsView || !cacheView || !favoritesView || !cloudView || !resultsBtn || !cacheBtn || !favoritesBtn || !cloudBtn) {
return;
}
resultsView.classList.toggle('is-active', viewName === 'results');
cacheView.classList.toggle('is-active', viewName === 'cache');
favoritesView.classList.toggle('is-active', viewName === 'favorites');
cloudView.classList.toggle('is-active', viewName === 'cloud');
resultsBtn.classList.toggle('is-active', viewName === 'results');
cacheBtn.classList.toggle('is-active', viewName === 'cache');
favoritesBtn.classList.toggle('is-active', viewName === 'favorites');
cloudBtn.classList.toggle('is-active', viewName === 'cloud');
if (viewName === 'favorites') {
renderFavoritesList();
} else if (viewName === 'cloud') {
renderCloudAuthSection();
}
}
function createFloatingPanel() {
var existing = document.getElementById('magnet-floating-panel');
if (existing) return existing;
ensurePanelStyles();
var ball = document.createElement('div');
ball.id = 'magnet-float-ball';
ball.innerHTML = '<svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2v4M12 18v4M4.93 4.93l2.83 2.83M16.24 16.24l2.83 2.83M2 12h4M18 12h4M4.93 19.07l2.83-2.83M16.24 7.76l2.83-2.83"/></svg>';
ball.oncontextmenu = function(e) {
e.preventDefault();
var menu = document.getElementById('magnet-debug-menu');
if (menu) menu.remove();
menu = document.createElement('div');
menu.id = 'magnet-debug-menu';
menu.style.cssText = 'position:fixed;bottom:90px;right:28px;background:var(--m-bg-card);border:1px solid var(--m-border-accent);border-radius:12px;box-shadow:0 16px 48px rgba(0,0,0,0.5);z-index:999999;padding:8px 0;font-size:12px;font-family:var(--m-font-body);';
menu.innerHTML = '<label style="display:block;padding:10px 16px;cursor:pointer;color:var(--m-text-primary)"><input type="checkbox" id="debug-check" style="accent-color:var(--m-accent);margin-right:8px;"' + (DEBUG_MODE ? ' checked' : '') + '> 调试模式</label>';
document.body.appendChild(menu);
menu.querySelector('input').onchange = function() {
DEBUG_MODE = this.checked;
localStorage.setItem('magnetDebugMode', DEBUG_MODE);
};
setTimeout(function() {
document.addEventListener('click', function hideMenu() {
menu.remove();
document.removeEventListener('click', hideMenu);
});
}, 100);
};
document.body.appendChild(ball);
var panel = document.createElement('div');
panel.id = 'magnet-floating-panel';
panel.innerHTML = '<div class="magnet-panel-header"><div class="magnet-panel-brand"><div class="magnet-panel-title">MAGNET LINKS</div><div class="magnet-panel-subtitle">智能抓取 · 缓存加速 · 一键复制</div></div><div class="magnet-panel-head-actions"><button id="magnet-view-results" class="magnet-panel-switch is-active">结果</button><button id="magnet-view-favorites" class="magnet-panel-switch">收藏</button><button id="magnet-view-cache" class="magnet-panel-switch">缓存</button><button id="magnet-view-cloud" class="magnet-panel-switch">云同步</button><button class="magnet-panel-close" title="关闭">×</button></div></div><div class="magnet-settings" id="magnet-settings"></div><div class="magnet-panel-content"><div id="magnet-results-view" class="magnet-view is-active"><div class="magnet-view-toolbar"><div><div class="magnet-view-title">搜索结果</div><div class="magnet-view-meta">关键词命中的磁力链接</div></div><div class="magnet-view-meta">共 <span id="magnet-count-num">0</span> 条</div></div><div class="magnet-list" id="magnet-list"></div></div><div id="magnet-favorites-view" class="magnet-view"><div class="magnet-view-toolbar"><div><div class="magnet-view-title">我的收藏</div><div class="magnet-view-meta">持久保存的磁力链接</div></div><div class="magnet-view-toolbar-actions"><button id="magnet-clear-favorites" class="magnet-panel-switch">清空收藏</button></div></div><div id="magnet-favorites-list"></div></div><div id="magnet-cache-view" class="magnet-view"><div class="magnet-view-toolbar"><div><div class="magnet-view-title">缓存总览</div><div class="magnet-view-meta">数据统计与快照管理</div></div><div class="magnet-view-toolbar-actions"><button id="magnet-refresh-cache" class="magnet-panel-switch is-active">刷新</button><button id="magnet-clear-cache-inline" class="magnet-panel-switch">清空</button></div></div><div id="magnet-cache-panel"></div></div><div id="magnet-cloud-view" class="magnet-view"><div class="magnet-view-toolbar"><div><div class="magnet-view-title">云同步账号</div><div class="magnet-view-meta">注册 / 登录 / 私有保险柜</div></div><div class="magnet-view-meta" id="magnet-cloud-view-meta">状态加载中</div></div><div id="magnet-cloud-page"></div></div></div><div class="magnet-panel-footer"><div id="magnet-status">设置参数后开始抓取</div><div class="magnet-progress-container"><div id="magnet-progress-bar" class="magnet-progress-bar"></div></div><div class="magnet-progress-text"><span id="magnet-progress-label">等待开始</span><span id="magnet-progress-percent">0%</span></div><button id="magnet-copy-all">一键复制全部</button></div>';
document.body.appendChild(panel);
var subtitle = panel.querySelector('.magnet-panel-subtitle');
if (subtitle) {
subtitle.innerHTML = '智能抓取 · 缓存加速 · 一键复制 <span class="magnet-cloud-status"><span id="magnet-cloud-sync-dot" class="magnet-cloud-dot"></span><span id="magnet-cloud-sync-text" class="magnet-cloud-label">云同步未登录</span></span>';
}
setPanelView('results');
ball.onclick = function() {
panel.style.display = 'flex';
ball.style.display = 'none';
};
var resultsSwitch = panel.querySelector('#magnet-view-results');
if (resultsSwitch) {
resultsSwitch.onclick = function() {
setPanelView('results');
};
}
var cacheSwitch = panel.querySelector('#magnet-view-cache');
if (cacheSwitch) {
cacheSwitch.onclick = function() {
setPanelView('cache');
refreshCacheOverview({ showStatus: true });
};
}
var favoritesSwitch = panel.querySelector('#magnet-view-favorites');
if (favoritesSwitch) {
favoritesSwitch.onclick = function() {
setPanelView('favorites');
};
}
var cloudSwitch = panel.querySelector('#magnet-view-cloud');
if (cloudSwitch) {
cloudSwitch.onclick = function() {
setPanelView('cloud');
};
}
var refreshCacheBtn = panel.querySelector('#magnet-refresh-cache');
if (refreshCacheBtn) {
refreshCacheBtn.onclick = function() {
refreshCacheOverview({ showStatus: true });
};
}
var clearCacheInlineBtn = panel.querySelector('#magnet-clear-cache-inline');
if (clearCacheInlineBtn) {
clearCacheInlineBtn.onclick = clearAllCacheWithConfirm;
}
var clearFavoritesBtn = panel.querySelector('#magnet-clear-favorites');
if (clearFavoritesBtn) {
clearFavoritesBtn.onclick = function() {
if (!confirm('确认清空全部收藏吗?')) {
return;
}
saveFavorites([]);
renderFavoritesList();
};
}
var closeBtn = panel.querySelector('.magnet-panel-close');
if (closeBtn) closeBtn.onclick = function() {
panel.style.display = 'none';
ball.style.display = 'flex';
};
var copyAllBtn = panel.querySelector('#magnet-copy-all');
if (copyAllBtn) {
copyAllBtn.onclick = function() {
var links = allMagnetLinks.length > 0
? allMagnetLinks.slice()
: Array.from(document.querySelectorAll('.magnet-item .magnet-copy-btn'))
.map(function(btn) { return btn.getAttribute('data-magnet'); })
.filter(function(link) { return !!link; });
if (links.length === 0) {
alert('暂无可复制的磁力链接');
return;
}
var allLinks = links.join('\n');
navigator.clipboard.writeText(allLinks)
.then(function() {
alert('已复制 ' + links.length + ' 个磁力链接!');
})
.catch(function(err) {
var errorMsg = err && err.message ? err.message : '复制失败';
alert('复制失败:' + errorMsg);
});
};
}
return panel;
}
function isThreadPage() {
return /\/thread-\d+-/i.test(window.location.href) || /[?&]tid=\d+/i.test(window.location.href);
}
function isListPage() {
if (isThreadPage()) {
return false;
}
if (document.querySelector('#threadlisttableid')) {
return true;
}
if (document.querySelector('tbody[id^="normalthread_"]') || document.querySelector('tbody[id^="stickthread_"]')) {
return true;
}
if (/\/forum-\d+(?:-\d+)?\.html/i.test(window.location.href) || /[?&]mod=forumdisplay/i.test(window.location.href) || /[?&]fid=\d+/i.test(window.location.href)) {
return true;
}
return false;
}
function getCurrentPage() {
var match = window.location.href.match(/[?&]page=(\d+)/);
if (match) return parseInt(match[1]);
match = window.location.href.match(/forum-\d+-(\d+)\.html/);
if (match) return parseInt(match[1]);
return 1;
}
function getForumOrigin(rawUrl) {
var sourceUrl = typeof rawUrl === 'string' && rawUrl ? rawUrl : window.location.href;
var match = sourceUrl.match(/^(https?:\/\/[^\/]+)\/?/i);
if (match) return match[1];
return window.location.origin || 'https://www.sehuatang.net';
}
function getForumIdFromUrl(rawUrl) {
var sourceUrl = typeof rawUrl === 'string' && rawUrl ? rawUrl : window.location.href;
var match = sourceUrl.match(/[?&]fid=(\d+)/i);
if (match) return match[1];
match = sourceUrl.match(/forum-(\d+)(?:-|\.html)/i);
if (match) return match[1];
return '2';
}
function getForumKey() {
return getForumOrigin(window.location.href) + '|fid:' + getForumIdFromUrl(window.location.href);
}
function getBaseUrl() {
return getForumOrigin(window.location.href) + '/forum-' + getForumIdFromUrl(window.location.href) + '-';
}
function getCurrentPage() {
var currentUrl = window.location.href;
var match = currentUrl.match(/forum-\d+-(\d+)\.html/i) || currentUrl.match(/[?&]page=(\d+)/i);
return match ? Math.max(1, Number(match[1]) || 1) : 1;
}
function getLastPage() {
var pageLinks = document.querySelectorAll('a[href*="forum-"][href$=".html"], .pg a, .pgt a');
var maxPage = getCurrentPage();
pageLinks.forEach(function(link) {
var href = link && link.href ? link.href : '';
var text = link && link.textContent ? link.textContent.trim() : '';
var match = href.match(/forum-\d+-(\d+)\.html/i) || href.match(/[?&]page=(\d+)/i);
var pageNum = match ? Number(match[1]) : Number(text);
if (Number.isFinite(pageNum) && pageNum > maxPage) {
maxPage = pageNum;
}
});
return Math.max(1, maxPage);
}
function normalizeThreadUrl(url) {
if (typeof url !== 'string' || !url) {
return '';
}
try {
var parsed = new URL(url, window.location.origin);
parsed.hash = '';
return parsed.href;
} catch (e) {
return url;
}
}
function getThreadKeyFromUrl(url) {
var normalizedUrl = normalizeThreadUrl(url);
if (!normalizedUrl) {
return '';
}
var match = normalizedUrl.match(/thread-(\d+)-/i) || normalizedUrl.match(/[?&]tid=(\d+)/i);
return match ? match[1] : normalizedUrl;
}
function normalizeCachedThreads(threads) {
var result = [];
var seen = Object.create(null);
(Array.isArray(threads) ? threads : []).forEach(function(thread) {
if (!thread || typeof thread !== 'object') return;
var normalizedUrl = normalizeThreadUrl(thread.url);
var normalizedTitle = typeof thread.title === 'string'
? thread.title.replace(/\s+/g, ' ').trim()
: '';
var threadKey = typeof thread.threadKey === 'string' && thread.threadKey
? thread.threadKey
: getThreadKeyFromUrl(normalizedUrl);
if (!normalizedUrl || !threadKey || seen[threadKey]) return;
seen[threadKey] = true;
result.push({
url: normalizedUrl,
title: normalizedTitle,
threadKey: threadKey
});
});
return result;
}
function normalizeMagnetList(magnets) {
var seen = Object.create(null);
var result = [];
(Array.isArray(magnets) ? magnets : []).forEach(function(magnet) {
if (typeof magnet !== 'string' || !magnet) return;
if (seen[magnet]) return;
seen[magnet] = true;
result.push(magnet);
});
return result;
}
function extractThreadsFromHtml(html) {
var parser = new DOMParser();
var doc = parser.parseFromString(html, 'text/html');
var threads = [];
var tbodies = doc.querySelectorAll('#threadlisttableid tbody[id^="normalthread_"]');
tbodies.forEach(function(tbody) {
var link = tbody.querySelector('th a[href*="thread-"]');
var title = tbody.querySelector('th a.xst') || tbody.querySelector('th .xst');
var titleText = title ? title.textContent : '';
var normalizedUrl = link && link.href ? normalizeThreadUrl(link.href) : '';
if (normalizedUrl) {
threads.push({
url: normalizedUrl,
title: titleText,
threadKey: getThreadKeyFromUrl(normalizedUrl)
});
}
});
return normalizeCachedThreads(threads);
}
function updateStatus(message, type) {
var status = document.getElementById('magnet-status');
if (status) {
status.textContent = message;
status.setAttribute('data-type', type || '');
// 默认样式
status.style.background = 'rgba(26,31,46,0.92)';
status.style.color = '#8892a4';
status.style.border = '1px solid rgba(255,255,255,0.06)';
if (type === 'loading') {
status.style.background = 'rgba(245,158,11,0.1)';
status.style.color = '#f59e0b';
status.style.borderColor = 'rgba(245,158,11,0.3)';
} else if (type === 'error') {
status.style.background = 'rgba(239,68,68,0.1)';
status.style.color = '#ef4444';
status.style.borderColor = 'rgba(239,68,68,0.3)';
} else if (type === 'done') {
status.style.background = 'rgba(16,185,129,0.1)';
status.style.color = '#10b981';
status.style.borderColor = 'rgba(16,185,129,0.3)';
}
if (type && type !== 'loading') {
scheduleStatePersist();
}
}
}
function updateCount(count) {
var countEl = document.getElementById('magnet-count-num');
if (countEl) countEl.textContent = count;
}
function updateProgress(current, total, label) {
var progressBar = document.getElementById('magnet-progress-bar');
var progressLabel = document.getElementById('magnet-progress-label');
var progressPercent = document.getElementById('magnet-progress-percent');
var percent = total > 0 ? Math.max(0, Math.min(100, Math.round(current / total * 100))) : 0;
if (progressBar) {
progressBar.style.width = percent + '%';
}
if (progressLabel) {
progressLabel.textContent = label || ('进度 ' + current + '/' + total);
}
if (progressPercent) {
progressPercent.textContent = percent + '%';
}
}
function resetProgress() {
updateProgress(0, 0, '等待开始');
}
function buildRangeProgressLabel(rangeStart, rangeEnd, threadIndex, threadTotal) {
return '页' + rangeStart + '-' + rangeEnd + '/帖子' + threadIndex + '/' + threadTotal;
}
function updateRangeThreadProgress(context, rangeStart, rangeEnd, threadIndex, threadTotal) {
var totalPages = Math.max(1, Number(context.totalPageCount) || (Number(context.normalizedEnd || 0) - Number(context.startPage || 0) + 1));
var safeRangeStart = Math.max(Number(context.startPage) || 1, Number(rangeStart) || Number(context.startPage) || 1);
var safeRangeEnd = Math.max(safeRangeStart, Number(rangeEnd) || safeRangeStart);
var completedBefore = Math.max(0, safeRangeStart - (Number(context.startPage) || 1));
var coveredPages = Math.max(1, safeRangeEnd - safeRangeStart + 1);
var fractional = threadTotal > 0 ? Math.max(0, Math.min(1, Number(threadIndex) / Number(threadTotal))) : 0;
var completedPages = completedBefore + coveredPages * fractional;
updateProgress(completedPages, totalPages, buildRangeProgressLabel(safeRangeStart, safeRangeEnd, threadIndex, threadTotal));
}
var FAVORITES_KEY = 'magnet-favorites';
var SEARCH_HISTORY_KEY = 'magnet-search-history';
var MAX_SEARCH_HISTORY = 20;
var PRIVATE_STORAGE_KEY = 'magnet-private-state-v1';
var privateStateLoaded = false;
var favoritesCache = [];
var searchHistoryCache = [];
var sessionBackupState = null;
function cloneSimpleArray(value) {
return JSON.parse(JSON.stringify(Array.isArray(value) ? value : []));
}
function loadPrivateStateFromExtensionStorage() {
return new Promise(function(resolve) {
var migratedFavorites = [];
var migratedHistory = [];
var state = null;
if (privateStateLoaded) {
resolve({ favorites: cloneSimpleArray(favoritesCache), searchHistory: cloneSimpleArray(searchHistoryCache) });
return;
}
chrome.storage.local.get(PRIVATE_STORAGE_KEY, function(result) {
state = result && result[PRIVATE_STORAGE_KEY] && typeof result[PRIVATE_STORAGE_KEY] === 'object' ? result[PRIVATE_STORAGE_KEY] : {};
favoritesCache = Array.isArray(state.favorites) ? state.favorites : [];
searchHistoryCache = Array.isArray(state.searchHistory) ? state.searchHistory : [];
if (favoritesCache.length === 0) {
try {
migratedFavorites = JSON.parse(localStorage.getItem(FAVORITES_KEY) || '[]');
favoritesCache = Array.isArray(migratedFavorites) ? migratedFavorites : [];
localStorage.removeItem(FAVORITES_KEY);
} catch (e) {
favoritesCache = [];
}
}
if (searchHistoryCache.length === 0) {
try {
migratedHistory = JSON.parse(localStorage.getItem(SEARCH_HISTORY_KEY) || '[]');
searchHistoryCache = Array.isArray(migratedHistory) ? migratedHistory : [];
localStorage.removeItem(SEARCH_HISTORY_KEY);
} catch (e) {
searchHistoryCache = [];
}
}
privateStateLoaded = true;
persistPrivateState();
resolve({ favorites: cloneSimpleArray(favoritesCache), searchHistory: cloneSimpleArray(searchHistoryCache) });
});
});
}
function persistPrivateState() {
if (!chrome.storage || !chrome.storage.local) {
return;
}
chrome.storage.local.set((function() {
var data = {};
data[PRIVATE_STORAGE_KEY] = {
favorites: favoritesCache,
searchHistory: searchHistoryCache
};
return data;
})());
}
function loadFavorites() {
return cloneSimpleArray(favoritesCache);
}
function saveFavorites(favorites, options) {
options = options || {};
favoritesCache = Array.isArray(favorites) ? favorites : [];
persistPrivateState();
if (!options.skipCloudSync) {
scheduleCloudVaultSync();
}
}
function isFavorited(link) {
return loadFavorites().some(function(item) {
return item && item.link === link;
});
}
function toggleFavorite(title, link, favoriteBtn) {
var favorites = loadFavorites();
var foundIndex = favorites.findIndex(function(item) {
return item.link === link;
});
if (foundIndex >= 0) {
favorites.splice(foundIndex, 1);
if (favoriteBtn) {
favoriteBtn.classList.remove('is-favorite');
favoriteBtn.textContent = '❤';
favoriteBtn.title = '收藏';
}
} else {
favorites.unshift({
title: title || '未命名帖子',
link: link,
addedAt: Date.now()
});
if (favoriteBtn) {
favoriteBtn.classList.add('is-favorite');
favoriteBtn.textContent = '❤';
favoriteBtn.title = '取消收藏';
}
}
saveFavorites(favorites);
var favoritesView = document.getElementById('magnet-favorites-view');
if (favoritesView && favoritesView.classList.contains('is-active')) {
renderFavoritesList();
}
}
function renderFavoritesList() {
var favoritesList = document.getElementById('magnet-favorites-list');
if (!favoritesList) return;
var favorites = loadFavorites();
favoritesList.innerHTML = '';
if (favorites.length === 0) {
favoritesList.innerHTML = '<div class="magnet-empty-state"><div class="magnet-empty-state-icon">☆</div><div class="magnet-empty-state-text">暂无收藏<br>点击结果项右侧的♡即可加入收藏</div></div>';
return;
}
favorites.forEach(function(favorite) {
var item = document.createElement('div');
item.className = 'magnet-favorite-item';
var title = document.createElement('div');
title.className = 'magnet-favorite-title';
title.textContent = favorite.title || '未命名帖子';
title.title = favorite.title || '未命名帖子';
var actions = document.createElement('div');
actions.className = 'magnet-favorite-actions';
var copyBtn = document.createElement('button');
copyBtn.className = 'magnet-favorite-copy';
copyBtn.textContent = '复制';
copyBtn.onclick = function() {
navigator.clipboard.writeText(favorite.link).then(function() {
copyBtn.textContent = '已复制';
setTimeout(function() {
copyBtn.textContent = '复制';
}, 1000);
});
};
var removeBtn = document.createElement('button');
removeBtn.className = 'magnet-favorite-remove';
removeBtn.textContent = '删除';
removeBtn.onclick = function() {
saveFavorites(loadFavorites().filter(function(itemData) {
return itemData.link !== favorite.link;
}));
renderFavoritesList();
};
actions.appendChild(copyBtn);
actions.appendChild(removeBtn);
item.appendChild(title);
item.appendChild(actions);
favoritesList.appendChild(item);
});
}
function loadSearchHistory() {
return cloneSimpleArray(searchHistoryCache);
}
function removeSearchHistoryItem(keyword) {
var value = String(keyword || '').trim();
if (!value) {
return;
}
setSearchHistoryList(loadSearchHistory().filter(function(item) {
return item !== value;
}), { skipCloudSync: false });
}
function saveSearchHistory(keyword) {
var value = String(keyword || '').trim();
if (!value) return;
var history = loadSearchHistory().filter(function(item) {
return item !== value;
});
history.unshift(value);
setSearchHistoryList(history.slice(0, MAX_SEARCH_HISTORY));
}
function removeHistoryDropdown() {
var dropdown = document.getElementById('magnet-history-dropdown');
if (dropdown) {
dropdown.remove();
}
}
function showHistoryDropdown(input) {
if (!input) return;
removeHistoryDropdown();
var history = loadSearchHistory();
var list = null;
if (history.length === 0) return;
var wrap = input.parentNode;
if (!wrap) return;
wrap.classList.add('magnet-keyword-wrap');
var dropdown = document.createElement('div');
dropdown.id = 'magnet-history-dropdown';
dropdown.className = 'magnet-history-dropdown';
list = document.createElement('div');
list.className = 'magnet-history-list';
history.forEach(function(historyKeyword) {
var item = document.createElement('div');
item.className = 'magnet-history-item';
var text = document.createElement('div');
var removeBtn = document.createElement('button');
text.className = 'magnet-history-text';
text.textContent = historyKeyword;
removeBtn.className = 'magnet-history-delete';
removeBtn.type = 'button';
removeBtn.textContent = '×';
removeBtn.title = '删除这条历史记录';
item.onclick = function() {
input.value = historyKeyword;
removeHistoryDropdown();
};
removeBtn.onclick = function(event) {
event.preventDefault();
event.stopPropagation();
removeSearchHistoryItem(historyKeyword);
showHistoryDropdown(input);
};
item.appendChild(text);
item.appendChild(removeBtn);
list.appendChild(item);
});
dropdown.appendChild(list);
var clearItem = document.createElement('div');
clearItem.className = 'magnet-history-clear';
clearItem.textContent = '清空历史记录';
clearItem.onclick = function() {
setSearchHistoryList([], { skipCloudSync: false });
removeHistoryDropdown();
};
dropdown.appendChild(clearItem);
wrap.appendChild(dropdown);
setTimeout(function() {
document.addEventListener('click', function hideHistory(event) {
if (!dropdown.contains(event.target) && event.target !== input) {
removeHistoryDropdown();
document.removeEventListener('click', hideHistory);
}
});
}, 0);
}
function playDoneTone() {
try {
var AudioContextCtor = window.AudioContext || window.webkitAudioContext;
if (!AudioContextCtor) return;
var context = new AudioContextCtor();
var oscillator = context.createOscillator();
var gainNode = context.createGain();
oscillator.connect(gainNode);
gainNode.connect(context.destination);
oscillator.type = 'sine';
oscillator.frequency.value = 880;
gainNode.gain.setValueAtTime(0.0001, context.currentTime);
gainNode.gain.exponentialRampToValueAtTime(0.18, context.currentTime + 0.01);
gainNode.gain.exponentialRampToValueAtTime(0.0001, context.currentTime + 0.35);
oscillator.start(context.currentTime);
oscillator.stop(context.currentTime + 0.35);
} catch (e) {
log('提示音播放失败: ' + e);
}
}
function showDoneNotification(title, body) {
if (!('Notification' in window)) return;
if (Notification.permission === 'granted') {
new Notification(title, { body: body });
return;
}
if (Notification.permission !== 'denied') {
Notification.requestPermission().then(function(permission) {
if (permission === 'granted') {
new Notification(title, { body: body });
}
});
}
}
var cloudSyncState = {
authenticated: false,
healthy: false,
color: 'red',
text: '云同步未登录',
email: '',
lastError: ''
};
var cloudVaultHydrated = false;
var cloudVaultSyncTimer = null;
function updateCloudSyncIndicator(status) {
var dot = document.getElementById('magnet-cloud-sync-dot');
var label = document.getElementById('magnet-cloud-sync-text');
cloudSyncState = Object.assign({}, cloudSyncState, status || {});
if (dot) {
dot.classList.toggle('is-ok', cloudSyncState.color === 'green');
}
if (label) {
label.textContent = cloudSyncState.text || (cloudSyncState.color === 'green' ? '云同步正常' : '云同步异常');
label.title = cloudSyncState.email ? (cloudSyncState.text + ' · ' + cloudSyncState.email) : (cloudSyncState.lastError || cloudSyncState.text || '云同步');
}
}
async function getCloudSyncStatus() {
try {
var response = await sendRuntimeMessage({ action: 'cloudGetSyncStatus' }, 12000);
if (response && response.ok) {
updateCloudSyncIndicator(response);
return response;
}
updateCloudSyncIndicator({ color: 'red', text: '云同步异常', lastError: response && response.error ? response.error : '状态获取失败' });
return null;
} catch (e) {
updateCloudSyncIndicator({ color: 'red', text: '云同步异常', lastError: e && e.message ? e.message : String(e) });
return null;
}
}
function getUiSettingsSnapshot() {
return {
speedMode: speedConfig[speedMode] ? speedMode : 'fast'
};
}
function applyUiSettingsSnapshot(snapshot) {
if (!snapshot || typeof snapshot !== 'object') {
return;
}
if (snapshot.speedMode && speedConfig[snapshot.speedMode]) {
speedMode = snapshot.speedMode;
var speedSelect = document.getElementById('speed-select');
if (speedSelect) {
speedSelect.value = speedMode;
}
}
}
function setSearchHistoryList(historyList, options) {
var normalized = [];
var seen = Object.create(null);
options = options || {};
(Array.isArray(historyList) ? historyList : []).forEach(function(item) {
var value = String(item || '').trim();
if (!value || seen[value]) {
return;
}
seen[value] = true;
normalized.push(value);
});
searchHistoryCache = normalized.slice(0, MAX_SEARCH_HISTORY);
persistPrivateState();
if (!options.skipCloudSync) {
scheduleCloudVaultSync();
}
}
function openCloudSyncCenter() {
sendRuntimeMessage({ action: 'openCloudSyncPage' }, 8000).catch(function(e) {
updateStatus('打开云同步中心失败:' + (e && e.message ? e.message : String(e)), 'error');
});
}
function triggerMagnetDownload(link, downloadBtn) {
var anchor = null;
var cleanup = null;
var fallbackCopy = function(message, type) {
navigator.clipboard.writeText(link).then(function() {
updateStatus(message || '未检测到本地下载器,已复制磁力链接。请在扩展页下载 qBittorrent推荐节点1/2', type || 'done');
}).catch(function() {
updateStatus('未能调用本地下载器,请手动复制磁力链接', 'error');
});
};
try {
updateStatus('正在调用本地下载器;如未安装,可到扩展页下载 qBittorrent推荐节点1/2', 'loading');
anchor = document.createElement('a');
anchor.href = link;
anchor.style.display = 'none';
anchor.rel = 'noreferrer noopener';
document.body.appendChild(anchor);
anchor.click();
if (downloadBtn) {
downloadBtn.textContent = '已发送';
cleanup = function() {
downloadBtn.textContent = '下载';
};
setTimeout(cleanup, 1200);
}
setTimeout(function() {
updateStatus('已发送到本地下载器,如未弹出请先安装并关联 qBittorrent', 'done');
}, 300);
} catch (e) {
fallbackCopy('未检测到本地下载器,已复制磁力链接。请在扩展页下载 qBittorrent推荐节点1/2', 'done');
} finally {
if (anchor && anchor.parentNode) {
anchor.parentNode.removeChild(anchor);
}
}
}
function mergeFavoritesList(localItems, remoteItems) {
var merged = [];
var seen = Object.create(null);
(Array.isArray(remoteItems) ? remoteItems : []).concat(Array.isArray(localItems) ? localItems : []).forEach(function(item) {
var link = item && typeof item.link === 'string' ? item.link : '';
if (!link || seen[link]) {
return;
}
seen[link] = true;
merged.push({
title: typeof item.title === 'string' ? item.title : '未命名帖子',
link: link,
addedAt: Number(item.addedAt) || Date.now()
});
});
return merged;
}
function mergeHistoryList(localItems, remoteItems) {
var merged = [];
var seen = Object.create(null);
(Array.isArray(remoteItems) ? remoteItems : []).concat(Array.isArray(localItems) ? localItems : []).forEach(function(item) {
var value = String(item || '').trim();
if (!value || seen[value]) {
return;
}
seen[value] = true;
merged.push(value);
});
return merged.slice(0, MAX_SEARCH_HISTORY);
}
async function pushCloudVaultItems(items) {
try {
var response = await sendRuntimeMessage({ action: 'cloudPushVaultItems', items: items }, 20000);
if (response && response.status) {
updateCloudSyncIndicator(response.status);
}
return response;
} catch (e) {
updateCloudSyncIndicator({ color: 'red', text: '云同步异常', lastError: e && e.message ? e.message : String(e) });
return null;
}
}
async function pullCloudVaultItems(itemTypes) {
try {
var response = await sendRuntimeMessage({ action: 'cloudPullVaultItems', itemTypes: itemTypes }, 20000);
if (response && response.ok) {
if (response.status) {
updateCloudSyncIndicator(response.status);
}
return response.items || [];
}
return [];
} catch (e) {
updateCloudSyncIndicator({ color: 'red', text: '云同步异常', lastError: e && e.message ? e.message : String(e) });
return [];
}
}
function scheduleCloudVaultSync() {
if (cloudVaultSyncTimer) {
clearTimeout(cloudVaultSyncTimer);
}
cloudVaultSyncTimer = setTimeout(function() {
cloudVaultSyncTimer = null;
syncAllLocalVaultData();
}, 600);
}
async function syncAllLocalVaultData() {
if (!cloudSyncState.authenticated) {
return null;
}
return pushCloudVaultItems([
{ itemType: 'favorites', itemKey: 'default', data: loadFavorites() },
{ itemType: 'search_history', itemKey: 'default', data: loadSearchHistory() },
{ itemType: 'ui_settings', itemKey: 'default', data: getUiSettingsSnapshot() }
]);
}
function applyVaultItems(items, options) {
var remoteFavorites = [];
var remoteHistory = [];
var remoteSettings = null;
var remoteProgress = null;
options = options || {};
(Array.isArray(items) ? items : []).forEach(function(item) {
if (!item || typeof item !== 'object') {
return;
}
if (item.itemType === 'favorites' && Array.isArray(item.data)) {
remoteFavorites = item.data;
} else if (item.itemType === 'search_history' && Array.isArray(item.data)) {
remoteHistory = item.data;
} else if (item.itemType === 'ui_settings' && item.data && typeof item.data === 'object') {
remoteSettings = item.data;
} else if (item.itemType === 'progress_state' && item.data && typeof item.data === 'object') {
remoteProgress = item.data;
}
});
if (remoteFavorites.length > 0) {
saveFavorites(mergeFavoritesList(loadFavorites(), remoteFavorites), { skipCloudSync: true });
}
if (remoteHistory.length > 0) {
setSearchHistoryList(mergeHistoryList(loadSearchHistory(), remoteHistory), { skipCloudSync: true });
}
if (remoteSettings) {
applyUiSettingsSnapshot(remoteSettings);
}
if (options.restoreProgress && remoteProgress && allMagnetLinks.length === 0) {
applyStateSnapshot(remoteProgress);
}
cloudVaultHydrated = true;
}
async function refreshCloudStatusAndVault(options) {
var status = await getCloudSyncStatus();
if (!status) {
renderCloudAuthSection();
return null;
}
if (status.authenticated && !cloudVaultHydrated && !options?.skipVaultPull) {
applyVaultItems(await pullCloudVaultItems(['favorites', 'search_history', 'ui_settings', 'progress_state']), { restoreProgress: !!options?.restoreProgress });
}
renderCloudAuthSection();
return status;
}
async function logoutCloudAccount() {
try {
var response = await sendRuntimeMessage({ action: 'cloudLogout' }, 15000);
cloudVaultHydrated = false;
updateCloudSyncIndicator(response && response.status ? response.status : { color: 'red', text: '云同步未登录', authenticated: false, healthy: false, email: '' });
renderCloudAuthSection();
updateStatus('已退出云同步', 'done');
} catch (e) {
updateCloudSyncIndicator({ color: 'red', text: '云同步异常', lastError: e && e.message ? e.message : String(e) });
updateStatus('退出失败:' + (e && e.message ? e.message : String(e)), 'error');
}
}
function renderCloudAuthSection() {
var settingsArea = document.getElementById('magnet-cloud-page');
var existing = document.getElementById('magnet-cloud-auth-card');
var viewMeta = document.getElementById('magnet-cloud-view-meta');
var html = '';
var card = null;
var syncNowBtn = null;
var logoutBtn = null;
var openCenterBtn = null;
if (!settingsArea) {
return;
}
if (existing) {
existing.remove();
}
card = document.createElement('div');
card.id = 'magnet-cloud-auth-card';
card.className = 'magnet-cloud-auth-card';
if (viewMeta) {
viewMeta.textContent = cloudSyncState.email ? (cloudSyncState.text + ' · ' + cloudSyncState.email) : (cloudSyncState.text || '状态未知');
}
if (cloudSyncState.authenticated) {
html = '<div class="magnet-cloud-auth-title">云同步已开启</div><div class="magnet-cloud-auth-meta">账号:' + escapeHtml(cloudSyncState.email || '已登录') + '<br>' + escapeHtml(cloudSyncState.text || '云同步正常') + '</div><div class="magnet-cloud-auth-row"><button id="magnet-cloud-sync-now" class="magnet-cloud-secondary-btn" type="button">立即同步</button><button id="magnet-cloud-open-center" class="magnet-cloud-secondary-btn" type="button">打开云同步中心</button><button id="magnet-cloud-logout" class="magnet-cloud-danger-btn" type="button">退出登录</button></div>';
} else {
html = '<div class="magnet-cloud-auth-title">云同步账号</div><div class="magnet-cloud-auth-meta">为了安全,登录/注册已移到扩展独立页面中进行。网页本身不会再承载账号密码输入框。</div><div class="magnet-cloud-auth-row"><button id="magnet-cloud-open-center" type="button">打开云同步中心</button></div>';
}
card.innerHTML = html;
settingsArea.innerHTML = '';
settingsArea.appendChild(card);
if (cloudSyncState.authenticated) {
syncNowBtn = card.querySelector('#magnet-cloud-sync-now');
logoutBtn = card.querySelector('#magnet-cloud-logout');
openCenterBtn = card.querySelector('#magnet-cloud-open-center');
if (syncNowBtn) {
syncNowBtn.onclick = function() {
updateStatus('正在同步保险柜...', 'loading');
syncAllLocalVaultData().then(function() {
updateStatus('保险柜同步完成', 'done');
}).catch(function(e) {
updateStatus('保险柜同步失败:' + (e && e.message ? e.message : String(e)), 'error');
});
};
}
if (logoutBtn) {
logoutBtn.onclick = logoutCloudAccount;
}
if (openCenterBtn) {
openCenterBtn.onclick = openCloudSyncCenter;
}
} else {
openCenterBtn = card.querySelector('#magnet-cloud-open-center');
if (openCenterBtn) {
openCenterBtn.onclick = openCloudSyncCenter;
}
}
}
function clearMagnetList(skipPersist) {
var list = document.getElementById('magnet-list');
if (list) list.innerHTML = '';
allMagnetLinks = [];
allMagnetRecords = [];
magnetRecordMap = Object.create(null);
updateCount(0);
if (!skipPersist) {
scheduleStatePersist();
}
}
function addMagnetItem(title, link, options) {
var list = document.getElementById('magnet-list');
if (!list) return;
var safeTitle = typeof title === 'string' ? title : String(title || '');
var safeLink = typeof link === 'string' ? link : String(link || '');
if (!safeLink) return;
if (magnetRecordMap[safeLink]) {
return;
}
magnetRecordMap[safeLink] = safeTitle || '恢复记录';
allMagnetRecords.push({
title: magnetRecordMap[safeLink],
link: safeLink
});
allMagnetLinks.push(safeLink);
var item = document.createElement('div');
item.className = 'magnet-item';
var titleEl = document.createElement('span');
titleEl.className = 'magnet-title';
titleEl.title = safeTitle;
titleEl.textContent = safeTitle;
var actions = document.createElement('div');
actions.className = 'magnet-item-actions';
var favoriteBtn = document.createElement('button');
favoriteBtn.className = 'magnet-favorite-btn';
favoriteBtn.textContent = '❤';
favoriteBtn.title = '收藏';
if (isFavorited(safeLink)) {
favoriteBtn.classList.add('is-favorite');
favoriteBtn.textContent = '❤';
favoriteBtn.title = '取消收藏';
}
favoriteBtn.onclick = function() {
toggleFavorite(safeTitle, safeLink, favoriteBtn);
};
var copyBtn = document.createElement('button');
copyBtn.className = 'magnet-copy-btn';
copyBtn.setAttribute('data-magnet', safeLink);
copyBtn.textContent = '复制';
var downloadBtn = document.createElement('button');
downloadBtn.className = 'magnet-download-btn';
downloadBtn.setAttribute('data-magnet', safeLink);
downloadBtn.textContent = '下载';
titleEl.onclick = function() {
navigator.clipboard.writeText(safeLink)
.then(function() {
titleEl.textContent = '已复制: ' + safeTitle.substring(0, 20) + '...';
setTimeout(function() {
titleEl.textContent = safeTitle;
}, 1500);
})
.catch(function(err) {
var errorMsg = err && err.message ? err.message : '复制失败';
log('标题复制失败: ' + errorMsg);
updateStatus('复制失败,请检查剪贴板权限', 'error');
});
};
copyBtn.onclick = function() {
navigator.clipboard.writeText(safeLink)
.then(function() {
copyBtn.textContent = '已复制';
setTimeout(function() {
copyBtn.textContent = '复制';
}, 1000);
})
.catch(function(err) {
var errorMsg = err && err.message ? err.message : '复制失败';
log('按钮复制失败: ' + errorMsg);
updateStatus('复制失败,请检查剪贴板权限', 'error');
});
};
downloadBtn.onclick = function() {
triggerMagnetDownload(safeLink, downloadBtn);
};
item.appendChild(titleEl);
actions.appendChild(favoriteBtn);
actions.appendChild(copyBtn);
actions.appendChild(downloadBtn);
item.appendChild(actions);
list.appendChild(item);
setPanelView('results');
updateCount(list.children.length);
if (!options || !options.skipPersist) {
scheduleStatePersist();
}
}
function extractMagnets() {
var magnetPattern = /magnet:\?xt=urn:btih:[a-fA-F0-9]{32,40}/gi;
var links = new Set();
function walk(node) {
if (node.nodeType === Node.TEXT_NODE) {
var matches = node.textContent.match(magnetPattern);
if (matches) matches.forEach(function(m) { links.add(m); });
} else if (node.nodeType === Node.ELEMENT_NODE) {
var tag = node.tagName.toLowerCase();
if (tag !== 'script' && tag !== 'style' && tag !== 'noscript') {
node.childNodes.forEach(walk);
}
}
}
walk(document.body);
document.querySelectorAll('a[href^="magnet:"]').forEach(function(a) { links.add(a.href); });
return Array.from(links);
}
var speedMode = 'fast';
var speedConfig = {
slow: {
thread: [300, 600],
page: [500, 900],
concurrency: 1,
fetchTimeout: 25000,
messageTimeout: 30000
},
medium: {
thread: [80, 180],
page: [150, 300],
concurrency: 2,
fetchTimeout: 18000,
messageTimeout: 22000
},
fast: {
thread: [0, 60],
page: [60, 150],
concurrency: 4,
fetchTimeout: 14000,
messageTimeout: 18000
},
ultrafast: {
thread: [0, 20],
page: [20, 80],
concurrency: 6,
fetchTimeout: 10000,
messageTimeout: 14000
}
};
function getSpeedProfile() {
return speedConfig[speedMode] || speedConfig.fast;
}
function getSpeedDelay(type) {
var profile = getSpeedProfile();
var range = profile[type] || [0, 0];
return Math.random() * (range[1] - range[0]) + range[0];
}
function getThreadConcurrency() {
return Math.max(1, getSpeedProfile().concurrency || 1);
}
function getFetchTimeout() {
return getSpeedProfile().fetchTimeout || 15000;
}
function getMessageTimeout() {
return getSpeedProfile().messageTimeout || 20000;
}
function sleep(ms) { return new Promise(function(resolve) { setTimeout(resolve, ms); }); }
function sendRuntimeMessage(payload, timeoutMs) {
return new Promise(function(resolve, reject) {
if (!chrome.runtime || !chrome.runtime.id) {
reject(new Error('扩展已失效,请刷新页面'));
return;
}
var finished = false;
var safeTimeoutMs = Number(timeoutMs);
if (!Number.isFinite(safeTimeoutMs) || safeTimeoutMs <= 0) {
safeTimeoutMs = 15000;
}
var timer = setTimeout(function() {
if (finished) return;
finished = true;
reject(new Error('请求超时,请稍后重试'));
}, safeTimeoutMs);
try {
chrome.runtime.sendMessage(payload, function(response) {
if (finished) return;
finished = true;
clearTimeout(timer);
if (chrome.runtime.lastError) {
reject(new Error(chrome.runtime.lastError.message));
return;
}
resolve(response);
});
} catch (err) {
if (finished) return;
finished = true;
clearTimeout(timer);
reject(err);
}
});
}
function buildKeywordList(keyword) {
return String(keyword || '')
.split(',')
.map(function(item) { return item.trim(); })
.filter(function(item) { return !!item; });
}
function escapeHtml(value) {
return String(value || '').replace(/[&<>"']/g, function(character) {
return {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#39;'
}[character] || character;
});
}
function filterThreadsByKeywords(threadList, keywords) {
var normalizedThreads = normalizeCachedThreads(threadList);
if (!keywords || keywords.length === 0) {
return normalizedThreads;
}
return normalizedThreads.filter(function(thread) {
return keywords.some(function(keyword) {
return thread.title.indexOf(keyword) !== -1;
});
});
}
function mergeCoverageThreads(targetMap, threads) {
normalizeCachedThreads(threads).forEach(function(thread) {
if (!targetMap[thread.threadKey]) {
targetMap[thread.threadKey] = thread;
return;
}
if (!targetMap[thread.threadKey].title && thread.title) {
targetMap[thread.threadKey].title = thread.title;
}
if (!targetMap[thread.threadKey].url && thread.url) {
targetMap[thread.threadKey].url = thread.url;
}
});
}
function coverageMapToList(targetMap) {
return Object.keys(targetMap).map(function(threadKey) {
return targetMap[threadKey];
});
}
function getSmartFrontRefreshPages(startPage, endPage) {
if (startPage !== 1) {
return 0;
}
return Math.min(20, Math.max(0, endPage - startPage + 1));
}
async function probeForumUpdateDepth(forumKey, baseUrl, startPage, endPage) {
if (startPage !== 1) {
return { refreshPages: 0, probeThreads: null, reason: 'not_from_page1' };
}
var totalRange = endPage - startPage + 1;
var maxRefresh = Math.min(20, totalRange);
var probeUrl = baseUrl + '1.html';
try {
var response = await sendRuntimeMessage({
action: 'fetchHtml',
url: probeUrl,
timeoutMs: getFetchTimeout()
}, getMessageTimeout());
if (!response || response.error || !response.html) {
log('[探针] 获取第1页失败回退到固定刷新');
return { refreshPages: maxRefresh, probeThreads: null, reason: 'probe_fetch_failed' };
}
var liveThreads = extractThreadsFromHtml(response.html);
var threadsPerPage = liveThreads.length || 25;
if (liveThreads.length === 0) {
log('[探针] 第1页未提取到帖子回退到固定刷新');
return { refreshPages: maxRefresh, probeThreads: null, reason: 'no_threads_extracted' };
}
var cachedPage = await sendRuntimeMessage({
action: 'cacheGetPageThreadKeys',
forumKey: forumKey,
page: 1
}, 5000);
if (!cachedPage || !cachedPage.ok || !cachedPage.threadKeys || cachedPage.threadKeys.length === 0) {
log('[探针] 无第1页缓存记录首次抓取');
return { refreshPages: maxRefresh, probeThreads: liveThreads, probeHtml: response.html, reason: 'no_cached_page1' };
}
var cachedKeySet = Object.create(null);
cachedPage.threadKeys.forEach(function(key) {
var pureKey = String(key || '').split('::').pop();
cachedKeySet[pureKey] = true;
});
var newCount = 0;
liveThreads.forEach(function(thread) {
var key = thread.threadKey || getThreadKeyFromUrl(thread.url);
if (key && !cachedKeySet[key]) {
newCount++;
}
});
if (newCount === 0) {
log('[探针] 第1页与缓存完全一致无需刷新');
return { refreshPages: 0, probeThreads: liveThreads, probeHtml: response.html, reason: 'no_change', newCount: 0 };
}
var estimatedShift = Math.ceil(newCount / threadsPerPage) + 1;
var refreshPages = Math.min(estimatedShift, maxRefresh);
log('[探针] 检测到 ' + newCount + ' 个新帖 (每页' + threadsPerPage + '帖),预估影响 ' + estimatedShift + ' 页,将刷新前 ' + refreshPages + ' 页');
return {
refreshPages: refreshPages,
probeThreads: liveThreads,
probeHtml: response.html,
newCount: newCount,
threadsPerPage: threadsPerPage,
reason: 'detected_' + newCount + '_new'
};
} catch (e) {
var errMsg = e && e.message ? e.message : String(e);
log('[探针] 异常: ' + errMsg + ',回退到固定刷新');
return { refreshPages: maxRefresh, probeThreads: null, reason: 'probe_error' };
}
}
async function getCachedCoveragePlan(forumKey, startPage, endPage, frontRefreshPages) {
try {
var response = await sendRuntimeMessage({
action: 'cacheGetCoveragePlan',
forumKey: forumKey,
startPage: startPage,
endPage: endPage,
frontRefreshPages: frontRefreshPages
}, 8000);
if (!response || !response.ok) {
log('读取缓存计划失败: ' + (response && response.error ? response.error : '空响应'));
return null;
}
return response;
} catch (e) {
var errorMsg = e && e.message ? e.message : String(e);
log('读取缓存计划异常: ' + errorMsg);
return null;
}
}
async function saveCoverageSnapshot(forumKey, startPage, endPage, threads, frontRefreshPages, strategy) {
var normalizedThreads = normalizeCachedThreads(threads);
if (!forumKey || normalizedThreads.length === 0) {
return;
}
try {
var response = await sendRuntimeMessage({
action: 'cacheSaveCoverage',
forumKey: forumKey,
startPage: startPage,
endPage: endPage,
frontRefreshPages: frontRefreshPages,
strategy: strategy,
crawledAt: Date.now(),
threads: normalizedThreads
}, 12000);
if (!response || !response.ok) {
log('保存范围缓存失败: ' + (response && response.error ? response.error : '空响应'));
}
} catch (e) {
var errorMsg = e && e.message ? e.message : String(e);
log('保存范围缓存异常: ' + errorMsg);
}
}
async function savePageCoverageSnapshot(forumKey, page, threads) {
var normalizedThreads = normalizeCachedThreads(threads);
try {
var response = await sendRuntimeMessage({
action: 'cacheSavePageCoverage',
forumKey: forumKey,
page: page,
crawledAt: Date.now(),
threads: normalizedThreads
}, 12000);
if (!response || !response.ok) {
log('保存页缓存失败: ' + (response && response.error ? response.error : '空响应'));
}
} catch (e) {
var errorMsg = e && e.message ? e.message : String(e);
log('保存页缓存异常: ' + errorMsg);
}
}
async function getCachedThreadMagnets(forumKey, threads) {
var normalizedThreads = normalizeCachedThreads(threads);
if (!forumKey || normalizedThreads.length === 0) {
return [];
}
try {
var response = await sendRuntimeMessage({
action: 'cacheGetThreadMagnets',
forumKey: forumKey,
threads: normalizedThreads
}, 12000);
if (!response || !response.ok || !Array.isArray(response.threads)) {
log('读取帖子磁链缓存失败: ' + (response && response.error ? response.error : '空响应'));
return [];
}
return response.threads.map(function(thread) {
return {
threadKey: typeof thread.threadKey === 'string' ? thread.threadKey : getThreadKeyFromUrl(thread.url),
url: normalizeThreadUrl(thread.url),
title: typeof thread.title === 'string' ? thread.title : '',
magnets: normalizeMagnetList(thread.magnets)
};
});
} catch (e) {
var errorMsg = e && e.message ? e.message : String(e);
log('读取帖子磁链缓存异常: ' + errorMsg);
return [];
}
}
async function saveThreadMagnetsToCache(forumKey, threads) {
var normalizedThreads = [];
(Array.isArray(threads) ? threads : []).forEach(function(thread) {
if (!thread || typeof thread !== 'object') {
return;
}
var normalizedUrl = normalizeThreadUrl(thread.url);
var threadKey = typeof thread.threadKey === 'string' && thread.threadKey
? thread.threadKey
: getThreadKeyFromUrl(normalizedUrl);
var magnets = normalizeMagnetList(thread.magnets);
if (!normalizedUrl || !threadKey || magnets.length === 0) {
return;
}
normalizedThreads.push({
threadKey: threadKey,
url: normalizedUrl,
title: typeof thread.title === 'string' ? thread.title : '',
magnets: magnets
});
});
if (!forumKey || normalizedThreads.length === 0) {
return;
}
try {
var response = await sendRuntimeMessage({
action: 'cacheSaveThreadMagnets',
forumKey: forumKey,
syncedAt: Date.now(),
threads: normalizedThreads
}, 12000);
if (!response || !response.ok) {
log('保存帖子磁链缓存失败: ' + (response && response.error ? response.error : '空响应'));
}
} catch (e) {
var errorMsg = e && e.message ? e.message : String(e);
log('保存帖子磁链缓存异常: ' + errorMsg);
}
}
function formatTimeLabel(timestamp) {
var value = Number(timestamp);
if (!value) {
return '无';
}
var date = new Date(value);
var month = String(date.getMonth() + 1).padStart(2, '0');
var day = String(date.getDate()).padStart(2, '0');
var hour = String(date.getHours()).padStart(2, '0');
var minute = String(date.getMinutes()).padStart(2, '0');
return month + '-' + day + ' ' + hour + ':' + minute;
}
function formatBytesLabel(bytes) {
var size = Number(bytes);
if (!Number.isFinite(size) || size <= 0) {
return '0 B';
}
if (size < 1024) {
return size + ' B';
}
if (size < 1024 * 1024) {
return (size / 1024).toFixed(1) + ' KB';
}
return (size / (1024 * 1024)).toFixed(2) + ' MB';
}
async function getCacheOverview(forumKey) {
try {
var response = await sendRuntimeMessage({
action: 'cacheGetOverview',
forumKey: forumKey,
limit: 12
}, 12000);
if (!response || !response.ok) {
log('读取缓存总览失败: ' + (response && response.error ? response.error : '空响应'));
return null;
}
return response;
} catch (e) {
var errorMsg = e && e.message ? e.message : String(e);
log('读取缓存总览异常: ' + errorMsg);
return null;
}
}
function renderCacheOverview(cacheOverview) {
var panel = document.getElementById('magnet-cache-panel');
if (!panel) {
return;
}
if (!cacheOverview || !cacheOverview.summary) {
panel.innerHTML = '<div class="magnet-cache-card"><div class="magnet-cache-card-label">缓存状态</div><div class="magnet-cache-card-value" style="font-size:14px">暂无缓存数据</div></div>';
return;
}
var summary = cacheOverview.summary;
var recentThreads = Array.isArray(cacheOverview.recentThreads) ? cacheOverview.recentThreads : [];
var recentCoverages = Array.isArray(cacheOverview.recentCoverages) ? cacheOverview.recentCoverages : [];
var html = '';
html += '<div class="magnet-cache-card" style="margin-bottom:12px;background:linear-gradient(135deg,#f7fbff,#eef6ff)"><div class="magnet-cache-card-label">说明</div><div class="magnet-cache-entry-meta">这里的帖子数是 <strong>唯一帖子数</strong>,同一板块重复搜索同一帖子不会重复累计;重复搜索只会新增/更新范围快照。</div></div>';
html += '<div class="magnet-cache-grid">';
html += '<div class="magnet-cache-card"><div class="magnet-cache-card-label">当前板块唯一帖子</div><div class="magnet-cache-card-value">' + summary.forumThreadCount + '</div><div class="magnet-cache-entry-meta">范围快照 ' + summary.forumCoverageCount + ' 份 · 页缓存 ' + Number(summary.forumPageCoverageCount || 0) + ' 页</div></div>';
html += '<div class="magnet-cache-card"><div class="magnet-cache-card-label">全部唯一帖子</div><div class="magnet-cache-card-value">' + summary.totalThreadCount + '</div><div class="magnet-cache-entry-meta">总快照 ' + summary.totalCoverageCount + ' 份 · 页缓存 ' + Number(summary.totalPageCoverageCount || 0) + ' 页</div></div>';
html += '<div class="magnet-cache-card"><div class="magnet-cache-card-label">磁链缓存</div><div class="magnet-cache-card-value">' + summary.recentMagnetCount + '</div><div class="magnet-cache-entry-meta">最近命中 ' + summary.recentMagnetCachedThreads + ' 帖</div></div>';
html += '<div class="magnet-cache-card"><div class="magnet-cache-card-label">存储占用</div><div class="magnet-cache-card-value" style="font-size:16px">' + formatBytesLabel(summary.storageUsage) + '</div><div class="magnet-cache-entry-meta">配额 ' + (summary.storageQuota ? formatBytesLabel(summary.storageQuota) : '未知') + '</div></div>';
html += '</div>';
html += '<div class="magnet-cache-section"><div class="magnet-cache-section-title">最近缓存帖子</div>';
if (recentThreads.length === 0) {
html += '<div class="magnet-cache-card"><div class="magnet-cache-card-label">提示</div><div class="magnet-cache-entry-meta">当前板块还没有缓存帖子</div></div>';
} else {
recentThreads.forEach(function(item) {
var title = item.title || '未命名帖子';
html += '<div class="magnet-cache-entry">';
html += '<div class="magnet-cache-entry-title">' + escapeHtml(title) + '</div>';
html += '<div class="magnet-cache-entry-meta">磁链缓存:' + Number(item.magnetCount || 0) + ' 条 · 帖子缓存:' + formatTimeLabel(item.lastSeenAt) + ' · 磁链缓存:' + formatTimeLabel(item.lastMagnetSyncAt) + '</div>';
html += '</div>';
});
}
html += '</div>';
html += '<div class="magnet-cache-section"><div class="magnet-cache-section-title">最近范围快照</div>';
if (recentCoverages.length === 0) {
html += '<div class="magnet-cache-card"><div class="magnet-cache-card-label">提示</div><div class="magnet-cache-entry-meta">暂无范围快照</div></div>';
} else {
recentCoverages.forEach(function(item) {
html += '<div class="magnet-cache-entry">';
html += '<div class="magnet-cache-entry-title">页码 ' + item.startPage + '-' + item.endPage + '</div>';
html += '<div class="magnet-cache-entry-meta">' + item.threadCount + ' 帖 · 策略 ' + item.strategy + ' · ' + formatTimeLabel(item.crawledAt) + '</div>';
html += '</div>';
});
}
html += '</div>';
panel.innerHTML = html;
}
async function refreshCacheOverview(options) {
options = options || {};
var cachePanel = document.getElementById('magnet-cache-panel');
if (cachePanel) {
cachePanel.innerHTML = '<div style="padding:8px;color:#8892a4;font-size:11px">正在读取缓存...</div>';
}
var overview = await getCacheOverview(getForumKey());
renderCacheOverview(overview);
if (options.showStatus) {
if (overview && overview.summary) {
updateStatus('已加载缓存:当前板块唯一帖子 ' + overview.summary.forumThreadCount + ' 帖,当前页缓存 ' + Number(overview.summary.forumPageCoverageCount || 0) + ' 页', 'done');
} else {
updateStatus('读取缓存失败,请稍后重试', 'error');
}
}
return overview;
}
async function clearAllCacheWithConfirm() {
if (!confirm('确认清空全部缓存吗?这不会删除当前页面已显示的结果。')) {
return;
}
if (!confirm('二次确认:真的要清空全部标题缓存、范围快照和磁链缓存吗?')) {
return;
}
updateStatus('正在清空缓存...', 'loading');
try {
var response = await sendRuntimeMessage({ action: 'cacheClearAll' }, 12000);
if (!response || !response.ok) {
updateStatus('清空缓存失败', 'error');
return;
}
await refreshCacheOverview();
updateStatus('已清空全部缓存', 'done');
} catch (e) {
var errorMsg = e && e.message ? e.message : String(e);
log('清空缓存异常: ' + errorMsg);
updateStatus('清空缓存失败:' + errorMsg, 'error');
}
}
function toggleCachePanel() {
var cacheView = document.getElementById('magnet-cache-view');
var resultsView = document.getElementById('magnet-results-view');
if (!cacheView || !resultsView) {
return;
}
var isVisible = cacheView.classList.contains('is-active');
setPanelView(isVisible ? 'results' : 'cache');
if (!isVisible) {
refreshCacheOverview({ showStatus: true });
}
}
async function applyCachedMagnetHits(pageLabel, filteredThreads, context) {
var cachedEntries = await getCachedThreadMagnets(context.forumKey, filteredThreads);
var cachedMap = Object.create(null);
var threadsToFetch = [];
var cachedThreadCount = 0;
var cachedMagnetCount = 0;
cachedEntries.forEach(function(entry) {
if (!entry || !entry.threadKey) return;
cachedMap[entry.threadKey] = entry;
});
normalizeCachedThreads(filteredThreads).forEach(function(thread) {
var threadKey = thread.threadKey || getThreadKeyFromUrl(thread.url);
var cachedEntry = threadKey ? cachedMap[threadKey] : null;
var cachedMagnets = cachedEntry ? normalizeMagnetList(cachedEntry.magnets) : [];
if (cachedMagnets.length === 0) {
if (!context.processedThreadKeys[threadKey]) {
threadsToFetch.push(thread);
}
return;
}
if (context.processedThreadKeys[threadKey]) {
return;
}
context.processedThreadKeys[threadKey] = true;
cachedThreadCount += 1;
context.totalFetched += 1;
cachedMagnets.forEach(function(magnet) {
if (context.allMagnets.has(magnet)) {
return;
}
context.allMagnets.add(magnet);
cachedMagnetCount += 1;
addMagnetItem(thread.title || cachedEntry.title || '缓存帖子', magnet);
});
});
if (cachedThreadCount > 0) {
updateStatus(
'正在搜索缓存:关键词命中 ' + filteredThreads.length + ' 帖,直接读取缓存磁链 ' + cachedThreadCount + ' 帖(' + cachedMagnetCount + ' 条)',
'loading'
);
}
return {
threadsToFetch: threadsToFetch,
cachedThreadCount: cachedThreadCount,
cachedMagnetCount: cachedMagnetCount,
pageLabel: pageLabel
};
}
var STATE_BACKUP_KEY = 'magnetPluginStateBackupV1';
var MAX_STATE_ITEMS = 2000;
var statePersistTimer = null;
var hasRestoredProgress = false;
var lastRestoredState = null;
var manualClearRequested = false;
var isFetching = false;
var stopFetching = false;
var allMagnetLinks = [];
var allMagnetRecords = [];
var magnetRecordMap = Object.create(null);
var progressRuntime = {
isRunning: false,
stoppedByUser: false,
startPage: 1,
endPage: 1,
resumeFromPage: 1,
keyword: '',
speedMode: 'fast'
};
function resetProgressRuntimeToIdle() {
var startInput = document.getElementById('page-start');
var endInput = document.getElementById('page-end');
var keywordInput = document.getElementById('keyword-input');
var currentStart = startInput ? parseInt(startInput.value, 10) || 1 : 1;
var currentEnd = endInput ? parseInt(endInput.value, 10) || currentStart : currentStart;
progressRuntime.isRunning = false;
progressRuntime.stoppedByUser = true;
progressRuntime.startPage = currentStart;
progressRuntime.endPage = Math.max(currentStart, currentEnd);
progressRuntime.resumeFromPage = currentStart;
progressRuntime.keyword = keywordInput ? keywordInput.value.trim() : '';
progressRuntime.speedMode = speedConfig[speedMode] ? speedMode : 'fast';
}
function clearSessionBackupState() {
sessionBackupState = null;
}
async function clearAllResults() {
manualClearRequested = true;
stopFetching = true;
resetProgressRuntimeToIdle();
clearMagnetList(true);
clearSessionBackupState();
try {
await sendRuntimeMessage({ action: 'clearProgressState' }, 6000);
} catch (e) {
var clearErrorMsg = e && e.message ? e.message : String(e);
log('远端清理失败: ' + clearErrorMsg);
}
if (!isFetching) {
manualClearRequested = false;
updateStatus('已手动清空结果', 'done');
scheduleStatePersist();
} else {
updateStatus('正在停止任务并清空结果...', 'loading');
}
}
function buildStateSnapshot() {
var keywordInput = document.getElementById('keyword-input');
var startInput = document.getElementById('page-start');
var endInput = document.getElementById('page-end');
var speedSelect = document.getElementById('speed-select');
var statusEl = document.getElementById('magnet-status');
var startPage = startInput ? parseInt(startInput.value, 10) || 1 : 1;
var endPage = endInput ? parseInt(endInput.value, 10) || startPage : startPage;
var resumeFromPage = Number(progressRuntime.resumeFromPage);
if (!Number.isFinite(resumeFromPage) || resumeFromPage < 1) {
resumeFromPage = startPage;
}
return {
links: allMagnetLinks.slice(-MAX_STATE_ITEMS),
items: allMagnetRecords.slice(-MAX_STATE_ITEMS),
keyword: keywordInput ? keywordInput.value.trim() : '',
speedMode: speedConfig[speedMode] ? speedMode : 'fast',
startPage: startPage,
endPage: endPage,
resumeFromPage: resumeFromPage,
isRunning: !!progressRuntime.isRunning,
stoppedByUser: !!progressRuntime.stoppedByUser,
statusText: statusEl ? statusEl.textContent || '' : '',
statusType: statusEl ? statusEl.getAttribute('data-type') || '' : '',
pageUrl: window.location.href,
updatedAt: Date.now()
};
}
function saveStateToSessionBackup(snapshot) {
sessionBackupState = snapshot;
}
function readStateFromSessionBackup() {
return sessionBackupState;
}
function applyStateSnapshot(snapshot) {
if (!snapshot || typeof snapshot !== 'object') {
return false;
}
var speedSelect = document.getElementById('speed-select');
if (speedSelect && snapshot.speedMode && speedConfig[snapshot.speedMode]) {
speedSelect.value = snapshot.speedMode;
speedMode = snapshot.speedMode;
}
var keywordInput = document.getElementById('keyword-input');
if (keywordInput && typeof snapshot.keyword === 'string') {
keywordInput.value = snapshot.keyword;
}
var startInput = document.getElementById('page-start');
if (startInput && Number.isFinite(Number(snapshot.startPage))) {
startInput.value = Math.max(1, Number(snapshot.startPage));
}
var endInput = document.getElementById('page-end');
if (endInput && Number.isFinite(Number(snapshot.endPage))) {
endInput.value = Math.max(1, Number(snapshot.endPage));
}
progressRuntime.startPage = Number.isFinite(Number(snapshot.startPage))
? Math.max(1, Number(snapshot.startPage))
: 1;
progressRuntime.endPage = Number.isFinite(Number(snapshot.endPage))
? Math.max(progressRuntime.startPage, Number(snapshot.endPage))
: progressRuntime.startPage;
progressRuntime.resumeFromPage = Number.isFinite(Number(snapshot.resumeFromPage))
? Math.max(1, Number(snapshot.resumeFromPage))
: progressRuntime.startPage;
progressRuntime.isRunning = !!snapshot.isRunning;
progressRuntime.stoppedByUser = !!snapshot.stoppedByUser;
progressRuntime.keyword = typeof snapshot.keyword === 'string' ? snapshot.keyword : '';
progressRuntime.speedMode = speedConfig[snapshot.speedMode] ? snapshot.speedMode : 'fast';
var records = Array.isArray(snapshot.items) && snapshot.items.length > 0
? snapshot.items
: (Array.isArray(snapshot.links)
? snapshot.links.map(function(link) {
return {
title: '恢复记录',
link: link
};
})
: []);
clearMagnetList(true);
records.forEach(function(record) {
if (!record || typeof record !== 'object') return;
if (typeof record.link !== 'string' || !record.link) return;
var title = typeof record.title === 'string' ? record.title : '恢复记录';
addMagnetItem(title, record.link, { skipPersist: true });
});
if (allMagnetLinks.length > 0) {
var statusText = typeof snapshot.statusText === 'string' && snapshot.statusText
? '已恢复: ' + snapshot.statusText
: '已恢复上次抓取记录,共' + allMagnetLinks.length + '条';
updateStatus(statusText, snapshot.statusType || 'done');
return true;
}
return false;
}
function saveStateNow() {
var snapshot = buildStateSnapshot();
saveStateToSessionBackup(snapshot);
sendRuntimeMessage({
action: 'saveProgressState',
state: snapshot
}, 6000).catch(function(e) {
var errorMsg = e && e.message ? e.message : String(e);
log('远端状态保存失败: ' + errorMsg);
});
}
function scheduleStatePersist() {
if (statePersistTimer) {
clearTimeout(statePersistTimer);
}
statePersistTimer = setTimeout(function() {
statePersistTimer = null;
saveStateNow();
}, 500);
}
async function restoreProgressState() {
if (hasRestoredProgress) {
return {
restored: allMagnetLinks.length > 0,
state: lastRestoredState
};
}
hasRestoredProgress = true;
var sessionState = readStateFromSessionBackup();
var sessionStateTime = sessionState && Number.isFinite(Number(sessionState.updatedAt))
? Number(sessionState.updatedAt)
: 0;
var remoteState = null;
var remoteStateTime = 0;
try {
var response = await sendRuntimeMessage({ action: 'loadProgressState' }, 6000);
if (response && response.ok && response.state) {
remoteState = response.state;
remoteStateTime = Number.isFinite(Number(response.state.updatedAt))
? Number(response.state.updatedAt)
: 0;
}
} catch (e) {
var errorMsg = e && e.message ? e.message : String(e);
log('远端状态恢复失败: ' + errorMsg);
}
var chosenState = null;
if (sessionState) {
chosenState = sessionState;
}
if (remoteState && (!chosenState || remoteStateTime >= sessionStateTime)) {
chosenState = remoteState;
}
lastRestoredState = chosenState;
var restored = chosenState ? applyStateSnapshot(chosenState) : false;
return {
restored: restored,
state: chosenState
};
}
window.addEventListener('pagehide', function() {
if (statePersistTimer) {
clearTimeout(statePersistTimer);
statePersistTimer = null;
}
saveStateNow();
});
async function fetchThreadsInParallel(page, filteredThreads, allMagnets, processedThreadKeys, options) {
if (!filteredThreads || filteredThreads.length === 0) {
return 0;
}
options = options || {};
var concurrency = Math.min(getThreadConcurrency(), filteredThreads.length);
var cursor = 0;
var fetchedCount = 0;
async function worker() {
while (!stopFetching) {
var currentIndex = cursor;
cursor += 1;
if (currentIndex >= filteredThreads.length) {
return;
}
var threadItem = filteredThreads[currentIndex];
var threadUrl = threadItem.url;
var threadTitle = threadItem.title || '未命名帖子';
var threadKey = threadItem.threadKey || getThreadKeyFromUrl(threadUrl);
if (processedThreadKeys && threadKey) {
if (processedThreadKeys[threadKey]) {
continue;
}
processedThreadKeys[threadKey] = true;
}
if (!options.suppressStatusProgress) {
updateStatus('页' + page + '/帖子' + (currentIndex + 1) + '/' + filteredThreads.length, 'loading');
}
if (options.onProgress) {
options.onProgress(currentIndex + 1, filteredThreads.length);
}
try {
var threadResponse = await sendRuntimeMessage({
action: 'openAndFetch',
url: threadUrl,
timeoutMs: getFetchTimeout()
}, getMessageTimeout());
if (threadResponse && threadResponse.error) {
log('帖子请求失败: ' + threadUrl + ' - ' + threadResponse.error);
} else if (threadResponse && threadResponse.magnets) {
var threadMagnets = normalizeMagnetList(threadResponse.magnets);
if (threadMagnets.length > 0 && options.forumKey) {
try {
await saveThreadMagnetsToCache(options.forumKey, [{
threadKey: threadKey,
url: threadUrl,
title: threadTitle,
magnets: threadMagnets
}]);
} catch (saveErr) {
log('实时保存线程缓存失败: ' + (saveErr && saveErr.message ? saveErr.message : String(saveErr)));
}
}
threadMagnets.forEach(function(m) {
if (!allMagnets.has(m)) {
allMagnets.add(m);
addMagnetItem(threadTitle, m);
}
});
}
} catch (e) {
var threadErrorMsg = e && e.message ? e.message : String(e);
log('获取帖子失败: ' + threadUrl + ' - ' + threadErrorMsg);
}
fetchedCount += 1;
var delay = getSpeedDelay('thread');
if (delay > 0) {
await sleep(delay);
}
}
}
var workers = [];
for (var workerIndex = 0; workerIndex < concurrency; workerIndex++) {
workers.push(worker());
}
await Promise.all(workers);
return fetchedCount;
}
async function processCachedThreadBatch(pageLabel, threads, context, statusText) {
var normalizedThreads = normalizeCachedThreads(threads);
var rangeText = typeof pageLabel === 'string' ? pageLabel : String(pageLabel || '');
var rangeMatch = rangeText.match(/^(\d+)(?:-(\d+))?$/);
var rangeStart = rangeMatch ? Number(rangeMatch[1]) : Number(context.startPage || 1);
var rangeEnd = rangeMatch ? Math.max(rangeStart, Number(rangeMatch[2] || rangeMatch[1])) : rangeStart;
if (normalizedThreads.length === 0) {
return;
}
if (statusText) {
updateStatus(statusText, 'loading');
}
mergeCoverageThreads(context.coverageThreadMap, normalizedThreads);
var filteredThreads = filterThreadsByKeywords(normalizedThreads, context.keywords);
context.matchedThreads += filteredThreads.length;
if (filteredThreads.length === 0) {
updateStatus('正在搜索缓存:已检查缓存标题 ' + normalizedThreads.length + ' 帖,当前关键词未命中', 'loading');
return;
}
var cacheResult = await applyCachedMagnetHits(pageLabel, filteredThreads, context);
if (cacheResult.threadsToFetch.length === 0) {
updateStatus('正在搜索缓存:关键词命中 ' + filteredThreads.length + ' 帖,结果已全部由缓存秒出', 'loading');
return;
}
updateStatus(
'正在搜索缓存:关键词命中 ' + filteredThreads.length + ' 帖,缓存磁链命中 ' + cacheResult.cachedThreadCount + ' 帖,补抓 ' + cacheResult.threadsToFetch.length + ' 帖',
'loading'
);
context.totalFetched += await fetchThreadsInParallel(
pageLabel,
cacheResult.threadsToFetch,
context.allMagnets,
context.processedThreadKeys,
{
forumKey: context.forumKey,
suppressStatusProgress: true,
onProgress: function(threadIndex, threadTotal) {
updateRangeThreadProgress(context, rangeStart, rangeEnd, threadIndex, threadTotal);
}
}
);
}
async function fetchLivePageRange(startPage, endPage, context) {
if (startPage > endPage) {
return;
}
for (var page = startPage; page <= endPage; page++) {
if (stopFetching) break;
progressRuntime.resumeFromPage = page;
scheduleStatePersist();
if (!chrome.runtime || !chrome.runtime.id) {
updateStatus('扩展已失效,请刷新页面', 'error');
break;
}
updateStatus('第' + page + '/' + context.normalizedEnd + '页...', 'loading');
updateProgress(page - context.startPage + 1, context.normalizedEnd - context.startPage + 1, '第' + page + '/' + context.normalizedEnd + '页');
var pageUrl = context.baseUrl + page + '.html';
try {
var response = await sendRuntimeMessage({
action: 'fetchHtml',
url: pageUrl,
timeoutMs: getFetchTimeout()
}, getMessageTimeout());
if (!response || response.error) {
context.failedPages += 1;
progressRuntime.resumeFromPage = page + 1;
scheduleStatePersist();
log('获取第 ' + page + ' 页失败: ' + (response && response.error ? response.error : '空响应'));
continue;
}
if (!response.html) {
progressRuntime.resumeFromPage = page + 1;
scheduleStatePersist();
continue;
}
var threadList = extractThreadsFromHtml(response.html);
mergeCoverageThreads(context.coverageThreadMap, threadList);
await savePageCoverageSnapshot(context.forumKey, page, threadList);
var filteredThreads = filterThreadsByKeywords(threadList, context.keywords);
context.matchedThreads += filteredThreads.length;
var cacheResult = await applyCachedMagnetHits(page, filteredThreads, context);
if (cacheResult.threadsToFetch.length > 0) {
if (cacheResult.cachedThreadCount > 0) {
updateStatus(
'第' + page + '/' + context.normalizedEnd + '页:缓存磁链命中 ' + cacheResult.cachedThreadCount + ' 帖,补抓 ' + cacheResult.threadsToFetch.length + ' 帖',
'loading'
);
}
context.totalFetched += await fetchThreadsInParallel(
page,
cacheResult.threadsToFetch,
context.allMagnets,
context.processedThreadKeys,
{ forumKey: context.forumKey }
);
}
progressRuntime.resumeFromPage = page + 1;
scheduleStatePersist();
} catch (e) {
context.failedPages += 1;
progressRuntime.resumeFromPage = page + 1;
scheduleStatePersist();
var pageErrorMsg = e && e.message ? e.message : String(e);
log('获取第 ' + page + ' 页异常: ' + pageErrorMsg);
}
if (stopFetching) break;
var pageDelay = getSpeedDelay('page');
if (pageDelay > 0) {
await sleep(pageDelay);
}
}
}
async function fetchFromPage(startPage, endPage, options) {
if (isFetching) return;
options = options || {};
var preserveExisting = !!options.preserveExisting;
var earlyStart = Math.max(1, parseInt(startPage, 10) || 1);
var earlyEnd = Math.max(earlyStart, parseInt(endPage, 10) || earlyStart);
isFetching = true;
stopFetching = false;
progressRuntime.isRunning = true;
progressRuntime.stoppedByUser = false;
progressRuntime.startPage = earlyStart;
progressRuntime.endPage = earlyEnd;
progressRuntime.resumeFromPage = earlyStart;
resetProgress();
var panel = createFloatingPanel();
var ball = document.getElementById('magnet-float-ball');
var startTime = Date.now();
panel.style.display = 'flex';
if (ball) ball.style.display = 'none';
if (!preserveExisting) {
clearMagnetList(true);
}
try {
var normalizedStart = Math.max(1, parseInt(startPage, 10) || 1);
var normalizedEnd = Math.max(normalizedStart, parseInt(endPage, 10) || normalizedStart);
var baseUrl = getBaseUrl();
var keywordInput = document.getElementById('keyword-input');
var speedSelect = document.getElementById('speed-select');
var keyword = typeof options.keyword === 'string'
? options.keyword.trim()
: (keywordInput ? keywordInput.value.trim() : '');
var selectedSpeed = typeof options.speedMode === 'string'
? options.speedMode
: (speedSelect ? speedSelect.value : 'fast');
speedMode = speedConfig[selectedSpeed] ? selectedSpeed : 'fast';
if (speedSelect) {
speedSelect.value = speedMode;
}
if (keywordInput) {
keywordInput.value = keyword;
}
progressRuntime.isRunning = true;
progressRuntime.stoppedByUser = false;
progressRuntime.startPage = normalizedStart;
progressRuntime.endPage = normalizedEnd;
progressRuntime.resumeFromPage = normalizedStart;
progressRuntime.keyword = keyword;
progressRuntime.speedMode = speedMode;
if (!baseUrl) {
updateStatus('无法获取页面URL', 'error');
progressRuntime.isRunning = false;
scheduleStatePersist();
return;
}
log('开始获取第 ' + normalizedStart + ' 到 ' + normalizedEnd + ' 页, 关键词: ' + keyword + ', 速度:' + speedMode + ', 并发:' + getThreadConcurrency());
if (preserveExisting && allMagnetLinks.length > 0) {
updateStatus('检测到未完成任务,已恢复' + allMagnetLinks.length + '条,继续抓取...', 'loading');
} else {
updateStatus('开始获取...', 'loading');
}
scheduleStatePersist();
var forumKey = getForumKey();
var searchContext = {
forumKey: forumKey,
baseUrl: baseUrl,
startPage: normalizedStart,
normalizedEnd: normalizedEnd,
totalPageCount: normalizedEnd - normalizedStart + 1,
keywords: buildKeywordList(keyword),
coverageThreadMap: Object.create(null),
processedThreadKeys: Object.create(null),
allMagnets: new Set(allMagnetLinks),
matchedThreads: 0,
totalFetched: 0,
failedPages: 0
};
var frontRefreshPages = getSmartFrontRefreshPages(normalizedStart, normalizedEnd);
updateStatus('正在规划缓存:检查本地缓存 / 云端规划 / 复用块...', 'loading');
var cachePlan = await getCachedCoveragePlan(forumKey, normalizedStart, normalizedEnd, frontRefreshPages);
var cacheStrategy = 'full_live';
var probeResult = null;
if (cachePlan && cachePlan.exactCoverage && Array.isArray(cachePlan.exactCoverage.threads) && cachePlan.exactCoverage.threads.length > 0) {
cacheStrategy = 'exact_cache';
await processCachedThreadBatch('缓存', cachePlan.exactCoverage.threads, searchContext, '命中当前板块缓存,直接搜索...');
progressRuntime.resumeFromPage = normalizedEnd + 1;
scheduleStatePersist();
if (normalizedStart === 1) {
updateStatus('智能探测:检测论坛是否有更新...', 'loading');
probeResult = await probeForumUpdateDepth(forumKey, baseUrl, normalizedStart, normalizedEnd);
if (probeResult.refreshPages > 0) {
frontRefreshPages = probeResult.refreshPages;
log('[智能更新] 缓存命中,探针检测到' + (probeResult.newCount || 0) + '个新帖,刷新前' + frontRefreshPages + '页');
updateStatus('发现' + (probeResult.newCount || 0) + '个新帖,刷新前' + frontRefreshPages + '页...', 'loading');
if (probeResult.probeThreads) {
mergeCoverageThreads(searchContext.coverageThreadMap, probeResult.probeThreads);
await savePageCoverageSnapshot(forumKey, 1, probeResult.probeThreads);
var probeFiltered = filterThreadsByKeywords(probeResult.probeThreads, searchContext.keywords);
searchContext.matchedThreads += probeFiltered.length;
var probeCacheResult = await applyCachedMagnetHits(1, probeFiltered, searchContext);
if (probeCacheResult.threadsToFetch.length > 0) {
searchContext.totalFetched += await fetchThreadsInParallel(
1, probeCacheResult.threadsToFetch, searchContext.allMagnets,
searchContext.processedThreadKeys, { forumKey: forumKey }
);
}
}
var exactRefreshEnd = Math.min(normalizedEnd, normalizedStart + frontRefreshPages - 1);
if (exactRefreshEnd >= 2) {
await fetchLivePageRange(2, exactRefreshEnd, searchContext);
}
if (!stopFetching) {
await saveCoverageSnapshot(
forumKey, normalizedStart, normalizedEnd,
coverageMapToList(searchContext.coverageThreadMap),
frontRefreshPages, 'exact_cache_refreshed'
);
}
} else {
log('[智能更新] 缓存命中,探针未检测到变化,补充处理探针数据以填补缓存缺口');
if (probeResult.probeThreads && probeResult.probeThreads.length > 0) {
mergeCoverageThreads(searchContext.coverageThreadMap, probeResult.probeThreads);
await savePageCoverageSnapshot(forumKey, 1, probeResult.probeThreads);
var gapFiltered = filterThreadsByKeywords(probeResult.probeThreads, searchContext.keywords);
if (gapFiltered.length > 0) {
var gapCacheResult = await applyCachedMagnetHits(1, gapFiltered, searchContext);
if (gapCacheResult.threadsToFetch.length > 0) {
log('[智能更新] 探针填补缺口:发现' + gapCacheResult.threadsToFetch.length + '个缓存未覆盖的帖子,补抓中...');
searchContext.totalFetched += await fetchThreadsInParallel(
1, gapCacheResult.threadsToFetch, searchContext.allMagnets,
searchContext.processedThreadKeys, { forumKey: forumKey }
);
}
}
}
}
}
} else {
if (normalizedStart === 1) {
updateStatus('智能探测:检测论坛更新情况...', 'loading');
probeResult = await probeForumUpdateDepth(forumKey, baseUrl, normalizedStart, normalizedEnd);
frontRefreshPages = probeResult.refreshPages;
if (probeResult.reason === 'no_change') {
log('[智能更新] 探针未检测到变化,补充处理探针数据以填补缓存缺口');
if (probeResult.probeThreads && probeResult.probeThreads.length > 0) {
mergeCoverageThreads(searchContext.coverageThreadMap, probeResult.probeThreads);
await savePageCoverageSnapshot(forumKey, 1, probeResult.probeThreads);
var gapFiltered3 = filterThreadsByKeywords(probeResult.probeThreads, searchContext.keywords);
if (gapFiltered3.length > 0) {
var gapCacheResult3 = await applyCachedMagnetHits(1, gapFiltered3, searchContext);
if (gapCacheResult3.threadsToFetch.length > 0) {
log('[智能更新] 探针填补缺口:发现' + gapCacheResult3.threadsToFetch.length + '个缓存未覆盖的帖子,补抓中...');
searchContext.totalFetched += await fetchThreadsInParallel(
1, gapCacheResult3.threadsToFetch, searchContext.allMagnets,
searchContext.processedThreadKeys, { forumKey: forumKey }
);
}
}
}
} else if (frontRefreshPages > 0) {
log('[智能更新] 探针检测到更新,需刷新前' + frontRefreshPages + '页');
}
}
var refreshedFrontEnd = normalizedStart - 1;
var shiftedReuseEnd = normalizedStart - 1;
var cachedBlocks = cachePlan && Array.isArray(cachePlan.cachedBlocks) ? cachePlan.cachedBlocks.slice() : [];
var liveCursor = normalizedStart;
if (frontRefreshPages > 0) {
refreshedFrontEnd = Math.min(normalizedEnd, normalizedStart + frontRefreshPages - 1);
if (probeResult && probeResult.probeThreads) {
mergeCoverageThreads(searchContext.coverageThreadMap, probeResult.probeThreads);
await savePageCoverageSnapshot(forumKey, 1, probeResult.probeThreads);
var probeFiltered2 = filterThreadsByKeywords(probeResult.probeThreads, searchContext.keywords);
searchContext.matchedThreads += probeFiltered2.length;
var probeCacheResult2 = await applyCachedMagnetHits(1, probeFiltered2, searchContext);
if (probeCacheResult2.threadsToFetch.length > 0) {
searchContext.totalFetched += await fetchThreadsInParallel(
1, probeCacheResult2.threadsToFetch, searchContext.allMagnets,
searchContext.processedThreadKeys, { forumKey: forumKey }
);
}
if (refreshedFrontEnd >= 2) {
updateStatus('智能增量刷新第2-' + refreshedFrontEnd + '页 (发现' + (probeResult.newCount || 0) + '个新帖)...', 'loading');
await fetchLivePageRange(2, refreshedFrontEnd, searchContext);
}
} else {
updateStatus('智能增量:刷新前' + frontRefreshPages + '页...', 'loading');
await fetchLivePageRange(normalizedStart, refreshedFrontEnd, searchContext);
}
liveCursor = refreshedFrontEnd + 1;
}
cachedBlocks = cachedBlocks.filter(function(block) {
return block && Array.isArray(block.threads) && block.threads.length > 0 && Number(block.endPage || 0) >= liveCursor;
}).sort(function(a, b) {
return Number(a.startPage || 0) - Number(b.startPage || 0);
});
if (!stopFetching && cachedBlocks.length > 0) {
cacheStrategy = 'assembled_pages';
for (var blockIndex = 0; blockIndex < cachedBlocks.length; blockIndex++) {
var cachedBlock = cachedBlocks[blockIndex];
var blockStart = Math.max(liveCursor, Number(cachedBlock.startPage) || liveCursor);
var blockEnd = Math.min(normalizedEnd, Number(cachedBlock.endPage) || blockStart);
if (blockStart > blockEnd) {
continue;
}
if (liveCursor < blockStart) {
updateStatus('缓存复用:补抓缺口页 ' + liveCursor + '-' + (blockStart - 1), 'loading');
await fetchLivePageRange(liveCursor, blockStart - 1, searchContext);
}
await processCachedThreadBatch(
String(blockStart) + '-' + String(blockEnd),
cachedBlock.threads,
searchContext,
'缓存复用:直接使用页缓存 ' + blockStart + '-' + blockEnd + ' 页'
);
shiftedReuseEnd = Math.max(shiftedReuseEnd, blockEnd);
liveCursor = blockEnd + 1;
progressRuntime.resumeFromPage = liveCursor;
scheduleStatePersist();
}
} else if (!stopFetching && cachePlan && cachePlan.shiftedCoverage && Array.isArray(cachePlan.shiftedCoverage.threads) && cachePlan.shiftedCoverage.threads.length > 0) {
cacheStrategy = 'smart_incremental';
shiftedReuseEnd = Math.max(shiftedReuseEnd, Math.min(normalizedEnd, Number(cachePlan.shiftedCoverage.reusedEndPage) || refreshedFrontEnd));
await processCachedThreadBatch(
String(cachePlan.shiftedCoverage.reusedStartPage) + '-' + String(shiftedReuseEnd),
cachePlan.shiftedCoverage.threads,
searchContext,
'智能增量:复用当前板块历史缓存 ' + cachePlan.shiftedCoverage.reusedStartPage + '-' + shiftedReuseEnd + ' 页'
);
progressRuntime.resumeFromPage = shiftedReuseEnd + 1;
scheduleStatePersist();
liveCursor = shiftedReuseEnd + 1;
}
var liveTailStart = Math.max(normalizedStart, liveCursor, shiftedReuseEnd + 1);
if (!stopFetching && liveTailStart <= normalizedEnd) {
if (liveTailStart > normalizedStart) {
updateStatus('智能增量:补抓未覆盖页 ' + liveTailStart + '-' + normalizedEnd, 'loading');
}
await fetchLivePageRange(liveTailStart, normalizedEnd, searchContext);
}
if (!stopFetching) {
await saveCoverageSnapshot(
forumKey,
normalizedStart,
normalizedEnd,
coverageMapToList(searchContext.coverageThreadMap),
frontRefreshPages,
cacheStrategy
);
}
}
progressRuntime.isRunning = false;
if (manualClearRequested) {
manualClearRequested = false;
clearMagnetList(true);
clearSessionBackupState();
resetProgressRuntimeToIdle();
updateStatus('已手动清空结果', 'done');
scheduleStatePersist();
return;
}
var keywordMsg = keyword ? ' (关键词:' + keyword + ' 匹配:' + searchContext.matchedThreads + '帖)' : '';
var failedMsg = searchContext.failedPages > 0 ? ',失败页:' + searchContext.failedPages : '';
updateProgress(normalizedEnd - normalizedStart + 1, normalizedEnd - normalizedStart + 1, stopFetching ? '已停止' : '已完成');
if (stopFetching) {
progressRuntime.stoppedByUser = true;
updateStatus('已停止 - 找到' + searchContext.allMagnets.size + '个磁力' + keywordMsg + ',已处理帖子:' + searchContext.totalFetched + failedMsg, 'error');
} else {
progressRuntime.stoppedByUser = false;
progressRuntime.resumeFromPage = normalizedEnd + 1;
updateStatus('完成! 共' + searchContext.allMagnets.size + '个磁力' + keywordMsg + ',已处理帖子:' + searchContext.totalFetched + failedMsg, 'done');
if (keyword) {
saveSearchHistory(keyword);
}
playDoneTone();
showDoneNotification('磁力链接抓取完成', '共获取 ' + searchContext.allMagnets.size + ' 个磁力链接,用时 ' + Math.max(1, Math.round((Date.now() - startTime) / 1000)) + ' 秒');
}
scheduleStatePersist();
} finally {
if (ball) ball.style.display = 'flex';
isFetching = false;
}
}
function fetchAllMagnets() {
if ('Notification' in window && Notification.permission === 'default') {
Notification.requestPermission().catch(function() {});
}
var startPage = parseInt(document.getElementById('page-start').value, 10) || 1;
var endPage = parseInt(document.getElementById('page-end').value, 10) || 1;
if (endPage < startPage) {
var temp = startPage;
startPage = endPage;
endPage = temp;
}
fetchFromPage(startPage, endPage);
}
function stopFetch() {
stopFetching = true;
progressRuntime.isRunning = false;
progressRuntime.stoppedByUser = true;
saveStateNow();
}
async function initializePluginUi() {
createFloatingPanel();
var panel = document.getElementById('magnet-floating-panel');
if (!panel) return;
var settingsArea = panel.querySelector('#magnet-settings');
if (!settingsArea) return;
await loadPrivateStateFromExtensionStorage();
renderCloudAuthSection();
if (isListPage() && !document.getElementById('keyword-input')) {
var currentPage = getCurrentPage();
var keywordDiv = document.createElement('div');
keywordDiv.className = 'magnet-control-row';
keywordDiv.innerHTML = '<input type="text" id="keyword-input" placeholder="关键词(逗号分隔多关键词)" style="width:100%;padding:11px 14px;border:1px solid rgba(0,212,170,0.3);border-radius:12px;font-size:13px;box-sizing:border-box;background:#0f1419;color:#f0f4f8;box-shadow:inset 0 1px 2px rgba(0,0,0,0.2)">';
var keywordInputEl = keywordDiv.querySelector('#keyword-input');
if (keywordInputEl) {
keywordInputEl.addEventListener('focus', function() {
showHistoryDropdown(keywordInputEl);
});
keywordInputEl.addEventListener('input', function() {
removeHistoryDropdown();
});
}
var pageRange = document.createElement('div');
pageRange.className = 'magnet-control-row';
pageRange.style.cssText = 'font-size:12px;color:#8892a4;display:flex;align-items:center;gap:8px';
pageRange.innerHTML = '<span>页码范围</span><input type="number" id="page-start" value="' + currentPage + '" min="1" style="width:72px;padding:9px 10px;border:1px solid rgba(0,212,170,0.3);border-radius:12px;text-align:center;font-size:13px;background:#0f1419;color:#f0f4f8"><span>到</span><input type="number" id="page-end" value="' + currentPage + '" min="1" style="width:72px;padding:9px 10px;border:1px solid rgba(0,212,170,0.3);border-radius:12px;text-align:center;font-size:13px;background:#0f1419;color:#f0f4f8"><span>页</span><button id="page-range-all" type="button" style="padding:8px 12px;background:rgba(26,31,46,0.92);color:#00d4aa;border:1px solid rgba(0,212,170,0.3);border-radius:10px;cursor:pointer;font-size:12px;font-weight:700;">全页</button>';
var btnContainer = document.createElement('div');
btnContainer.className = 'magnet-control-row';
var btn = document.createElement('button');
btn.textContent = '开始';
btn.style.cssText = 'flex:1;padding:11px 14px;background:linear-gradient(135deg,#00d4aa,#00f5c4);color:#0a0e14;border:none;border-radius:12px;cursor:pointer;font-size:13px;font-weight:700;box-shadow:0 10px 20px rgba(0,212,170,.3)';
btn.onclick = fetchAllMagnets;
var stopBtn = document.createElement('button');
stopBtn.textContent = '停止';
stopBtn.style.cssText = 'padding:11px 14px;background:linear-gradient(135deg,#ef4444,#f87171);color:#fff;border:none;border-radius:12px;cursor:pointer;font-size:13px;font-weight:700';
stopBtn.onclick = stopFetch;
var clearBtn = document.createElement('button');
clearBtn.textContent = '清结果';
clearBtn.style.cssText = 'padding:11px 14px;background:rgba(26,31,46,0.92);color:#8892a4;border:1px solid rgba(255,255,255,0.06);border-radius:12px;cursor:pointer;font-size:13px;font-weight:700';
clearBtn.onclick = clearAllResults;
btnContainer.appendChild(btn);
btnContainer.appendChild(stopBtn);
btnContainer.appendChild(clearBtn);
var speedDiv = document.createElement('div');
speedDiv.className = 'magnet-control-row';
speedDiv.style.cssText = 'font-size:12px;color:#8892a4;display:flex;align-items:center;gap:8px';
speedDiv.innerHTML = '<span>抓取速度</span><select id="speed-select" style="flex:1;padding:10px 12px;border:1px solid rgba(0,212,170,0.3);border-radius:12px;font-size:13px;background:#0f1419;color:#f0f4f8"><option value="slow">慢</option><option value="medium">中</option><option value="fast" selected>快</option><option value="ultrafast">超快</option></select>';
settingsArea.appendChild(keywordDiv);
settingsArea.appendChild(pageRange);
settingsArea.appendChild(btnContainer);
settingsArea.appendChild(speedDiv);
var pageStartEl = pageRange.querySelector('#page-start');
var pageEndEl = pageRange.querySelector('#page-end');
var pageRangeAllBtn = pageRange.querySelector('#page-range-all');
if (pageRangeAllBtn && pageStartEl && pageEndEl) {
pageRangeAllBtn.addEventListener('click', function() {
var lastPage = getLastPage();
pageStartEl.value = '1';
pageEndEl.value = String(lastPage);
updateStatus('已选择全页范围1-' + lastPage, 'done');
});
}
var speedSelectEl = speedDiv.querySelector('#speed-select');
if (speedSelectEl) {
speedSelectEl.addEventListener('change', function() {
speedMode = speedConfig[speedSelectEl.value] ? speedSelectEl.value : 'fast';
scheduleCloudVaultSync();
});
}
}
if (!document.getElementById('magnet-status')) {
var footer = panel.querySelector('.magnet-panel-footer');
if (footer) {
var statusText = document.createElement('div');
statusText.id = 'magnet-status';
statusText.style.cssText = 'margin-bottom:8px;padding:10px 12px;background:rgba(26,31,46,0.92);border:1px solid rgba(0,212,170,0.3);border-radius:12px;font-size:12px;color:#8892a4';
footer.insertBefore(statusText, footer.firstChild);
}
}
var restoreResult = await restoreProgressState();
var restored = !!(restoreResult && restoreResult.restored);
var restoredState = restoreResult ? restoreResult.state : null;
await refreshCloudStatusAndVault({ restoreProgress: !restored });
if (isListPage()) {
var resumePage = Number.isFinite(Number(restoredState && restoredState.resumeFromPage))
? Math.max(1, Number(restoredState.resumeFromPage))
: Math.max(1, Number(restoredState && restoredState.startPage) || 1);
var resumeEndPage = Number.isFinite(Number(restoredState && restoredState.endPage))
? Math.max(1, Number(restoredState.endPage))
: resumePage;
var statusType = typeof (restoredState && restoredState.statusType) === 'string'
? restoredState.statusType
: '';
var statusText = typeof (restoredState && restoredState.statusText) === 'string'
? restoredState.statusText
: '';
var legacyRunningHint = statusType === 'loading' || /正在|开始|第\d+\/\d+页/.test(statusText);
var shouldAutoResume = !!(
restoredState &&
(restoredState.isRunning || legacyRunningHint) &&
!restoredState.stoppedByUser &&
resumePage <= resumeEndPage
);
if (shouldAutoResume && !isFetching) {
updateStatus('检测到未完成任务,正在自动继续...', 'loading');
fetchFromPage(resumePage, resumeEndPage, {
preserveExisting: true,
keyword: typeof restoredState.keyword === 'string' ? restoredState.keyword : '',
speedMode: typeof restoredState.speedMode === 'string' ? restoredState.speedMode : 'fast'
});
return;
}
if (!restored) {
updateStatus('输入页码范围获取磁力', '');
}
return;
}
var pageTitle = document.title || '当前帖子';
var magnets = extractMagnets();
if (magnets.length > 0) {
magnets.forEach(function(m) { addMagnetItem(pageTitle, m); });
updateStatus('找到 ' + magnets.length + ' 个磁力', 'done');
} else if (!restored) {
updateStatus('未找到磁力链接', 'error');
}
}
var currentUrl = window.location.hostname;
if (currentUrl.includes('sehuatang')) {
initializePluginUi().catch(function(e) {
log('初始化失败: ' + e);
});
setInterval(function() {
var panelExists = !!document.getElementById('magnet-floating-panel');
var ballExists = !!document.getElementById('magnet-float-ball');
if (panelExists && ballExists) {
return;
}
hasRestoredProgress = false;
initializePluginUi().catch(function(e) {
log('自动恢复UI失败: ' + e);
});
}, 2500);
}
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
if (request.action === 'getMagnets') {
var magnets = extractMagnets();
sendResponse({
magnets: magnets,
found: magnets.length > 0,
count: magnets.length
});
return;
}
if (request.action === 'copyMagnet') {
var magnetsToCopy = extractMagnets();
if (magnetsToCopy.length === 0) {
sendResponse({ found: false, count: 0 });
return;
}
navigator.clipboard.writeText(magnetsToCopy.join('\n'))
.then(function() {
sendResponse({ found: true, count: magnetsToCopy.length });
})
.catch(function(err) {
var errorMsg = err && err.message ? err.message : '复制失败';
sendResponse({ found: false, count: magnetsToCopy.length, error: errorMsg });
});
return true;
}
});
})();