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 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

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