// ==UserScript== // @name Lyra's Universal AI Exporter (ChromeXt兼容版) // @namespace userscript://lyra-universal-exporter-chromext // @version 0.1 // @description ChromeXt优化版:支持Claude、Gemini、NotebookLM、Google AI Studio的AI对话导出工具 // @author Yalums // @match https://pro.easychat.top/* // @match https://claude.ai/* // @match https://gemini.google.com/* // @match https://notebooklm.google.com/* // @match https://aistudio.google.com/* // @run-at document-end // @license GNU General Public License v3.0 // @downloadURL none // ==/UserScript== (function() { 'use strict'; // ChromeXt兼容性配置 const CHROMEXT_CONFIG = { INIT_DELAY: 5000, // 增加初始化延迟 RETRY_TIMES: 10, // 重试次数 RETRY_INTERVAL: 2000, // 重试间隔 USE_LEGACY_API: true, // 使用传统API DEBUG_MODE: true // 调试模式 }; function debugLog(msg) { if (CHROMEXT_CONFIG.DEBUG_MODE) { console.log(`[Lyra ChromeXt] ${msg}`); } } // 安全的样式注入(ChromeXt兼容) function injectStyleSafely(cssText) { try { // 方法1:尝试创建style标签 const style = document.createElement('style'); style.textContent = cssText; // 尝试多个注入点 const targets = [ document.head, document.body, document.documentElement ]; for (const target of targets) { if (target) { target.appendChild(style); debugLog('Style injected to: ' + target.tagName); return true; } } } catch (e) { debugLog('Style injection failed: ' + e.message); } // 方法2:如果失败,尝试内联样式 return false; } // 安全的元素创建(ChromeXt兼容) function createElementSafely(tag, properties = {}, innerHTML = '') { try { const element = document.createElement(tag); // 使用setAttribute而不是直接赋值(更兼容) for (const [key, value] of Object.entries(properties)) { if (key === 'style' && typeof value === 'object') { // 处理样式对象 for (const [styleProp, styleValue] of Object.entries(value)) { element.style[styleProp] = styleValue; } } else if (key === 'className') { element.className = value; } else { element.setAttribute(key, value); } } if (innerHTML) { element.innerHTML = innerHTML; } return element; } catch (e) { debugLog('Element creation failed: ' + e.message); return null; } } // 等待元素出现(ChromeXt优化版) function waitForElement(selector, timeout = 30000) { return new Promise((resolve) => { const startTime = Date.now(); function check() { const element = document.querySelector(selector); if (element) { debugLog(`Element found: ${selector}`); resolve(element); return; } if (Date.now() - startTime > timeout) { debugLog(`Timeout waiting for: ${selector}`); resolve(null); return; } // 使用requestAnimationFrame代替setTimeout(更可靠) if (window.requestAnimationFrame) { requestAnimationFrame(check); } else { setTimeout(check, 100); } } check(); }); } // 核心初始化函数(ChromeXt优化) async function initializeScript() { debugLog('Starting ChromeXt compatible initialization...'); // 检测平台 const hostname = window.location.hostname; let platform = ''; if (hostname.includes('easychat.top') || hostname.includes('claude.ai')) { platform = 'claude'; } else if (hostname.includes('gemini.google.com')) { platform = 'gemini'; } else if (hostname.includes('notebooklm.google.com')) { platform = 'notebooklm'; } else if (hostname.includes('aistudio.google.com')) { platform = 'aistudio'; } debugLog(`Platform detected: ${platform}`); if (!platform) { debugLog('Unsupported platform'); return; } // 等待页面基本结构加载 if (platform === 'gemini') { await waitForElement('c-wiz[jsrenderer], main[data-test-id], .chat-container, body'); } else if (platform === 'notebooklm') { await waitForElement('mat-sidenav-content, router-outlet, body'); } else if (platform === 'aistudio') { await waitForElement('ms-app, mat-sidenav-content, body'); } // 创建简化的浮动按钮(内联样式,更兼容) const button = createElementSafely('div', { id: 'lyra-chromext-button', style: { position: 'fixed', right: '20px', bottom: '80px', width: '56px', height: '56px', backgroundColor: '#1a73e8', borderRadius: '50%', display: 'flex', alignItems: 'center', justifyContent: 'center', cursor: 'pointer', boxShadow: '0 4px 12px rgba(0,0,0,0.3)', zIndex: '2147483647', color: 'white', fontSize: '24px', fontWeight: 'bold', userSelect: 'none' } }, '↓'); // 添加悬停效果(使用事件而不是CSS) button.addEventListener('mouseenter', function() { this.style.backgroundColor = '#1557b0'; this.style.transform = 'scale(1.1)'; }); button.addEventListener('mouseleave', function() { this.style.backgroundColor = '#1a73e8'; this.style.transform = 'scale(1)'; }); // 点击事件 button.addEventListener('click', function() { debugLog('Export button clicked'); // 根据平台执行不同的导出逻辑 if (platform === 'gemini') { exportGeminiChat(); } else if (platform === 'notebooklm') { exportNotebookLMChat(); } else if (platform === 'aistudio') { exportAIStudioChat(); } }); // 尝试多次添加按钮到页面 let added = false; for (let i = 0; i < CHROMEXT_CONFIG.RETRY_TIMES; i++) { if (document.body) { document.body.appendChild(button); debugLog('Button added to page'); added = true; break; } await new Promise(resolve => setTimeout(resolve, 500)); } if (!added) { debugLog('Failed to add button to page'); } // 添加提示文字 const tooltip = createElementSafely('div', { id: 'lyra-chromext-tooltip', style: { position: 'fixed', right: '80px', bottom: '90px', backgroundColor: 'rgba(0,0,0,0.8)', color: 'white', padding: '8px 12px', borderRadius: '4px', fontSize: '14px', whiteSpace: 'nowrap', display: 'none', zIndex: '2147483646' } }, 'Export Chat to JSON'); if (document.body) { document.body.appendChild(tooltip); } // 显示/隐藏提示 button.addEventListener('mouseenter', function() { tooltip.style.display = 'block'; }); button.addEventListener('mouseleave', function() { tooltip.style.display = 'none'; }); } // 简化的导出函数 function exportGeminiChat() { debugLog('Starting Gemini export...'); const messages = []; const turns = document.querySelectorAll('div.conversation-turn, div.single-turn'); turns.forEach(turn => { const userQuery = turn.querySelector('.query-text, user-query')?.innerText?.trim(); const modelResponse = turn.querySelector('.model-response-text, .markdown-content')?.innerText?.trim(); if (userQuery || modelResponse) { messages.push({ human: userQuery || '', assistant: modelResponse || '' }); } }); downloadJSON(messages, 'Gemini_Chat'); } function exportNotebookLMChat() { debugLog('Starting NotebookLM export...'); const messages = []; const turns = document.querySelectorAll('.chat-message-pair, chat-message'); turns.forEach(turn => { const userMsg = turn.querySelector('.from-user-container')?.innerText?.trim(); const botMsg = turn.querySelector('.to-user-container')?.innerText?.trim(); if (userMsg || botMsg) { messages.push({ human: userMsg || '', assistant: botMsg || '' }); } }); downloadJSON(messages, 'NotebookLM_Chat'); } function exportAIStudioChat() { debugLog('Starting AI Studio export...'); const messages = []; const turns = document.querySelectorAll('ms-chat-turn'); turns.forEach(turn => { const userTurn = turn.querySelector('.user-prompt-container')?.innerText?.trim(); const modelTurn = turn.querySelector('.model-response-container')?.innerText?.trim(); if (userTurn || modelTurn) { messages.push({ human: userTurn || '', assistant: modelTurn || '' }); } }); downloadJSON(messages, 'AIStudio_Chat'); } // 下载JSON文件 function downloadJSON(data, prefix) { try { const timestamp = new Date().toISOString().replace(/:/g, '-').slice(0, 19); const filename = `${prefix}_${timestamp}.json`; const json = JSON.stringify(data, null, 2); const blob = new Blob([json], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = filename; a.style.display = 'none'; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); debugLog(`Downloaded: ${filename}`); // 显示成功提示 showToast('Export successful!'); } catch (e) { debugLog('Download failed: ' + e.message); alert('Export failed: ' + e.message); } } // 简单的Toast提示 function showToast(message) { const toast = createElementSafely('div', { style: { position: 'fixed', bottom: '150px', right: '20px', backgroundColor: 'rgba(0,0,0,0.8)', color: 'white', padding: '12px 20px', borderRadius: '4px', zIndex: '2147483648' } }, message); if (document.body) { document.body.appendChild(toast); setTimeout(() => { toast.style.opacity = '0'; setTimeout(() => { toast.remove(); }, 300); }, 2000); } } // 启动脚本 debugLog('Script loaded, waiting for initialization...'); // 使用多种方式确保脚本执行 if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => { setTimeout(initializeScript, CHROMEXT_CONFIG.INIT_DELAY); }); } else { setTimeout(initializeScript, CHROMEXT_CONFIG.INIT_DELAY); } // 备用:监听页面变化 if (window.MutationObserver) { let initialized = false; const observer = new MutationObserver(() => { if (!initialized && document.body) { initialized = true; setTimeout(initializeScript, CHROMEXT_CONFIG.INIT_DELAY); observer.disconnect(); } }); observer.observe(document.documentElement, { childList: true, subtree: true }); } })();