// ==UserScript== // @name 超级助手面板 (链接新标签页+Cookie+AI媒体下载) // @name:en Super Assistant Panel (New Tab Links, Cookies, AI Media Downloader) // @namespace https://greasyfork.org/users/your-username // 建议替换为你的唯一命名空间 // @version 1.7.1 // @description 功能集成:1. 链接新标签页打开(白名单启用模式)。2. Cookie管理。3. AI媒体内容下载(作者:醉春风)。均通过右下角UI面板操作。 // @description:en Integrated: 1. Force links in new tab (whitelist mode). 2. Cookie management. 3. AI Media Downloader (Author: 醉春风). All via a UI panel. // @author AI Assistant (AI Media Downloader features by 醉春风) // @match *://*/* // @grant GM_openInTab // @grant window.open // @grant GM_getValue // @grant GM_setValue // @grant GM_registerMenuCommand // @grant GM_addStyle // @grant GM_setClipboard // @grant GM_download // @grant GM_xmlhttpRequest // @license MIT // @downloadURL none // ==/UserScript== (function() { 'use strict'; // ************************************************************************************* // * 主脚本部分 (链接新标签页打开 + Cookie管理 + 主UI面板框架) * // ************************************************************************************* const MAIN_DEBUG = true; // 设置为 true 来查看主面板的调试日志 const main_log = (...args) => { if (MAIN_DEBUG) console.log('[主助手面板]', ...args); }; const MAIN_SCRIPT_CONFIG = { whitelistKey: 'openInNewTab_custom_whitelist_v1', blacklistKey: 'openInNewTab_custom_blacklist_v1', panelVisibleKey: 'openInNewTab_panelVisibleState_v1', defaultWhitelist: [], defaultBlacklist: [] }; let main_userWhitelist = []; let main_userBlacklist = []; let main_panelVisible = GM_getValue(MAIN_SCRIPT_CONFIG.panelVisibleKey, false); main_log("脚本加载,初始面板可见状态 (从存储读取):", main_panelVisible); let main_uiPanel = null; GM_addStyle(` #superAssistantPanel { position: fixed !important; right: 15px !important; /* 定位到右侧 */ bottom: 15px !important; /* 定位到下方 */ left: auto !important; top: auto !important; width: 320px; max-height: calc(90vh - 30px); background-color: #f0f2f5; border: 1px solid #d9d9d9; border-radius: 8px; padding: 10px; z-index: 2147483640 !important; /* 主面板的 z-index */ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol"; font-size: 13px; color: #333; box-shadow: 0 6px 16px 0 rgba(0, 0, 0, 0.08), 0 3px 6px -4px rgba(0, 0, 0, 0.12), 0 9px 28px 8px rgba(0, 0, 0, 0.05); display: none !important; /* 默认隐藏 */ flex-direction: column; line-height: 1.5; } #superAssistantPanel.panel-visible { /* 当此类存在时显示 */ display: flex !important; } #superAssistantPanel .panel-header { text-align: center; font-size: 16px; font-weight: 600; color: #2c3e50; padding-bottom: 8px; margin-bottom: 8px; border-bottom: 1px solid #e8e8e8;} #superAssistantPanel .panel-content-scrollable { overflow-y: auto; flex-grow: 1; margin-bottom: 8px; padding-right: 5px; } #superAssistantPanel .section-title { margin-top: 8px; margin-bottom: 6px; font-size: 14px; font-weight: 500; color: #34495e; padding-bottom: 4px; border-bottom: 1px dashed #bdc3c7; } #superAssistantPanel .add-form { display: flex; margin-bottom: 8px; } #superAssistantPanel .add-form input[type="text"] { flex-grow: 1; margin-right: 6px; padding: 5px 8px; border: 1px solid #ccc; border-radius: 4px; font-size: 12px; } #superAssistantPanel .add-form button { padding: 5px 10px; font-size: 12px; color: white; background-color: #3498db; border: none; border-radius: 4px; cursor: pointer; white-space: nowrap; } #superAssistantPanel .add-form button:hover { background-color: #2980b9; } #superAssistantPanel ul { list-style-type: none; padding-left: 0; margin: 0 0 8px 0; border: 1px solid #ecf0f1; border-radius: 4px; background-color: #fff; } #superAssistantPanel li { padding: 6px 8px; border-bottom: 1px solid #f5f7fa; display: flex; align-items: center; justify-content: space-between; font-size: 12px; } #superAssistantPanel li:last-child { border-bottom: none; } #superAssistantPanel li .pattern-text { word-break: break-all; margin-right: 6px; flex-grow: 1; line-height: 1.4; } #superAssistantPanel .delete-btn { color: #e74c3c; background-color: transparent; border: none; cursor: pointer; font-weight: bold; font-size: 18px; padding: 0 4px; line-height: 1; opacity: 0.7; } #superAssistantPanel .delete-btn:hover { opacity: 1; } #superAssistantPanel .panel-empty-msg { font-size: 12px; color: #7f8c8d; padding: 8px; text-align: center; background-color: #fff; border-radius: 4px; } #superAssistantPanel .cookie-section { margin-top: 10px; } #superAssistantPanel .cookie-display { width: calc(100% - 12px); min-height: 60px; max-height:100px; margin-top: 4px; margin-bottom: 6px; padding: 5px; border: 1px solid #ccc; border-radius: 4px; font-size: 11px; word-break: break-all; overflow-y: auto; line-height: 1.4; background:#fff; } #superAssistantPanel .actions-group { display: flex; justify-content: space-between; gap: 8px; margin-top: 6px; } #superAssistantPanel .actions-group button { padding: 6px 0; font-size: 12px; color: white; border-radius: 4px; cursor: pointer; flex-grow: 1; border:none; } #superAssistantPanel .actions-group .btn-primary { background-color: #3498db; } #superAssistantPanel .actions-group .btn-primary:hover { background-color: #2980b9; } #superAssistantPanel .actions-group .btn-secondary { background-color: #2ecc71; } #superAssistantPanel .actions-group .btn-secondary:hover { background-color: #27ae60; } #superAssistantPanel .actions-group .btn-info { background-color: #1abc9c; } #superAssistantPanel .actions-group .btn-info:hover { background-color: #16a085; } #superAssistantPanel .close-panel-btn { display: block; width: 100%; padding: 7px 10px; font-size: 13px; color: #fff; background-color: #95a5a6; border: none; border-radius: 4px; cursor: pointer; margin-top:10px; } #superAssistantPanel .close-panel-btn:hover { background-color: #7f8c8d; } #superAssistantPanel .ai-downloader-author { font-size: 10px; color: #7f8c8d; text-align: right; margin-top: 4px; } /* AI Downloader Panel Styles (manus-ai-*) - 这些是AI下载模块自己的UI,层级更高 */ .manus-ai-preview-panel { /* AI下载模块的媒体选择面板,从右侧滑出 */ position: fixed !important; top: 0 !important; right: 0 !important; width: 320px !important; height: 100% !important; background: rgba(26, 26, 26, 0.98) !important; border-radius: 0 !important; box-shadow: -5px 0 15px rgba(0, 0, 0, 0.2) !important; z-index: 2147483646 !important; /* 比主面板高 */ display: flex !important; flex-direction: column !important; transform: translateX(100%) !important; transition: transform 0.35s ease-out !important; color: white !important; font-family: sans-serif !important; border-left: 1px solid #3a8ffe !important; } .manus-ai-preview-panel.show { transform: translateX(0) !important; } /* ... (AI下载模块的其他CSS,与之前版本类似,确保z-index高于主面板) ... */ .manus-ai-preview-header { display: flex !important; justify-content: space-between !important; align-items: center !important; padding: 10px 12px !important; border-bottom: 1px solid rgba(255, 255, 255, 0.1) !important; flex-shrink: 0 !important; background-color: rgba(0,0,0,0.2); } .manus-ai-preview-title { color: white !important; font-size: 15px !important; font-weight: bold !important; } .manus-ai-author-credit { color: #ffcc00 !important; font-size: 11px !important; font-style: italic !important; margin-left: 8px !important; font-weight: normal !important; } .manus-ai-preview-close { color: white !important; background: none !important; border: none !important; cursor: pointer !important; font-size: 20px !important; padding: 5px !important; line-height:1; opacity: 0.7; } .manus-ai-preview-close:hover { opacity: 1; background: rgba(255,255,255,0.1) !important; border-radius: 50%;} .manus-ai-preview-toolbar { display: flex !important; padding: 8px 12px !important; border-bottom: 1px solid rgba(255, 255, 255, 0.1) !important; gap: 6px !important; flex-wrap: wrap !important; flex-shrink: 0 !important; background-color: rgba(0,0,0,0.1); } .manus-ai-preview-toolbar button { background: rgba(255, 255, 255, 0.1) !important; color: white !important; border: none !important; border-radius: 3px !important; padding: 5px 8px !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.8) !important; font-size: 12px !important; display: flex !important; align-items: center !important; } .manus-ai-preview-content { flex: 1 !important; overflow-y: auto !important; padding: 10px !important; display: grid !important; grid-template-columns: repeat(auto-fill, minmax(75px, 1fr)) !important; gap: 8px !important; } .manus-ai-preview-content::-webkit-scrollbar { width: 5px !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.25) !important; border-radius: 3px !important; } .manus-ai-media-item { position: relative !important; width: 100% !important; padding-bottom: 100% !important; border-radius: 3px !important; overflow: hidden !important; cursor: pointer !important; transition: transform 0.15s, box-shadow 0.15s !important; background-color: #222 !important; } .manus-ai-media-item:hover { transform: scale(1.03) !important; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.4) !important; z-index: 1 !important; } .manus-ai-media-item.selected { border: 2px solid #007bff !important; box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25) !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: #282c34 !important; } .manus-ai-media-thumbnail-container svg { width: 28px !important; height: 28px !important; opacity: 0.7 !important; fill: none !important; stroke: #ccc !important; stroke-width: 1.5 !important; stroke-linecap: round !important; stroke-linejoin: round !important; } .manus-ai-media-checkbox { position: absolute !important; top: 4px !important; right: 4px !important; width: 16px !important; height: 16px !important; border: 1.5px solid white !important; border-radius: 2px !important; background: rgba(0, 0, 0, 0.6) !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: #007bff !important; border-color: #007bff !important; } .manus-ai-media-checkbox.checked::after { content: "" !important; width: 8px !important; height: 4px !important; border-left: 2px solid white !important; border-bottom: 2px solid white !important; transform: rotate(-45deg) translate(0px, -1px) !important; } .manus-ai-media-item.video .manus-ai-media-thumbnail-container::before { content: "▶"; position: absolute; font-size: 20px; color: rgba(255,255,255,0.7); z-index: 1; opacity: 0.8; text-shadow: 0 0 3px black;} .manus-ai-media-duration { position: absolute !important; bottom: 4px !important; right: 4px !important; background: rgba(0, 0, 0, 0.75) !important; color: white !important; font-size: 9px !important; padding: 1px 3px !important; border-radius: 2px !important; z-index: 3 !important; } .manus-ai-preview-footer { padding: 8px 12px !important; border-top: 1px solid rgba(255, 255, 255, 0.1) !important; display: flex !important; justify-content: space-between !important; flex-shrink: 0 !important; background-color: rgba(0,0,0,0.2); } .manus-ai-download-btn { background: linear-gradient(135deg, #007bff 0%, #0056b3 100%) !important; color: white !important; border: none !important; border-radius: 3px !important; padding: 6px 12px !important; font-size: 13px !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: 5px !important; fill: none !important; stroke: white !important; stroke-width: 2 !important; stroke-linecap: round !important; stroke-linejoin: round !important; width: 14px; height: 14px; } .manus-ai-download-btn:hover { transform: translateY(-1px) !important; box-shadow: 0 2px 5px rgba(0, 0, 0, 0.25) !important; } .manus-ai-download-btn:disabled { background: #555 !important; cursor: not-allowed !important; transform: none !important; box-shadow: none !important; opacity:0.7; } .manus-ai-cancel-btn { background: rgba(255, 255, 255, 0.15) !important; color: white !important; border: none !important; border-radius: 3px !important; padding: 6px 12px !important; font-size: 13px !important; cursor: pointer !important; transition: background 0.2s !important; } .manus-ai-cancel-btn:hover { background: rgba(255, 255, 255, 0.25) !important; } .manus-ai-downloader-notification, .manus-ai-downloader-error { position: fixed !important; top: 20px !important; right: 20px !important; background: rgba(0, 0, 0, 0.85) !important; color: white !important; padding: 10px 15px !important; border-radius: 4px !important; z-index: 2147483647 !important; font-size: 14px !important; max-width: 300px !important; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2) !important; opacity: 0; animation: manusAiFadeInAndOut 3s ease-in-out forwards !important; } .manus-ai-downloader-error { background: rgba(220, 53, 69, 0.9) !important; animation-duration: 3.5s !important; } .manus-ai-downloader-error svg { margin-right: 8px; vertical-align: middle; } @keyframes manusAiFadeInAndOut { 0%, 100% { opacity: 0; transform: translateY(-10px); } 10%, 90% { opacity: 1; transform: translateY(0); } } `); function main_getPageCookies() { return document.cookie; } function main_copyToClipboard(text) { if (typeof GM_setClipboard === 'function') { GM_setClipboard(text, 'text'); alert('内容已复制到剪贴板! (GM)'); } else { const textarea = document.createElement('textarea'); textarea.value = text; textarea.style.position = 'fixed'; textarea.style.top = '-9999px'; textarea.style.left = '-9999px'; document.body.appendChild(textarea); textarea.focus(); textarea.select(); try { if (document.execCommand('copy')) alert('内容已复制到剪贴板!'); else prompt('复制失败,请手动复制:', text); } catch (err) { prompt('复制操作异常,请手动复制:', text); } document.body.removeChild(textarea); } } function main_createAddForm(listType, listArray, configKey, parentElement) { const form = document.createElement('div'); form.className = 'add-form'; const input = document.createElement('input'); input.type = 'text'; input.placeholder = `添加${listType === 'whitelist' ? '白' : '黑'}名单模式...`; input.addEventListener('keypress', (e) => { if (e.key === 'Enter') addButton.click(); }); const addButton = document.createElement('button'); addButton.textContent = '添加'; addButton.onclick = () => { const pattern = input.value.trim(); if (pattern) { if (listArray.includes(pattern)) { alert(`模式 "${pattern}" 已存在!`); return; } listArray.push(pattern); GM_setValue(configKey, listArray.join(',')); input.value = ''; main_renderListsUI(); } else { alert('请输入有效的URL模式!'); } }; form.appendChild(input); form.appendChild(addButton); parentElement.appendChild(form); } function main_createUIPanel() { main_log("main_createUIPanel 调用"); if (!document.body) { main_log("document.body 未准备好,延迟创建"); setTimeout(main_createUIPanel, 100); return; } if (document.getElementById('superAssistantPanel')) { main_log("面板已存在,仅更新状态和内容"); main_uiPanel = document.getElementById('superAssistantPanel'); main_renderListsUI(); main_uiPanel.classList.toggle('panel-visible', main_panelVisible); main_log("面板已存在,切换后 classList:", main_uiPanel.classList); return; } main_log("创建新的UI面板DOM"); main_uiPanel = document.createElement('div'); main_uiPanel.id = 'superAssistantPanel'; const header = document.createElement('div'); header.className = 'panel-header'; header.textContent = '超级助手面板'; main_uiPanel.appendChild(header); const scrollableContent = document.createElement('div'); scrollableContent.className = 'panel-content-scrollable'; const linkRulesSection = document.createElement('div'); const linkRulesTitle = document.createElement('h4'); linkRulesTitle.className = 'section-title'; linkRulesTitle.textContent = '链接新标签页规则:'; linkRulesSection.appendChild(linkRulesTitle); ['whitelist', 'blacklist'].forEach(type => { const subSection = document.createElement('div'); const subTitle = document.createElement('h5'); subTitle.style.cssText = 'font-size: 13px; font-weight: normal; margin-bottom: 4px;'; subTitle.textContent = type === 'whitelist' ? '白名单 (在这些站点启用):' : '黑名单 (在这些站点禁用):'; subSection.appendChild(subTitle); main_createAddForm(type, type === 'whitelist' ? main_userWhitelist : main_userBlacklist, type === 'whitelist' ? MAIN_SCRIPT_CONFIG.whitelistKey : MAIN_SCRIPT_CONFIG.blacklistKey, subSection); const ul = document.createElement('ul'); ul.id = `main-${type}-ul`; subSection.appendChild(ul); linkRulesSection.appendChild(subSection); }); scrollableContent.appendChild(linkRulesSection); const cookieSection = document.createElement('div'); cookieSection.className = 'cookie-section'; const cookieTitle = document.createElement('h4'); cookieTitle.className = 'section-title'; cookieTitle.textContent = 'Cookie 管理:'; cookieSection.appendChild(cookieTitle); const cookieDisplayArea = document.createElement('textarea'); cookieDisplayArea.id = 'main-cookie-display'; cookieDisplayArea.readOnly = true; cookieDisplayArea.className = 'cookie-display'; cookieDisplayArea.placeholder = '点击“获取Cookies”按钮显示...\n(注意: HttpOnly Cookies 无法获取)'; cookieSection.appendChild(cookieDisplayArea); const cookieActionsDiv = document.createElement('div'); cookieActionsDiv.className = 'actions-group'; const getCookieButton = document.createElement('button'); getCookieButton.textContent = '获取Cookies'; getCookieButton.className = 'btn-info'; getCookieButton.onclick = () => { const cookies = main_getPageCookies(); cookieDisplayArea.value = cookies || ''; cookieDisplayArea.placeholder = cookies ? 'Cookies 已获取 (不含 HttpOnly)' : '当前页面没有可访问的 Cookies。'; }; const copyCookieButton = document.createElement('button'); copyCookieButton.textContent = '复制Cookies'; copyCookieButton.className = 'btn-secondary'; copyCookieButton.onclick = () => { if (cookieDisplayArea.value) main_copyToClipboard(cookieDisplayArea.value); else alert('文本框中没有 Cookies 内容可复制。'); }; cookieActionsDiv.appendChild(getCookieButton); cookieActionsDiv.appendChild(copyCookieButton); cookieSection.appendChild(cookieActionsDiv); scrollableContent.appendChild(cookieSection); const aiDownloaderSection = document.createElement('div'); aiDownloaderSection.style.cssText = 'margin-top: 10px; padding-top: 8px; border-top: 1px solid #e8e8e8;'; const aiDownloaderTitle = document.createElement('h4'); aiDownloaderTitle.className = 'section-title'; aiDownloaderTitle.textContent = 'AI 媒体下载'; aiDownloaderSection.appendChild(aiDownloaderTitle); const aiAuthorCredit = document.createElement('div'); aiAuthorCredit.className = 'ai-downloader-author'; aiAuthorCredit.textContent = `功能模块作者: 醉春风 (v${AIContentDownloader && AIContentDownloader.CONFIG ? AIContentDownloader.CONFIG.VERSION : 'N/A'})`; aiDownloaderSection.appendChild(aiAuthorCredit); const aiActionsDiv = document.createElement('div'); aiActionsDiv.className = 'actions-group'; const toggleMediaPanelButton = document.createElement('button'); toggleMediaPanelButton.textContent = '显示/隐藏媒体列表'; toggleMediaPanelButton.className = 'btn-primary'; toggleMediaPanelButton.onclick = () => AIContentDownloader.toggleMediaPanel(); const manualScanButton = document.createElement('button'); manualScanButton.textContent = '手动扫描媒体'; manualScanButton.className = 'btn-info'; manualScanButton.onclick = () => AIContentDownloader.scanForMedia(); aiActionsDiv.appendChild(toggleMediaPanelButton); aiActionsDiv.appendChild(manualScanButton); aiDownloaderSection.appendChild(aiActionsDiv); scrollableContent.appendChild(aiDownloaderSection); main_uiPanel.appendChild(scrollableContent); const closeButton = document.createElement('button'); closeButton.textContent = '关闭面板'; closeButton.className = 'close-panel-btn'; closeButton.onclick = main_togglePanelVisibility; main_uiPanel.appendChild(closeButton); document.body.appendChild(main_uiPanel); main_log("UI面板已附加到body"); main_renderListsUI(); main_uiPanel.classList.toggle('panel-visible', main_panelVisible); main_log(`UI面板创建后,显示状态 (基于main_panelVisible=${main_panelVisible}):`, main_uiPanel.classList.contains('panel-visible') ? '可见' : '隐藏'); } function main_renderListsUI() { if (!main_uiPanel || !document.body.contains(main_uiPanel)) return; main_log("main_renderListsUI 调用"); const renderUlContent = (listArray, ulElementId, listType, configKey) => { const ul = main_uiPanel.querySelector('#' + ulElementId); if (!ul) return; ul.innerHTML = ''; if (listArray.length === 0) { const emptyMsgLi = document.createElement('li'); emptyMsgLi.textContent = '(列表为空)'; emptyMsgLi.className = 'panel-empty-msg'; emptyMsgLi.style.justifyContent = 'center'; emptyMsgLi.style.padding = '8px'; ul.appendChild(emptyMsgLi); return; } listArray.forEach((pattern, index) => { const li = document.createElement('li'); const patternText = document.createElement('span'); patternText.className = 'pattern-text'; patternText.textContent = pattern; const deleteBtn = document.createElement('button'); deleteBtn.innerHTML = '×'; deleteBtn.className = 'delete-btn'; deleteBtn.title = '删除此条规则'; deleteBtn.onclick = function() { if (confirm(`确定要从${listType === 'whitelist' ? '白' : '黑'}名单中删除 "${pattern}" 吗?`)) { listArray.splice(index, 1); GM_setValue(configKey, listArray.join(',')); main_renderListsUI(); } }; li.appendChild(patternText); li.appendChild(deleteBtn); ul.appendChild(li); }); }; renderUlContent(main_userWhitelist, 'main-whitelist-ul', 'whitelist', MAIN_SCRIPT_CONFIG.whitelistKey); renderUlContent(main_userBlacklist, 'main-blacklist-ul', 'black', MAIN_SCRIPT_CONFIG.blacklistKey); } function main_togglePanelVisibility() { main_log(`切换面板可见性前,当前状态 main_panelVisible: ${main_panelVisible}`); main_panelVisible = !main_panelVisible; GM_setValue(MAIN_SCRIPT_CONFIG.panelVisibleKey, main_panelVisible); main_log(`切换后,目标状态 main_panelVisible: ${main_panelVisible}, 已保存`); if (main_panelVisible) { if (!main_uiPanel || !document.body.contains(main_uiPanel)) { main_log("面板DOM不存在或未附加,调用 main_createUIPanel()"); main_createUIPanel(); } else { main_log("面板DOM已存在,设置为可见并刷新列表"); main_renderListsUI(); main_uiPanel.classList.add('panel-visible'); } } else { if (main_uiPanel) { main_log("设置为隐藏面板"); main_uiPanel.classList.remove('panel-visible'); } else { main_log("面板DOM不存在,无需隐藏操作"); } } if (main_uiPanel) main_log("面板最终classList:", main_uiPanel.classList.toString()); else main_log("面板DOM (main_uiPanel) 仍然为 null"); } function main_loadConfig() { main_log("main_loadConfig 调用"); const storedWhitelistStr = GM_getValue(MAIN_SCRIPT_CONFIG.whitelistKey); const storedBlacklistStr = GM_getValue(MAIN_SCRIPT_CONFIG.blacklistKey); main_userWhitelist = (typeof storedWhitelistStr === 'string' && storedWhitelistStr.trim() !== '') ? storedWhitelistStr.split(',').map(s => s.trim()).filter(s => s) : (typeof storedWhitelistStr === 'undefined' ? (GM_setValue(MAIN_SCRIPT_CONFIG.whitelistKey, MAIN_SCRIPT_CONFIG.defaultWhitelist.join(',')), [...MAIN_SCRIPT_CONFIG.defaultWhitelist]) : []); main_userBlacklist = (typeof storedBlacklistStr === 'string' && storedBlacklistStr.trim() !== '') ? storedBlacklistStr.split(',').map(s => s.trim()).filter(s => s) : (typeof storedBlacklistStr === 'undefined' ? (GM_setValue(MAIN_SCRIPT_CONFIG.blacklistKey, MAIN_SCRIPT_CONFIG.defaultBlacklist.join(',')), [...MAIN_SCRIPT_CONFIG.defaultBlacklist]) : []); if (main_uiPanel && main_panelVisible && document.body.contains(main_uiPanel)) { main_log("配置已加载,面板可见,刷新UI列表"); main_renderListsUI(); } } function main_urlMatchesPattern(url, pattern) { /* ... (与之前版本相同) ... */ if (!pattern || !url) return false; let currentUrl = url; let p = pattern.trim(); if (!p.includes("://")) { currentUrl = currentUrl.replace(/^https?:\/\//, ""); } if (!p.startsWith("www.") && !p.includes("://") && currentUrl.startsWith("www.")) { currentUrl = currentUrl.replace(/^www\./, "");} const escapedPattern = p.replace(/[.+?^${}()|[\]\\]/g, '\\$&'); const regexString = '^' + escapedPattern.replace(/\\\*/g, '.*') + '$'; try { return new RegExp(regexString, 'i').test(currentUrl); } catch (e) { return false; } } if (typeof GM_registerMenuCommand === 'function') { /* ... (与之前版本相同) ... */ GM_registerMenuCommand('配置 "链接新标签页" 白名单 (Prompt)', () => { const c = GM_getValue(MAIN_SCRIPT_CONFIG.whitelistKey,main_userWhitelist.join(',')); const n=prompt('白名单(启用链接新标签页功能),逗号分隔:',c); if(n!==null){GM_setValue(MAIN_SCRIPT_CONFIG.whitelistKey,n);main_loadConfig();alert('链接白名单已更新!');} }, 'L'); GM_registerMenuCommand('配置 "链接新标签页" 黑名单 (Prompt)', () => { const c = GM_getValue(MAIN_SCRIPT_CONFIG.blacklistKey,main_userBlacklist.join(',')); const n=prompt('黑名单(禁用链接新标签页功能),逗号分隔:',c); if(n!==null){GM_setValue(MAIN_SCRIPT_CONFIG.blacklistKey,n);main_loadConfig();alert('链接黑名单已更新!');} }, 'K'); GM_registerMenuCommand('显示/隐藏 超级助手面板', main_togglePanelVisibility, 'P'); } // ************************************************************************************* // * AIContentDownloader 模块 (基于 "醉春风" 的脚本,并根据新需求调整) * // ************************************************************************************* const AIContentDownloader = (function() { const CONFIG = { /* ... (AI下载模块的配置,与之前版本一致) ... */ DEBUG: false, INIT_DELAY: 2500, RETRY_DELAY: 3000, MAX_RETRIES: 2, AUTHOR: "醉春风", VERSION: "3.4" // 保留原作者信息 }; const state = { /* ... (AI下载模块的状态,与之前版本一致) ... */ platform: null, mediaItems: [], selectedItems: [], isProcessing: false, isPanelOpen: false, mediaMap: new Map(), mediaUrls: new Set(), windowLock: false, instanceId: 'manus_ai_dl_' + Math.random().toString(36).substr(2, 9), initRetries: 0, observer: null, observerScanTimeout: null }; const log = (...args) => { if (CONFIG.DEBUG) console.log(`[AI下载模块 v${CONFIG.VERSION}]`, ...args); }; const error = (...args) => { console.error(`[AI下载模块 v${CONFIG.VERSION}]`, ...args); }; const ICONS = { /* ... (与之前版本一致) ... */ image: ``, video: ``, download: ``, error: ``, }; class MediaItem { /* ... (与之前版本一致,特别是 createThumbnailElement 中的点击行为已修改为新标签打开) ... */ constructor(element, type, url = null) { /* ... */ this.id = 'media_' + Math.random().toString(36).substr(2, 9); this.element = element; this.type = type; this.url = url || (element ? (element.src || element.currentSrc) : null); this.highestQualityUrl = this.url; this.width = element ? (element.naturalWidth || element.videoWidth || 0) : 0; this.height = element ? (element.naturalHeight || 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; } createThumbnailElement() { /* ... (已修改为点击非checkbox区域时新标签打开 this.url) ... */ 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 { const placeholder = document.createElement('div'); placeholder.innerHTML = this.type === 'image' ? ICONS.image : ICONS.video; placeholder.style.cssText = 'position:absolute;top:0;left:0;width:100%;height:100%;display:flex;align-items:center;justify-content:center;background-color:#2a2a2a;'; thumbnailContainer.appendChild(placeholder); if (this.type === 'image') { const img = document.createElement('img'); img.src = this.url; img.style.cssText = 'width:100%;height:100%;object-fit:cover;position:absolute;top:0;left:0;'; img.crossOrigin = 'anonymous'; img.loading = 'lazy'; img.decoding = 'async'; img.onload = () => { this.thumbnailLoaded = true; if(placeholder.parentNode) placeholder.remove(); }; img.onerror = () => { /* 保留占位符 */ }; thumbnailContainer.appendChild(img); } else { /* video */ const video = document.createElement('video'); video.src = this.url; video.crossOrigin = 'anonymous'; video.muted = true; video.preload = 'metadata'; video.style.cssText = 'width:100%;height:100%;object-fit:cover;position:absolute;top:0;left:0;'; 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); if(placeholder.parentNode) placeholder.remove(); }); video.addEventListener('error', () => { /* 保留占位符 */}); thumbnailContainer.appendChild(video); } } catch (e) { error('创建缩略图DOM出错:', 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 (this.url) { if (typeof GM_openInTab === 'function') { GM_openInTab(this.url, { active: true, insert: true }); } else { window.open(this.url, '_blank'); } } else { UI.showError("此媒体项没有有效的URL可打开。"); } } }); return itemDiv; } toggleSelect() { /* ... (与原AI脚本一致) ... */ 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) { /* ... (与原AI脚本一致) ... */ if (!seconds || isNaN(seconds)) return '0:00'; seconds = Math.round(seconds); const m = Math.floor(seconds / 60); const s = seconds % 60; return `${m}:${s.toString().padStart(2, '0')}`; } } const UI = { /* ... (与之前版本类似,但移除了LargePreview相关方法) ... */ 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 = `
媒体内容 by ${CONFIG.AUTHOR} v${CONFIG.VERSION}
已选: 0
`; document.body.appendChild(panel); panel.querySelector('.manus-ai-preview-close').addEventListener('click', () => UI.togglePreviewPanel(false)); panel.querySelector('.manus-ai-cancel-btn').addEventListener('click', () => UI.togglePreviewPanel(false)); panel.querySelector('.manus-ai-select-all').addEventListener('click', UI.selectAll); panel.querySelector('.manus-ai-select-none').addEventListener('click', UI.selectNone); panel.querySelector('.manus-ai-select-images').addEventListener('click', () => UI.selectByType('image')); panel.querySelector('.manus-ai-select-videos').addEventListener('click', () => UI.selectByType('video')); panel.querySelector('.manus-ai-download-btn').addEventListener('click', UI.startDownload); return panel; }, showNotification: (message, duration = 2500) => { /* ... (与之前版本一致) ... */ const n = document.createElement('div'); n.className = 'manus-ai-downloader-notification'; n.textContent = message; document.body.appendChild(n); setTimeout(() => n.remove(), duration); }, showError: (message, duration = 3500) => { /* ... (与之前版本一致) ... */ const e = document.createElement('div'); e.className = 'manus-ai-downloader-error'; e.innerHTML = `${ICONS.error} ${message}`; document.body.appendChild(e); setTimeout(() => e.remove(), duration); }, togglePreviewPanel: (show = null) => { /* ... (与之前版本一致) ... */ const panel = UI.createPreviewPanel(); const isCurrentlyShown = panel.classList.contains('show'); const shouldShow = show !== null ? show : !isCurrentlyShown; if (shouldShow) { panel.classList.add('show'); state.isPanelOpen = true; UI.refreshPreviewContent(); } else { panel.classList.remove('show'); state.isPanelOpen = false; } }, refreshPreviewContent: () => { /* ... (与之前版本一致) ... */ const cc = document.querySelector('.manus-ai-preview-panel .manus-ai-preview-content'); if (!cc) return; cc.innerHTML = ''; if (state.mediaItems.length === 0) { cc.innerHTML = '
暂无媒体或尝试手动扫描
'; } else { [...state.mediaItems].sort((a, b) => b.timestamp - a.timestamp).forEach(item => cc.appendChild(item.createThumbnailElement())); } UI.updateSelectedCounter(); UI.updateDownloadButton(); }, updateSelectedCounter: () => { /* ... (与之前版本一致) ... */ const c = document.querySelector('.manus-ai-preview-counter'); if (c) c.textContent = `已选: ${state.selectedItems.length}`; }, updateDownloadButton: () => { /* ... (与之前版本一致) ... */ const b = document.querySelector('.manus-ai-download-btn'); if(!b)return; const cnt = state.selectedItems.length; b.disabled = cnt === 0; b.innerHTML = `${ICONS.download} 下载选中(${cnt})`; }, selectAll: () => { state.mediaItems.forEach(i => i.setSelected(true)); UI.updateSelectedCounter(); UI.updateDownloadButton(); }, selectNone: () => { state.mediaItems.forEach(i => i.setSelected(false)); UI.updateSelectedCounter(); UI.updateDownloadButton(); }, selectByType: (t) => { state.mediaItems.forEach(i => i.setSelected(i.type === t)); UI.updateSelectedCounter(); UI.updateDownloadButton(); }, startDownload: () => { /* ... (与之前版本一致) ... */ const itemsToDownload = state.mediaItems.filter(item => state.selectedItems.includes(item.id)); if (itemsToDownload.length === 0) { UI.showError('没有选中任何项'); return; } UI.showNotification(`开始下载 ${itemsToDownload.length} 个媒体项...`); itemsToDownload.forEach((item, index) => { setTimeout(() => { if (!item.url) { UI.showError(`媒体项 #${index+1} URL无效`); return; } const ext = item.type === 'video' ? (item.url.match(/\.(mp4|webm|mov|mkv|avi)/i)?.[1] || 'mp4') : (item.url.match(/\.(jpg|jpeg|png|gif|webp|bmp|svg)/i)?.[1] || 'jpg'); const ts = new Date().toISOString().replace(/[-:.]/g, '').replace('T','_'); const fileName = `${item.type}_${CONFIG.AUTHOR}_${ts}_${index + 1}.${ext}`; log(`准备下载: ${fileName} from ${item.url}`); if (typeof GM_download === 'function') { GM_download({ url: item.url, name: fileName, saveAs: false, onload: () => log(`${fileName} 下载完成。`), onerror: (err) => { error(`下载 ${fileName} 失败:`, err.error || err); UI.showError(`${fileName.substring(0,20)}... 下载失败`); } }); } else { const a = document.createElement('a'); a.href = item.url; a.download = fileName; document.body.appendChild(a); a.click(); document.body.removeChild(a); } }, index * 700); }); } }; const utils = { /* ... (与之前版本一致) ... */ detectPlatform: () => { const url = window.location.href.toLowerCase(); const host = window.location.hostname.toLowerCase(); if (host.includes('klingai.com') || host.includes('kuaishou.com') || host.includes('kuaishou.cn') || host.includes('app.klingai.com')) return 'keling'; if (host.includes('mjianying.com') || host.includes('dreamina.capcut.com') || host.includes('capcut.com')) return 'jimeng'; if (host.includes('googleusercontent.com')) { if (url.includes('/youtube.com/')) return 'generic_video'; return 'generic_image';} // 注意这里的 /0 变 /2,或更通用 if (document.querySelector('[class*="kl-"], [data-kl]')) return 'keling'; if (document.querySelector('[class*="jm-"], [data-jm], [class*="capcut"], [class*="jianying"]')) return 'jimeng'; const title = document.title.toLowerCase(); if (title.includes('可灵') || title.includes('kling')) return 'keling'; if (title.includes('即梦') || title.includes('jimeng') || title.includes('capcut') || title.includes('剪映')) return 'jimeng'; return null; }, isUrlProcessed: (url) => { if (!url) return true; return state.mediaUrls.has(url); }, markUrlAsProcessed: (url) => { if (url) state.mediaUrls.add(url); }, cleanupDuplicateMedia: () => { /* ... (与之前版本一致) ... */ const uniqueUrls = new Set(); const uniqueItems = []; for (let i = state.mediaItems.length - 1; i >= 0; i--) { const item = state.mediaItems[i]; if (item.url && !uniqueUrls.has(item.url)) { uniqueUrls.add(item.url); uniqueItems.push(item); }} uniqueItems.reverse(); if (state.mediaItems.length !== uniqueItems.length) { state.mediaItems = uniqueItems; log(`清理后媒体项: ${uniqueItems.length}`);} } }; const platforms = { /* ... (与之前版本一致) ... */ keling: { selectors: ['video[src]', 'img[src]', 'div[style*="background-image"]', '[class*="video"] video', '[class*="image"] img', '[class*="gallery"] img', '[class*="player"] video'] }, jimeng: { selectors: ['video[src]', 'img[src]', 'div[style*="background-image"]', '[class*="player"] video', '[class*="preview"] img', '[class*="image-render"] img', '[class*="canvas"] canvas'] }, generic_video: { selectors: ['video[src]'] }, generic_image: { selectors: ['img[src]', 'div[style*="background-image"]'] } }; function findAndProcessMedia() { /* ... (与之前版本一致) ... */ if (!state.platform && state.platform !== null) { state.platform = utils.detectPlatform() || 'generic_image'; } else if (state.platform === null) { state.platform = utils.detectPlatform() || 'generic_image';} log(`扫描媒体 (平台: ${state.platform})...`); let newMediaFound = false; const platformConfig = platforms[state.platform] || { selectors: ['video[src]','img[src]', 'div[style*="background-image"]'] }; platformConfig.selectors.forEach(selector => { try { document.querySelectorAll(selector).forEach(element => { let url = null; let type = 'image'; if (element.tagName === 'IMG') { url = element.src; type = 'image'; } else if (element.tagName === 'VIDEO') { url = element.src || element.currentSrc; type = 'video'; } else if (element.tagName === 'DIV' && selector.includes('background-image')) { const bgStyle = window.getComputedStyle(element).backgroundImage; if (bgStyle && bgStyle.startsWith('url("') && bgStyle.endsWith('")')) { url = bgStyle.slice(5, -2); type = 'image';} } else if (element.tagName === 'CANVAS' && state.platform === 'jimeng') { try { url = element.toDataURL('image/png'); type = 'image'; } catch (e) { return; } } if (!url || utils.isUrlProcessed(url)) return; if (type === 'image' && (element.naturalWidth < 50 || element.naturalHeight < 50) && !url.startsWith('data:')) return; utils.markUrlAsProcessed(url); const mediaItem = new MediaItem(null, type, url); mediaItem.width = element.naturalWidth || element.videoWidth || element.width || 0; mediaItem.height = element.naturalHeight || element.videoHeight || element.height || 0; if (type === 'video') mediaItem.duration = element.duration || 0; state.mediaItems.push(mediaItem); newMediaFound = true; }); } catch (e) { error(`选择器 ${selector} 出错:`, e); } }); if (newMediaFound) { utils.cleanupDuplicateMedia(); if (state.isPanelOpen) UI.refreshPreviewContent(); } else { /* log('本次扫描未发现新媒体。'); */ } return newMediaFound; } function setupNetworkListener() { /* ... (与之前版本一致) ... */ const processPotentialMediaUrl = (url) => { if (url && (url.match(/\.(jpe?g|png|gif|webp|mp4|webm|mov|mkv|avi|bmp|svg)/i) || url.includes('/image/') || url.includes('/video/') || url.startsWith('blob:') || url.startsWith('data:image') || url.startsWith('data:video'))) { if (utils.isUrlProcessed(url)) return; const type = (url.match(/\.(mp4|webm|mov|mkv|avi)/i) || url.includes('/video/') || url.startsWith('data:video')) ? 'video' : 'image'; utils.markUrlAsProcessed(url); const mediaItem = new MediaItem(null, type, url); state.mediaItems.push(mediaItem); utils.cleanupDuplicateMedia(); if (state.isPanelOpen) UI.refreshPreviewContent(); } }; if(window.XMLHttpRequest_ManusAiHooked) return; const origXHR = window.XMLHttpRequest; window.XMLHttpRequest = function() { const xhr = new origXHR(); xhr.addEventListener('load', function() { if(this.responseURL) processPotentialMediaUrl(this.responseURL); }); return xhr; }; window.XMLHttpRequest_ManusAiHooked = true; if(window.fetch_ManusAiHooked) return; const origFetch = window.fetch; window.fetch = function(...args) { return origFetch.apply(this, args).then(response => { if (response && response.url) processPotentialMediaUrl(response.url); return response.clone(); }).catch(err => { error("Fetch hook error:", err); throw err;}); }; window.fetch_ManusAiHooked = true; } function startObserver() { /* ... (与之前版本一致) ... */ if (state.observer) state.observer.disconnect(); state.observer = new MutationObserver((mutations) => { let p = mutations.some(m => (m.type==='childList'&&m.addedNodes.length>0) || (m.type==='attributes'&&(m.attributeName==='src'||m.attributeName==='currentSrc'||m.attributeName==='style'||m.attributeName==='href'))); if(p){clearTimeout(state.observerScanTimeout); state.observerScanTimeout = setTimeout(findAndProcessMedia,700);} }); if(document.body) state.observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['src', 'currentSrc', 'style', 'href'] }); else setTimeout(startObserver, 500); } function initializeScript() { /* ... (与之前版本一致,不再创建独立按钮) ... */ log(`尝试初始化AI下载模块 (第 ${state.initRetries + 1} 次)`); state.platform = utils.detectPlatform(); log(`AI下载平台: ${state.platform || '通用 (未特定识别)'}`); UI.createPreviewPanel(); setupNetworkListener(); const foundInitially = findAndProcessMedia(); startObserver(); if (!foundInitially && state.initRetries < CONFIG.MAX_RETRIES) { state.initRetries++; setTimeout(initializeScript, CONFIG.RETRY_DELAY); return; } log(`AI下载模块 (v${CONFIG.VERSION} by ${CONFIG.AUTHOR}) 初始化完成。`); } return { CONFIG: CONFIG, initialize: initializeScript, toggleMediaPanel: UI.togglePreviewPanel, scanForMedia: () => { log("用户手动扫描"); findAndProcessMedia(); UI.showNotification('手动扫描完成'); } }; })(); // --- 主脚本初始化和事件监听 --- main_loadConfig(); const main_ensureInitialPanelState = () => { /* ... (与之前版本相同) ... */ if (main_panelVisible) { if (!main_uiPanel || !document.body.contains(main_uiPanel)) { main_createUIPanel(); } else { main_uiPanel.classList.add('panel-visible'); main_renderListsUI(); } } }; const startAllFeatures = () => { main_ensureInitialPanelState(); AIContentDownloader.initialize(); }; if (document.readyState !== "loading") { startAllFeatures(); } else { document.addEventListener("DOMContentLoaded", startAllFeatures); } document.addEventListener('click', function(event) { /* ... (与之前版本相同,包含对面板内部点击的忽略逻辑和链接处理逻辑) ... */ if ((main_uiPanel && main_uiPanel.contains(event.target)) || (document.querySelector('.manus-ai-preview-panel') && document.querySelector('.manus-ai-preview-panel').contains(event.target))) return; const currentPageUrl = window.location.href; let scriptShouldRunLinkLogic = false; const isWhitelisted = main_userWhitelist.some(pattern => main_urlMatchesPattern(currentPageUrl, pattern)); if (isWhitelisted) { scriptShouldRunLinkLogic = true; } else { const isBlacklisted = main_userBlacklist.some(pattern => main_urlMatchesPattern(currentPageUrl, pattern)); if (isBlacklisted) { scriptShouldRunLinkLogic = false; } else { scriptShouldRunLinkLogic = false; }} if(isWhitelisted) scriptShouldRunLinkLogic = true; // 确保白名单优先 if (!scriptShouldRunLinkLogic) return; if (event.button !== 0 || event.ctrlKey || event.metaKey || event.shiftKey || event.altKey) return; let targetElement = event.target; let anchorElement = null; for (let i = 0; i < 5 && targetElement && targetElement !== document.body; i++) { if (targetElement.tagName === 'A') { anchorElement = targetElement; break; } targetElement = targetElement.parentElement; } if (anchorElement) { const href = anchorElement.href; const rawHref = anchorElement.getAttribute('href'); if (!href || (rawHref && rawHref.startsWith('#')) || href.startsWith('javascript:')) return; if (anchorElement.hasAttribute('download')) return; if (event.altKey && event.shiftKey) { return; } event.preventDefault(); event.stopPropagation(); if (typeof GM_openInTab === 'function') { GM_openInTab(href, { active: true, insert: true }); } else if (typeof window.open === 'function') { window.open(href, '_blank'); } } }, true); })();