Greasy Fork

来自缓存

Greasy Fork is available in English.

Lyra's Universal AI Exporter (ChromeXt兼容版)

ChromeXt优化版:支持Claude、Gemini、NotebookLM、Google AI Studio的AI对话导出工具

当前为 2025-06-24 提交的版本,查看 最新版本

// ==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
// ==/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 });
    }
})();