// ==UserScript== // @name 可灵和即梦AI去水印下载脚本 (兼容增强修复版) // @namespace http://tampermonkey.net/ // @version 3.4 // @description 自动监测可灵和即梦AI网页端生成的视频和照片,提供完全独立的预览、勾选和无水印批量下载功能 (增强兼容性与修复视频预览问题) // @author 醉春风 // @license Custom:ZuiChunFeng-Exclusive; 本脚本版权归"醉春风"所有,未经授权禁止复制、修改、分发或二次开发。仅限作者本人维护和更新。 // @match https://app.klingai.com/* // @match https://klingai.kuaishou.com/* // @match https://klingai.com/* // @match https://jimeng.jianying.com/* // @match https://www.youtube.com/* // @match https://youtube.com/* // @grant GM_xmlhttpRequest // @grant GM_download // @grant GM_addStyle // @grant unsafeWindow // @grant GM_log // @connect * // @run-at document-idle // @downloadURL https://update.greasyfork.icu/scripts/537396/%E5%8F%AF%E7%81%B5%E5%92%8C%E5%8D%B3%E6%A2%A6AI%E5%8E%BB%E6%B0%B4%E5%8D%B0%E4%B8%8B%E8%BD%BD%E8%84%9A%E6%9C%AC%20%28%E5%85%BC%E5%AE%B9%E5%A2%9E%E5%BC%BA%E4%BF%AE%E5%A4%8D%E7%89%88%29.user.js // @updateURL https://update.greasyfork.icu/scripts/537396/%E5%8F%AF%E7%81%B5%E5%92%8C%E5%8D%B3%E6%A2%A6AI%E5%8E%BB%E6%B0%B4%E5%8D%B0%E4%B8%8B%E8%BD%BD%E8%84%9A%E6%9C%AC%20%28%E5%85%BC%E5%AE%B9%E5%A2%9E%E5%BC%BA%E4%BF%AE%E5%A4%8D%E7%89%88%29.meta.js // ==/UserScript== /* * ===================================================================== * 版权声明:本脚本版权归"醉春风"所有,保留所有权利 * 使用许可:仅限个人使用,未经授权禁止复制、修改、分发或二次开发 * 维护权限:仅限作者本人维护和更新 * 违规处理:任何未经授权的复制、修改或分发行为将被视为侵权 * ===================================================================== */ (function() { 'use strict'; // 版权信息 console.log('[版权信息] 版权所有:醉春风'); // --- 配置 --- const CONFIG = { DEBUG: true, // 是否开启调试日志 INIT_DELAY: 5000, // 初始延迟 (毫秒), 等待页面加载 RETRY_DELAY: 3000, // 重试延迟 (毫秒) MAX_RETRIES: 5, // 最大重试次数 BUTTON_CHECK_INTERVAL: 2000, // 按钮可见性检查间隔 (毫秒) AUTHOR: "醉春风", // 作者署名 VERSION: "3.4" // 版本号 }; // --- 日志 --- const log = (...args) => { if (CONFIG.DEBUG) { console.log(`[AI下载脚本 v${CONFIG.VERSION}]`, ...args); // GM_log(`[AI下载脚本 v${CONFIG.VERSION}] ` + args.join(' ')); // 可选:使用GM_log持久化日志 } }; const error = (...args) => { console.error(`[AI下载脚本 v${CONFIG.VERSION}]`, ...args); // GM_log(`[AI下载脚本 v${CONFIG.VERSION}] ERROR: ` + args.join(' ')); // 可选:使用GM_log持久化日志 }; // --- 样式注入 --- GM_addStyle(` /* 主按钮样式 */ .manus-ai-downloader-btn { position: fixed !important; bottom: 20px !important; right: 20px !important; background: linear-gradient(135deg, #3a8ffe 0%, #9259fe 100%) !important; color: white !important; border: none !important; border-radius: 4px !important; padding: 10px 15px !important; font-size: 14px !important; font-weight: bold !important; cursor: pointer !important; z-index: 2147483646 !important; /* Max z-index - 1 */ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2) !important; display: flex !important; align-items: center !important; transition: all 0.3s ease !important; } .manus-ai-downloader-btn:hover { transform: translateY(-2px) !important; box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3) !important; } .manus-ai-downloader-btn svg { margin-right: 8px !important; fill: none !important; stroke: white !important; stroke-width: 2 !important; stroke-linecap: round !important; stroke-linejoin: round !important; } /* 手动扫描按钮 */ .manus-ai-manual-scan-btn { position: fixed !important; bottom: 70px !important; right: 20px !important; background: #444 !important; color: white !important; border: none !important; border-radius: 4px !important; padding: 8px 12px !important; font-size: 14px !important; cursor: pointer !important; z-index: 2147483645 !important; box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2) !important; transition: all 0.3s ease !important; } .manus-ai-manual-scan-btn:hover { background: #555 !important; transform: translateY(-2px) !important; } /* 通知样式 */ .manus-ai-downloader-notification { position: fixed !important; top: 20px !important; right: 20px !important; background: rgba(0, 0, 0, 0.8) !important; color: white !important; padding: 10px 15px !important; border-radius: 4px !important; z-index: 2147483647 !important; /* Max z-index */ font-size: 14px !important; max-width: 300px !important; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2) !important; animation: manusAiFadeInOut 3s forwards !important; } @keyframes manusAiFadeInOut { 0% { opacity: 0; transform: translateY(-20px); } 10% { opacity: 1; transform: translateY(0); } 90% { opacity: 1; transform: translateY(0); } 100% { opacity: 0; transform: translateY(-20px); } } /* 错误通知样式 */ .manus-ai-downloader-error { position: fixed !important; top: 20px !important; right: 20px !important; background: rgba(255, 59, 48, 0.9) !important; color: white !important; padding: 10px 15px !important; border-radius: 4px !important; z-index: 2147483647 !important; /* Max z-index */ font-size: 14px !important; max-width: 300px !important; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2) !important; animation: manusAiShakeError 0.5s forwards, manusAiFadeOutError 3s 0.5s forwards !important; display: flex !important; align-items: center !important; } .manus-ai-downloader-error svg { margin-right: 8px !important; fill: none !important; stroke: white !important; stroke-width: 2 !important; stroke-linecap: round !important; stroke-linejoin: round !important; } @keyframes manusAiShakeError { 0%, 100% { transform: translateX(0); } 10%, 30%, 50%, 70%, 90% { transform: translateX(-5px); } 20%, 40%, 60%, 80% { transform: translateX(5px); } } @keyframes manusAiFadeOutError { 0%, 80% { opacity: 1; } 100% { opacity: 0; transform: translateY(-20px); } } /* 预览面板样式 */ .manus-ai-preview-panel { position: fixed !important; top: 10% !important; right: 0 !important; width: 30% !important; height: 80% !important; min-width: 300px !important; min-height: 400px !important; background: rgba(26, 26, 26, 0.95) !important; /* Slightly less transparent */ border-radius: 8px 0 0 8px !important; box-shadow: 0 4px 20px rgba(0, 0, 0, 0.25) !important; z-index: 2147483646 !important; /* Max z-index - 1 */ display: flex !important; flex-direction: column !important; transform: translateX(100%) !important; transition: transform 0.3s ease !important; border-left: 2px solid #3a8ffe !important; color: white !important; /* Ensure text color */ font-family: sans-serif !important; /* Reset font */ } .manus-ai-preview-panel.show { transform: translateX(0) !important; } /* 预览面板头部 */ .manus-ai-preview-header { display: flex !important; justify-content: space-between !important; align-items: center !important; padding: 10px 15px !important; border-bottom: 1px solid rgba(255, 255, 255, 0.1) !important; flex-shrink: 0 !important; } .manus-ai-preview-title { color: white !important; font-size: 16px !important; font-weight: bold !important; } .manus-ai-author-credit { color: #ffcc00 !important; font-size: 12px !important; font-style: italic !important; margin-left: 10px !important; font-weight: normal !important; } .manus-ai-preview-close { color: white !important; background: none !important; border: none !important; cursor: pointer !important; font-size: 18px !important; padding: 5px !important; display: flex !important; align-items: center !important; justify-content: center !important; border-radius: 50% !important; width: 24px !important; height: 24px !important; transition: background 0.2s !important; } .manus-ai-preview-close:hover { background: rgba(255, 255, 255, 0.1) !important; } /* 预览面板工具栏 */ .manus-ai-preview-toolbar { display: flex !important; padding: 10px 15px !important; border-bottom: 1px solid rgba(255, 255, 255, 0.1) !important; gap: 8px !important; flex-wrap: wrap !important; flex-shrink: 0 !important; } .manus-ai-preview-toolbar button { background: rgba(255, 255, 255, 0.1) !important; color: white !important; border: none !important; border-radius: 4px !important; padding: 5px 10px !important; font-size: 12px !important; cursor: pointer !important; transition: background 0.2s !important; } .manus-ai-preview-toolbar button:hover { background: rgba(255, 255, 255, 0.2) !important; } .manus-ai-preview-counter { margin-left: auto !important; color: rgba(255, 255, 255, 0.7) !important; font-size: 12px !important; display: flex !important; align-items: center !important; } /* 预览内容区域 */ .manus-ai-preview-content { flex: 1 !important; overflow-y: auto !important; padding: 15px !important; display: grid !important; grid-template-columns: repeat(auto-fill, minmax(80px, 1fr)) !important; gap: 10px !important; } .manus-ai-preview-content::-webkit-scrollbar { width: 6px !important; } .manus-ai-preview-content::-webkit-scrollbar-track { background: rgba(0, 0, 0, 0.1) !important; } .manus-ai-preview-content::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, 0.2) !important; border-radius: 3px !important; } /* 媒体项样式 (完全自定义) */ .manus-ai-media-item { position: relative !important; width: 100% !important; padding-bottom: 100% !important; /* 1:1 Aspect Ratio */ border-radius: 4px !important; overflow: hidden !important; cursor: pointer !important; transition: transform 0.2s, box-shadow 0.2s !important; background-color: #2a2a2a !important; } .manus-ai-media-item:hover { transform: scale(1.05) !important; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3) !important; z-index: 1 !important; } .manus-ai-media-item.selected { border: 2px solid #3a8ffe !important; box-shadow: 0 0 0 2px rgba(58, 143, 254, 0.2) !important; } .manus-ai-media-thumbnail-container { position: absolute !important; top: 0 !important; left: 0 !important; width: 100% !important; height: 100% !important; display: flex !important; align-items: center !important; justify-content: center !important; background-color: #2a2a2a !important; } .manus-ai-media-thumbnail-container svg { width: 32px !important; height: 32px !important; opacity: 1 !important; fill: none !important; stroke: #ffffff !important; stroke-width: 2 !important; stroke-linecap: round !important; stroke-linejoin: round !important; } .manus-ai-media-checkbox { position: absolute !important; bottom: 5px !important; left: 5px !important; width: 18px !important; height: 18px !important; border: 2px solid white !important; border-radius: 2px !important; background: rgba(0, 0, 0, 0.5) !important; display: flex !important; align-items: center !important; justify-content: center !important; transition: background 0.2s !important; z-index: 3 !important; } .manus-ai-media-checkbox.checked { background: #3a8ffe !important; } .manus-ai-media-checkbox.checked::after { content: "" !important; width: 10px !important; height: 5px !important; border-left: 2px solid white !important; border-bottom: 2px solid white !important; transform: rotate(-45deg) translate(1px, -1px) !important; } /* 视频特殊样式 */ .manus-ai-media-item.video .manus-ai-media-thumbnail-container::after { content: "" !important; position: absolute !important; top: 50% !important; left: 50% !important; transform: translate(-50%, -50%) !important; width: 0 !important; height: 0 !important; border-top: 10px solid transparent !important; border-bottom: 10px solid transparent !important; border-left: 15px solid rgba(255, 255, 255, 0.8) !important; filter: drop-shadow(0 0 2px rgba(0, 0, 0, 0.5)) !important; z-index: 1 !important; } .manus-ai-media-duration { position: absolute !important; bottom: 5px !important; right: 5px !important; background: rgba(0, 0, 0, 0.7) !important; color: white !important; font-size: 10px !important; padding: 2px 4px !important; border-radius: 2px !important; z-index: 3 !important; } /* 预览面板底部 */ .manus-ai-preview-footer { padding: 10px 15px !important; border-top: 1px solid rgba(255, 255, 255, 0.1) !important; display: flex !important; justify-content: space-between !important; flex-shrink: 0 !important; } .manus-ai-download-btn { background: linear-gradient(135deg, #3a8ffe 0%, #9259fe 100%) !important; color: white !important; border: none !important; border-radius: 4px !important; padding: 8px 15px !important; font-size: 14px !important; font-weight: bold !important; cursor: pointer !important; transition: all 0.2s !important; display: flex !important; align-items: center !important; } .manus-ai-download-btn svg { margin-right: 6px !important; fill: none !important; stroke: white !important; stroke-width: 2 !important; stroke-linecap: round !important; stroke-linejoin: round !important; } .manus-ai-download-btn:hover { transform: translateY(-2px) !important; box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2) !important; } .manus-ai-download-btn:disabled { background: #666 !important; cursor: not-allowed !important; transform: none !important; box-shadow: none !important; } .manus-ai-cancel-btn { background: rgba(255, 255, 255, 0.1) !important; color: white !important; border: none !important; border-radius: 4px !important; padding: 8px 15px !important; font-size: 14px !important; cursor: pointer !important; transition: background 0.2s !important; } .manus-ai-cancel-btn:hover { background: rgba(255, 255, 255, 0.2) !important; } /* 大预览样式 */ .manus-ai-large-preview { position: fixed !important; top: 0 !important; left: 0 !important; width: 100% !important; height: 100% !important; background: rgba(0, 0, 0, 0.9) !important; z-index: 2147483647 !important; /* Max z-index */ display: flex !important; align-items: center !important; justify-content: center !important; opacity: 0 !important; pointer-events: none !important; transition: opacity 0.3s ease !important; } .manus-ai-large-preview.show { opacity: 1 !important; pointer-events: auto !important; } .manus-ai-large-preview-close { position: absolute !important; top: 20px !important; right: 20px !important; color: white !important; background: rgba(0, 0, 0, 0.5) !important; border: none !important; border-radius: 50% !important; width: 40px !important; height: 40px !important; font-size: 24px !important; display: flex !important; align-items: center !important; justify-content: center !important; cursor: pointer !important; transition: background 0.2s !important; z-index: 2 !important; } .manus-ai-large-preview-close:hover { background: rgba(255, 255, 255, 0.2) !important; } .manus-ai-large-preview-container { max-width: 90% !important; max-height: 90% !important; position: relative !important; display: flex !important; align-items: center !important; justify-content: center !important; } .manus-ai-large-preview-container img { max-width: 100% !important; max-height: 100% !important; object-fit: contain !important; border-radius: 4px !important; box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5) !important; } .manus-ai-large-preview-container video { max-width: 100% !important; max-height: 100% !important; object-fit: contain !important; border-radius: 4px !important; box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5) !important; background: #000 !important; } .manus-ai-video-controls { position: absolute !important; bottom: 10px !important; left: 0 !important; width: 100% !important; display: flex !important; justify-content: center !important; padding: 10px !important; background: rgba(0, 0, 0, 0.5) !important; border-radius: 0 0 4px 4px !important; opacity: 0 !important; transition: opacity 0.3s !important; } .manus-ai-large-preview-container:hover .manus-ai-video-controls { opacity: 1 !important; } .manus-ai-video-error { color: white !important; background: rgba(255, 59, 48, 0.8) !important; padding: 10px 15px !important; border-radius: 4px !important; text-align: center !important; max-width: 80% !important; } /* 作者署名水印 */ .manus-ai-author-watermark { position: absolute !important; bottom: 10px !important; left: 10px !important; color: rgba(255, 255, 255, 0.5) !important; font-size: 12px !important; font-style: italic !important; pointer-events: none !important; z-index: 1 !important; text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5) !important; } `); // --- 状态管理 --- const state = { platform: null, mediaItems: [], selectedItems: [], isProcessing: false, isPanelOpen: false, downloadQueue: [], currentDownloading: null, largePreviewOpen: false, mediaMap: new Map(), mediaUrls: new Set(), windowLock: false, instanceId: 'manus_ai_downloader_' + Math.random().toString(36).substr(2, 9), initRetries: 0, observer: null, // MutationObserver实例 buttonCheckIntervalId: null // 按钮检查定时器ID }; // --- 图标 --- const ICONS = { image: ``, video: ``, download: ``, error: ``, play: `` }; // --- 媒体项类 --- class MediaItem { constructor(element, type, url = null) { this.id = 'media_' + Math.random().toString(36).substr(2, 9); this.element = element; // 原始DOM元素引用 (可能为null) this.type = type; // 'image' 或 'video' this.url = url || (element ? (element.src || element.currentSrc) : null); this.highestQualityUrl = this.url; this.width = element ? (element.width || element.videoWidth || 0) : 0; this.height = element ? (element.height || element.videoHeight || 0) : 0; this.duration = type === 'video' && element ? element.duration : 0; this.selected = false; this.timestamp = Date.now(); // 添加时间戳 this.videoLoaded = false; // 视频是否已加载成功 this.thumbnailLoaded = false; // 缩略图是否已加载成功 log(`创建媒体项: ${this.id}, 类型: ${this.type}, URL: ${this.url ? this.url.substring(0, 50) + '...' : 'N/A'}`); } // 创建缩略图元素 (完全自定义) createThumbnailElement() { const itemDiv = document.createElement('div'); itemDiv.className = `manus-ai-media-item ${this.type}`; itemDiv.dataset.id = this.id; const thumbnailContainer = document.createElement('div'); thumbnailContainer.className = 'manus-ai-media-thumbnail-container'; // 尝试加载实际缩略图 if (this.url) { try { if (this.type === 'image') { const img = document.createElement('img'); img.src = this.url; img.style.width = '100%'; img.style.height = '100%'; img.style.objectFit = 'cover'; img.style.position = 'absolute'; img.style.top = '0'; img.style.left = '0'; img.crossOrigin = 'anonymous'; // 尝试解决跨域问题 img.loading = 'eager'; // 优先加载 img.decoding = 'async'; // 异步解码 // 添加加载中占位符 const placeholder = document.createElement('div'); placeholder.innerHTML = ICONS.image; placeholder.style.position = 'absolute'; placeholder.style.top = '0'; placeholder.style.left = '0'; placeholder.style.width = '100%'; placeholder.style.height = '100%'; placeholder.style.display = 'flex'; placeholder.style.alignItems = 'center'; placeholder.style.justifyContent = 'center'; placeholder.style.backgroundColor = '#2a2a2a'; thumbnailContainer.appendChild(placeholder); img.onload = () => { this.thumbnailLoaded = true; placeholder.remove(); log(`图片 ${this.id} 缩略图加载成功`); }; img.onerror = () => { // 加载失败时保留占位符 log(`图片 ${this.id} 加载失败,保留占位符`); // 尝试使用备用方法加载 setTimeout(() => { if (!this.thumbnailLoaded) { // 创建一个新的图片元素尝试再次加载 const retryImg = new Image(); retryImg.crossOrigin = 'anonymous'; retryImg.src = this.url + '?retry=' + new Date().getTime(); // 添加时间戳避免缓存 retryImg.onload = () => { img.src = retryImg.src; this.thumbnailLoaded = true; placeholder.remove(); log(`图片 ${this.id} 重试加载成功`); }; } }, 1000); }; thumbnailContainer.appendChild(img); } else { // 视频缩略图 - 尝试加载视频并获取第一帧 const video = document.createElement('video'); video.src = this.url; video.crossOrigin = 'anonymous'; // 尝试解决跨域问题 video.muted = true; video.preload = 'metadata'; // 只加载元数据 video.style.width = '100%'; video.style.height = '100%'; video.style.objectFit = 'cover'; video.style.position = 'absolute'; video.style.top = '0'; video.style.left = '0'; // 添加视频占位符 const placeholder = document.createElement('div'); placeholder.innerHTML = ICONS.video; placeholder.style.position = 'absolute'; placeholder.style.top = '0'; placeholder.style.left = '0'; placeholder.style.width = '100%'; placeholder.style.height = '100%'; placeholder.style.display = 'flex'; placeholder.style.alignItems = 'center'; placeholder.style.justifyContent = 'center'; placeholder.style.backgroundColor = '#2a2a2a'; thumbnailContainer.appendChild(placeholder); // 添加播放按钮图标覆盖层 const playIcon = document.createElement('div'); playIcon.style.position = 'absolute'; playIcon.style.top = '50%'; playIcon.style.left = '50%'; playIcon.style.transform = 'translate(-50%, -50%)'; playIcon.style.width = '30px'; playIcon.style.height = '30px'; playIcon.style.zIndex = '2'; playIcon.style.opacity = '0.8'; playIcon.innerHTML = ICONS.play; // 尝试加载视频并获取第一帧 video.addEventListener('loadeddata', () => { this.videoLoaded = true; this.thumbnailLoaded = true; this.duration = video.duration || 0; // 更新时长显示 const durationEl = itemDiv.querySelector('.manus-ai-media-duration'); if (durationEl) { durationEl.textContent = this.formatDuration(this.duration); } // 视频加载成功,移除占位符 placeholder.remove(); thumbnailContainer.appendChild(playIcon); log(`视频 ${this.id} 缩略图加载成功`); }); video.addEventListener('error', (e) => { // 视频加载失败,保留占位符 log(`视频 ${this.id} 加载失败: ${e.target.error ? e.target.error.message : '未知错误'}`); // 尝试使用备用方法加载 setTimeout(() => { if (!this.thumbnailLoaded) { // 尝试使用不同的方式加载视频 const retryVideo = document.createElement('video'); retryVideo.crossOrigin = 'anonymous'; retryVideo.muted = true; retryVideo.src = this.url + '?retry=' + new Date().getTime(); // 添加时间戳避免缓存 retryVideo.addEventListener('loadeddata', () => { video.src = retryVideo.src; this.videoLoaded = true; this.thumbnailLoaded = true; this.duration = retryVideo.duration || 0; // 更新时长显示 const durationEl = itemDiv.querySelector('.manus-ai-media-duration'); if (durationEl) { durationEl.textContent = this.formatDuration(this.duration); } placeholder.remove(); thumbnailContainer.appendChild(playIcon); log(`视频 ${this.id} 重试加载成功`); }); } }, 1000); }); thumbnailContainer.appendChild(video); // 设置一个超时,如果视频在一定时间内未加载,则保留占位符 setTimeout(() => { if (!this.thumbnailLoaded) { log(`视频 ${this.id} 加载超时,保留占位符`); } }, 5000); } } catch (e) { error('创建缩略图时出错:', e); thumbnailContainer.innerHTML = this.type === 'image' ? ICONS.image : ICONS.video; } } else { thumbnailContainer.innerHTML = this.type === 'image' ? ICONS.image : ICONS.video; } itemDiv.appendChild(thumbnailContainer); if (this.type === 'video') { const durationSpan = document.createElement('span'); durationSpan.className = 'manus-ai-media-duration'; durationSpan.textContent = this.formatDuration(this.duration); itemDiv.appendChild(durationSpan); } const checkbox = document.createElement('div'); checkbox.className = 'manus-ai-media-checkbox'; if (this.selected) { checkbox.classList.add('checked'); } itemDiv.appendChild(checkbox); itemDiv.addEventListener('click', (e) => { if (state.windowLock) return; const rect = checkbox.getBoundingClientRect(); const isCheckboxClick = ( e.clientX >= rect.left && e.clientX <= rect.right && e.clientY >= rect.top && e.clientY <= rect.bottom ); if (isCheckboxClick) { this.toggleSelect(); UI.updateSelectedCounter(); UI.updateDownloadButton(); } else { if (!state.largePreviewOpen) { state.windowLock = true; setTimeout(() => { state.windowLock = false; }, 500); UI.openLargePreview(this); } } }); return itemDiv; } toggleSelect() { this.selected = !this.selected; const itemElement = document.querySelector(`.manus-ai-media-item[data-id="${this.id}"]`); if (itemElement) { itemElement.classList.toggle('selected', this.selected); itemElement.querySelector('.manus-ai-media-checkbox').classList.toggle('checked', this.selected); } if (this.selected) { if (!state.selectedItems.includes(this.id)) state.selectedItems.push(this.id); } else { const index = state.selectedItems.indexOf(this.id); if (index !== -1) state.selectedItems.splice(index, 1); } } setSelected(selected) { if (this.selected !== selected) { this.toggleSelect(); } } formatDuration(seconds) { if (!seconds || isNaN(seconds)) return '0:00'; seconds = Math.round(seconds); const minutes = Math.floor(seconds / 60); seconds = seconds % 60; return `${minutes}:${seconds.toString().padStart(2, '0')}`; } } // --- UI 相关函数 --- const UI = { // 创建主按钮 (增强) createMainButton: () => { const existingButton = document.querySelector('.manus-ai-downloader-btn'); if (existingButton) { log('主按钮已存在'); return existingButton; } const button = document.createElement('button'); button.className = 'manus-ai-downloader-btn'; button.innerHTML = `${ICONS.download} 无水印下载`; button.style.visibility = 'hidden'; // Initially hidden // Try appending to body first document.body.appendChild(button); log('尝试将按钮添加到 document.body'); // Check visibility after a short delay setTimeout(() => { const rect = button.getBoundingClientRect(); if (rect.width > 0 && rect.height > 0) { button.style.visibility = 'visible'; log('主按钮已成功添加到 body 并可见'); } else { error('主按钮添加到 body 后不可见,尝试其他容器'); // Fallback: Try appending to documentElement if body fails document.documentElement.appendChild(button); setTimeout(() => { const rect2 = button.getBoundingClientRect(); if (rect2.width > 0 && rect2.height > 0) { button.style.visibility = 'visible'; log('主按钮已成功添加到 documentElement 并可见'); } else { error('主按钮添加到 documentElement 后仍不可见,注入失败!'); UI.showError('无法创建下载按钮,请联系开发者'); button.remove(); // Clean up invisible button } }, 500); } }, 500); button.addEventListener('click', () => { if (state.windowLock) return; state.windowLock = true; setTimeout(() => { state.windowLock = false; }, 500); UI.togglePreviewPanel(); }); // Start periodic check for button visibility UI.startButtonVisibilityCheck(); return button; }, // 创建手动扫描按钮 createManualScanButton: () => { const existingButton = document.querySelector('.manus-ai-manual-scan-btn'); if (existingButton) { log('手动扫描按钮已存在'); return existingButton; } const button = document.createElement('button'); button.className = 'manus-ai-manual-scan-btn'; button.textContent = '手动扫描媒体'; document.body.appendChild(button); button.addEventListener('click', () => { log('用户触发手动扫描'); findAndProcessMedia(); UI.showNotification('手动扫描完成,检测到 ' + state.mediaItems.length + ' 个媒体项'); }); return button; }, // 定期检查按钮可见性 startButtonVisibilityCheck: () => { if (state.buttonCheckIntervalId) { clearInterval(state.buttonCheckIntervalId); } state.buttonCheckIntervalId = setInterval(() => { const button = document.querySelector('.manus-ai-downloader-btn'); if (button) { const rect = button.getBoundingClientRect(); if (rect.width === 0 || rect.height === 0 || button.style.display === 'none' || button.style.visibility === 'hidden') { log('检测到下载按钮变得不可见,尝试重新显示...'); button.style.display = 'flex !important'; button.style.visibility = 'visible !important'; // Ensure it's still in the DOM, re-append if necessary if (!document.body.contains(button) && !document.documentElement.contains(button)) { log('按钮已从DOM移除,重新添加...'); document.body.appendChild(button); } } } else { log('下载按钮丢失,尝试重新创建...'); UI.createMainButton(); // Recreate if lost } // 同时检查手动扫描按钮 const scanButton = document.querySelector('.manus-ai-manual-scan-btn'); if (!scanButton) { log('手动扫描按钮丢失,重新创建...'); UI.createManualScanButton(); } }, CONFIG.BUTTON_CHECK_INTERVAL); }, // 创建预览面板 (单例) createPreviewPanel: () => { let panel = document.querySelector('.manus-ai-preview-panel'); if (panel) return panel; panel = document.createElement('div'); panel.className = 'manus-ai-preview-panel'; panel.dataset.instanceId = state.instanceId; panel.innerHTML = `