AI对话导出word/json/md - DeepSeek/纳米/腾讯元宝/Kimi/通义千问/讯飞星火

支持DeepSeek,纳米AI,腾讯元宝,kimi,通义千问和讯飞星火的对话导出功能,支持JSON、Markdown和word格式,拜请各位大神完善。

目前為 2025-07-10 提交的版本,檢視 最新版本

// ==UserScript==
// @name         AI对话导出word/json/md - DeepSeek/纳米/腾讯元宝/Kimi/通义千问/讯飞星火
// @namespace    http://tampermonkey.net/
// @version      2025.7.10
// @description  支持DeepSeek,纳米AI,腾讯元宝,kimi,通义千问和讯飞星火的对话导出功能,支持JSON、Markdown和word格式,拜请各位大神完善。
// @author       春秋 + deepseek + 网友
// @match        *://chat.deepseek.com/*
// @match        *://bot.n.cn/*
// @match        *://yuanbao.tencent.com/*
// @match        *://*.kimi.com/*
// @match        *://*.tongyi.com/*
// @match        *://*.xfyun.cn/*
// @grant        GM_xmlhttpRequest
// @grant        GM_addStyle
// @license       MIT


// ==/UserScript==

(function() {
    'use strict';

    // 新增Word导出API端点
    const API_ENDPOINT_WORD = 'https://api.any2card.com/api/md-to-word';
    //Kimi配置
    const comment_params = { "chat_session_id": '' };
    const headersAuthorization = '';
    let kimiTitleCache = null; // 缓存Kimi对话标题

    // 状态管理增强版
    let state = {
        targetResponse: null,
        lastUpdateTime: null,
        convertedMd: null,
        platformType: null, // 'deepseek' 或 'ncn' 或 'yuanbao' 或 'kimi' 或 'tongyi'
        messageStats: {
            totalTokens: 0,
            totalChars: 0,
            fileCount: 0,
            questions: 0,
            convTurns: 0
        },
        currentTitle: '未命名对话'
    };

    const log = {
        info: (msg) => console.log(`[AI 对话导出] ${msg}`),
        error: (msg, e) => console.error(`[AI 对话导出] ${msg}`, e)
    };

    // 多平台URL模式匹配
    const urlPatterns = {
        deepseek: /chat_session_id=/,
        ncn: /conversation\/info\?conversation_id=/,
        yuanbao: /\/api\/user\/agent\/conversation\/v1\/detail/,
        kimi: /\/api\/chat\/.+\/segment\/scroll/,
        tongyi: /\/dialog\/chat\/list/,
        iflytek: /\/iflygpt\/u\/chat_history\/all\//
    };

    // 辅助函数 - 时间戳格式化
    function formatTimestamp(unixTimestamp) {
        if (!unixTimestamp) return 'N/A';
        try {
            const dt = new Date(unixTimestamp * 1000);
            return dt.toLocaleString('sv').replace(/[T \/]/g, '_');
        } catch (e) {
            log.error('Error formatting timestamp:', e);
            return 'Invalid Date';
        }
    }

    // 辅助函数 - Markdown标题降级
    function adjustHeaderLevels(text, increaseBy = 1) {
        if (!text) return '';
        return text.replace(/^(#+)(\s*)(.*?)\s*$/gm, (match, hashes, existingSpace, content) => {
            return '#'.repeat(hashes.length + increaseBy) + ' ' + content.trim();
        });
    }

    // 核心处理函数
   async function processTargetResponse(text, url) {
        try {
            let detectedPlatform = null;
            Object.entries(urlPatterns).forEach(([platform, pattern]) => {
                if (pattern.test(url)) detectedPlatform = platform;
            });

            if (!detectedPlatform) return;

            state.targetResponse = text;
            state.platformType = detectedPlatform;
            state.lastUpdateTime = new Date().toLocaleTimeString();

            // 重置统计信息
            state.messageStats = {
                totalTokens: 0,
                totalChars: 0,
                fileCount: 0,
                questions: 0,
                convTurns: 0
            };
            const jsonData = JSON.parse(text);

            // 差异化处理
            if (detectedPlatform === 'deepseek') {
                state.convertedMd = convertDeepSeekToMd(jsonData);
                state.messageStats.totalTokens = jsonData.data.biz_data.chat_messages.reduce((acc, msg) => acc + msg.accumulated_token_usage, 0);
                state.currentTitle = jsonData.data.biz_data.chat_session.title || 'deepseek-Chat';
            } else if (detectedPlatform === 'ncn') {
                state.convertedMd = convertNcnToMd(jsonData);
                state.messageStats.totalChars = jsonData.data.messages.reduce((acc, msg) => acc + (msg.result?.length || 0), 0);
                state.messageStats.questions = jsonData.data.messages.reduce((acc, msg) => acc + (msg.ask_further?.length || 0), 0);
                state.currentTitle = jsonData.data.title || 'AI-Chat';
            } else if (detectedPlatform === 'yuanbao') {
                state.convertedMd = convertYuanbaoToMd(jsonData);
                state.messageStats.convTurns = jsonData.convs?.length || 0;
                state.currentTitle =jsonData.sessionTitle || jsonData.title || 'Yuanbao-Chat';
                if (jsonData.multiMediaInfo) {
                    state.messageStats.fileCount = jsonData.multiMediaInfo.length;
                }
            }
            else if (detectedPlatform === 'kimi') {
                // 获取会话ID
                const match = url.match(/\/api\/chat\/(.+?)\/segment\/scroll/);
                if (match && match[1]) {
                    comment_params.chat_session_id = match[1];
                    state.currentTitle = await fetchChatTitle(); // 确保先获取标题
                    //state.currentTitle = await getCurrentTitleData();
                    console.log("检查标题:",state.currentTitle);
                }

                state.convertedMd = await convertKimiToMd(jsonData);
                state.messageStats.totalChars = jsonData.items
                    .reduce((acc, item) => acc + (JSON.stringify(item.contents).length || 0), 0);
            }
            else if (detectedPlatform === 'tongyi') {
                state.convertedMd = convertTongyiToMd(jsonData);
                state.messageStats.convTurns = jsonData.data?.length || 0;
                state.messageStats.totalChars = jsonData.data?.reduce((acc, msg) => {
                    return acc + (msg.contents?.reduce((sum, content) => sum + (content.content?.length || 0), 0) || 0);
                }, 0) || 0;
                state.currentTitle = '通义千问对话';
            }
            else if (detectedPlatform === 'iflytek') {
                state.convertedMd = convertIflytekToMd(jsonData);
                state.messageStats.convTurns = jsonData.data?.[0]?.historyList?.length || 0;
                state.messageStats.totalChars = jsonData.data?.[0]?.historyList?.reduce((acc, msg) => {
                    return acc + (msg.message?.length || 0) + (msg.answer?.length || 0);
                }, 0) || 0;
                state.currentTitle = '讯飞星火对话';
            }


            updateButtonStatus();
            log.info(`成功处理${detectedPlatform.toUpperCase()}响应`);
        } catch (e) {
            log.error('响应处理错误:', e);
        }
    }

    // DeepSeek转换逻辑
    function convertDeepSeekToMd(data) {
        let mdContent = [];
        const title = data.data.biz_data.chat_session.title || '未命名对话';
        const totalTokens = data.data.biz_data.chat_messages.reduce((acc, msg) => acc + msg.accumulated_token_usage, 0);
        mdContent.push(`# DeepSeek对话 - ${title}`);
        mdContent.push(`\n> 统计信息:累计Token用量 ${totalTokens}`);

        data.data.biz_data.chat_messages.forEach(msg => {
            if (msg.role === 'USER') {
                return; // 跳过用户消息
            }
            const role = msg.role === 'USER' ? 'Human' : 'Assistant';
            mdContent.push(`### ${role}`);

            const timestamp = formatTimestamp(msg.inserted_at);
            mdContent.push(`*${timestamp}*\n`);

            if (msg.files && msg.files.length > 0) {
                msg.files.forEach(file => {
                    const insertTime = new Date(file.inserted_at * 1000).toISOString();
                    const updateTime = new Date(file.updated_at * 1000).toISOString();
                    mdContent.push(`### File Information`);
                    mdContent.push(`- Name: ${file.file_name}`);
                    mdContent.push(`- Size: ${file.file_size} bytes`);
                    mdContent.push(`- Token Usage: ${file.token_usage}`);
                    mdContent.push(`- Upload Time: ${insertTime}`);
                    mdContent.push(`- Last Update: ${updateTime}\n`);
                });
            }

            let content = msg.content;

            if (msg.search_results && msg.search_results.length > 0) {
                const citations = {};
                msg.search_results.forEach((result, index) => {
                    if (result.cite_index !== null) {
                        citations[result.cite_index] = result.url;
                    }
                });
                content = content.replace(/\[citation:(\d+)\]/g, (match, p1) => {
                    const url = citations[parseInt(p1)];
                    return url ? ` [${p1}](${url})` : match;
                });
                content = content.replace(/\s+,/g, ',').replace(/\s+\./g, '.');
            }

            if (msg.thinking_content) {
                const thinkingTime = msg.thinking_elapsed_secs ? `(${msg.thinking_elapsed_secs}s)` : '';
                content += `\n\n**Thinking Process ${thinkingTime}:**\n${msg.thinking_content}`;
            }

            content = content.replace(/\$\$(.*?)\$\$/gs, (match, formula) => {
                return formula.includes('\n') ? `\n$$\n${formula}\n$$\n` : `$$${formula}$$`;
            });

            mdContent.push(content + '\n');
        });
        return mdContent.join('\n');
    }

    // n.cn 转换逻辑
    function convertNcnToMd(data) {
        let mdContent = [];
        const meta = data.data;
        mdContent.push(`# AI对话记录 - ${meta.title}`);
        mdContent.push(`\n> 统计信息:总字数 ${state.messageStats.totalChars} | 后续问题 ${state.messageStats.questions} 个`);

        meta.messages.forEach(msg => {
           // mdContent.push(`## 用户提问\n${msg.prompt}`);
            if (msg.file?.length) {
                mdContent.push('### 附件信息');
                msg.file.forEach(file => {
                    mdContent.push(`- ${file.title} (${(file.size / 1024).toFixed(1)}KB)`);
                    state.messageStats.fileCount++;
                });
            }
            mdContent.push(`## AI回复\n${msg.result}`);
            if (msg.ask_further?.length) {
                mdContent.push('### 推荐追问');
                msg.ask_further.forEach(q => {
                    mdContent.push(`- ${q.content}`);
                });
            }
            mdContent.push('\n---\n');
        });
        return mdContent.join('\n');
    }

    // 腾讯元宝转换逻辑
    function convertYuanbaoToMd(jsonData) {
        if (!jsonData || !jsonData.convs || !Array.isArray(jsonData.convs)) {
            log.error('Invalid JSON data or missing/invalid "convs" array.');
            return '# 错误:无效的JSON数据\n\n无法解析对话内容。';
        }

        let markdownContent = '';

        // 1. 添加标题信息
        const title = jsonData.sessionTitle || jsonData.title || '元宝对话记录';
        markdownContent += `# ${title}\n\n`;

        // 2. 添加多媒体信息
        if (jsonData.multiMediaInfo && jsonData.multiMediaInfo.length > 0) {
            markdownContent += '**包含的多媒体文件:**\n';
            jsonData.multiMediaInfo.forEach(media => {
                markdownContent += `* [${media.fileName || '未知文件'}](${media.url || '#'}) (${media.type || '未知类型'})\n`;
            });
            markdownContent += '\n---\n';
        } else {
            markdownContent += '---\n';
        }

        // 3. 处理对话轮次(按index正序处理)
        const sortedConvs = [...jsonData.convs].sort((a, b) => (a.index || 0) - (b.index || 0));

        if (sortedConvs.length === 0) {
            log.info('No conversation turns found in the data.');
            return markdownContent.replace('\n---\n', '').trim() || '# 对话记录为空';
        }

        sortedConvs.forEach(turn => {
            // 跳过用户发言部分
            if (turn.speaker === 'human') {
                return; // 直接跳过本次迭代
            }
            const timestamp = formatTimestamp(turn.createTime);
            const index = turn.index !== undefined ? turn.index : 'N/A';

            if (turn.speaker === 'human') {
                markdownContent += `\n## 用户 (轮次 ${index})\n`;
                markdownContent += `*时间: ${timestamp}*\n\n`;

                let userTextMsg = ' ';
                if (turn.speechesV2 && turn.speechesV2.length > 0 && turn.speechesV2[0].content) {
                    const textBlock = turn.speechesV2[0].content.find(block => block.type === 'text');
                    if (textBlock && typeof textBlock.msg === 'string') {
                        userTextMsg = textBlock.msg;
                    } else if (typeof turn.displayPrompt === 'string') {
                        userTextMsg = turn.displayPrompt;
                    }
                } else if (typeof turn.displayPrompt === 'string') {
                    userTextMsg = turn.displayPrompt;
                }
                markdownContent += `${userTextMsg}\n`;

                // 检查用户上传的文件
                if (turn.speechesV2 && turn.speechesV2.length > 0 && turn.speechesV2[0].content) {
                    let uploadedMedia = [];
                    turn.speechesV2[0].content.forEach(block => {
                        if (block.type !== 'text' && block.fileName && block.url) {
                            uploadedMedia.push(`[${block.fileName}](${block.url}) (类型: ${block.type})`);
                        }
                    });
                    if (uploadedMedia.length > 0) {
                        markdownContent += `\n*上传了文件: ${uploadedMedia.join(', ')}*\n`;
                    }
                }

            } else if (turn.speaker === 'ai') {
                markdownContent += `\n## AI (轮次 ${index})\n`;

                let modelDisplay = '未知模型';
                let primaryPluginId = '无插件';

                if (turn.speechesV2 && turn.speechesV2.length > 0) {
                    const firstSpeech = turn.speechesV2[0];
                    const modelIdRaw = firstSpeech.chatModelId;
                    primaryPluginId = firstSpeech.pluginId || primaryPluginId;

                    if (modelIdRaw && String(modelIdRaw).trim() !== '') {
                        modelDisplay = `\`${modelIdRaw}\``;
                    }
                }

                markdownContent += `*时间: ${timestamp} | 模型: ${modelDisplay} | 插件: \`${primaryPluginId}\`*\n\n`;

                // 处理内容块
                if (turn.speechesV2 && turn.speechesV2.length > 0) {
                    turn.speechesV2.forEach(speech => {
                        if (speech.content && speech.content.length > 0) {
                            speech.content.forEach(block => {
                                switch (block.type) {
                                    case 'text':
                                        markdownContent += `${adjustHeaderLevels(block.msg || '', 1)}\n\n`;
                                        break;
                                    case 'think':
                                        markdownContent += `> **[思考过程]** ${block.title || ''}\n>\n`;
                                        (block.content || '无思考内容').split('\n').forEach(line => {
                                            markdownContent += `> ${line}\n`;
                                        });
                                        markdownContent += '\n';
                                        break;
                                    case 'searchGuid':
                                        markdownContent += `**${block.title || '搜索结果'}** (查询: \`${block.botPrompt || 'N/A'}\` | 主题: ${block.topic || 'N/A'})\n`;
                                        if (block.docs && block.docs.length > 0) {
                                            block.docs.forEach((doc, docIndex) => {
                                                markdownContent += `* [${docIndex + 1}] [${doc.title || '无标题'}](${doc.url || '#'}) (${doc.sourceName || '未知来源'})\n    * > ${doc.quote || '无引用'}\n`;
                                            });
                                        }
                                        markdownContent += '\n';
                                        break;
                                    case 'image':
                                    case 'code':
                                    case 'pdf':
                                        markdownContent += `*文件:* [${block.fileName || '未知文件'}](${block.url || '#'}) (类型: ${block.type})\n\n`;
                                        break;
                                    default:
                                        // 静默跳过未知类型
                                }
                            });
                        }
                    });
                }
            } else {
                // 其他发言者
                markdownContent += `\n## ${turn.speaker || '未知发言者'} (轮次 ${index})\n`;
                markdownContent += `*时间: ${timestamp}*\n\n`;
                markdownContent += `*无法识别的内容*\n`;
            }
            // 每轮之间的分隔符
            markdownContent += '\n---\n';
        });

        // 移除末尾多余的分隔符
        if (markdownContent.endsWith('\n---\n')) {
            markdownContent = markdownContent.slice(0, markdownContent.length - 5).trimEnd();
        }

        return markdownContent.trim();
    }

    // Kimi转换逻辑
  async function convertKimiToMd(data, titleData) {
        let mdContent = [];

        // 获取对话标题
      const title = state.currentTitle || 'Kimi对话记录';
      mdContent.push(`# ${title}\n`);

        data.items.forEach(item => {
            const role = item.role === 'user' ? 'Human' : 'Assistant';
            mdContent.push(`### ${role}`);

            const timestamp = item.created_at;
            mdContent.push(`*${timestamp}*\n`);

            // 解析内容结构
            item.contents.zones.forEach(zone => {
                zone.sections.forEach(section => {
                    // 提取思考过程
                    if (section.view === 'k1' && section.k1?.text) {
                        mdContent.push("**推理过程**\n> ");
                        mdContent.push(section.k1.text
                            .replace(/^\n+|\n+$/g, '')
                            .replace(/\n{2,}/g, '\n') + '\n');
                    }

                    // 处理正式回复
                    if (section.view === 'cmpl' && section.cmpl) {
                        let content = section.cmpl
                            .replace(/\[citation:\d+\]/g, '')// 移除引用标记
                            .replace(/\n{3,}/g, '\n\n');// 压缩多余空行

                        // 保留代码块格式
                        content = content.replace(/```([\s\S]*?)```/g, '\n```$1```\n');
                        mdContent.push(content);
                    }
                });
            });

            mdContent.push('\n---\n'); // 消息分隔线
        });

        return mdContent.join('\n');
    }

    // 获取Kimi对话标题 - 独立函数
    // 改进的XHR拦截
    const originalOpen = XMLHttpRequest.prototype.open;
    const originalSend = XMLHttpRequest.prototype.send;
    const originalSetRequestHeader = XMLHttpRequest.prototype.setRequestHeader;

/*     XMLHttpRequest.prototype.open = function(method, url) {
        this._requestURL = url;
        if (url.includes('/api/chat/') && !url.includes('/api/chat/list')) {
            console.log(`[Kimi导出器] 拦截到相关请求: ${method} ${url}`);
            this._isChatAPI = true;
        }
        return originalOpen.apply(this, arguments);
    }; */

/*     XMLHttpRequest.prototype.setRequestHeader = function(header, value) {
        if (this._isChatAPI && header.toLowerCase() === 'authorization') {
            headersAuthorization = value;
            console.log('[Kimi导出器] 获取到Authorization头');
        }
        return originalSetRequestHeader.apply(this, arguments);
    }; */

    // 改进的sessionId提取
    function getSessionId() {
        const currentUrl = window.location.href;
        // 匹配类似 /chat/abc123 或 /chat/abc123/ 的格式
        const match = currentUrl.match(/chat\/([a-zA-Z0-9]+)(?:\/|$)/);
        return match ? match[1] : null;
    }

    // 改进的标题获取
    async function fetchChatTitle() {
        if (kimiTitleCache) return kimiTitleCache;

        const sessionId = getSessionId();
        if (!sessionId) {
            console.log('[Kimi导出器] 无法获取sessionId');
            return "未获取session_Kimi对话";
        }

        // 等待获取Authorization头
        let retry = 0;
        while (!headersAuthorization && retry < 5) {
            console.log('[Kimi导出器] 等待Authorization头...');
            await new Promise(resolve => setTimeout(resolve, 500));
            retry++;
        }

        if (!headersAuthorization) {
            console.log('[Kimi导出器] 未能获取Authorization头');
            return "未获取author_Kimi对话";
        }

        try {
            const url = `https://www.kimi.com/api/chat/${sessionId}`;
            console.log(`[Kimi导出器] 请求标题API: ${url}`);

            const response = await fetch(url, {
                headers: {
                    'Authorization': headersAuthorization,
                    'Content-Type': 'application/json',
                },
                credentials: 'include'
            });

            if (!response.ok) {
                console.log(`[Kimi导出器] API错误: ${response.status}`);
                return "Kimi对话";
            }

            const data = await response.json();
            console.log('[Kimi导出器] API响应:', data);

            kimiTitleCache = data.name || "Kimi对话";
            return kimiTitleCache;
        } catch (error) {
            console.error('[Kimi导出器] 获取标题失败:', error);
            return "Kimi对话";
        }
    }

    // 通义千问转换逻辑
    function convertTongyiToMd(data) {
        let mdContent = [];
        mdContent.push(`# 通义千问对话记录`);
        mdContent.push(`\n> 统计信息:对话轮次 ${state.messageStats.convTurns} | 总字数 ${state.messageStats.totalChars}`);

        if (data.data && Array.isArray(data.data)) {
            // 按时间排序,确保对话顺序正确
            const sortedMessages = [...data.data].sort((a, b) => a.createTime - b.createTime);

            sortedMessages.forEach(msg => {
                const time = new Date(msg.createTime).toLocaleString();
                const role = msg.senderType === 'USER' ? '用户' : '助手';

                mdContent.push(`## ${role} (${time})\n`);

                if (msg.contents && Array.isArray(msg.contents)) {
                    msg.contents.forEach(content => {
                        if (content.content) {
                            // 处理换行和格式
                            let text = content.content.replace(/\n/g, '\n\n');
                            // 保留代码块格式
                            text = text.replace(/```([\s\S]*?)```/g, '\n```$1```\n');
                            mdContent.push(text);
                        }
                    });
                }

                mdContent.push('\n---\n');
            });
        }

        return mdContent.join('\n');
    }

    // 讯飞星火转换逻辑
    function convertIflytekToMd(data) {
        let mdContent = [];
        mdContent.push(`# 讯飞星火对话记录`);
        mdContent.push(`\n> 统计信息:对话轮次 ${state.messageStats.convTurns} | 总字数 ${state.messageStats.totalChars}`);

        if (data.data && data.data[0]?.historyList) {
            data.data[0].historyList.forEach(msg => {
                // 用户提问
                if (msg.type === 0) {
                    const time = new Date(msg.createTime).toLocaleString();
                    mdContent.push(`## 用户 (${time})\n\n${msg.message}\n`);
                }
                // AI回复
                else if (msg.message) {
                    const time = new Date(msg.createTime).toLocaleString();

                    // 处理来源信息
                    let sourceInfo = '';
                    if (msg.traceSource) {
                        try {
                            const sources = JSON.parse(msg.traceSource);
                            if (Array.isArray(sources)) {
                                sources.forEach(source => {
                                    if (source.type === 'searchSource' && source.data) {
                                        sourceInfo += '\n\n**参考来源**:\n';
                                        source.data.forEach(item => {
                                            sourceInfo += `- [${item.docid}](${item.source})\n`;
                                        });
                                    }
                                });
                            }
                        } catch (e) {
                            console.error('解析来源信息失败:', e);
                        }
                    }

                    mdContent.push(`## AI助手 (${time})\n\n${msg.message}${sourceInfo}\n`);
                }

                mdContent.push('---\n');
            });
        }

        return mdContent.join('\n');
    }

    // 时间戳转换为本地时间
    function getLocalTimestamp() {
        const d = new Date();
        const offset = d.getTimezoneOffset() * 60 * 1000; // 时区偏移毫秒数
        return new Date(d.getTime() - offset).toISOString()
            .slice(0, 19)
            .replace(/T/g, '_')
            .replace(/:/g, '-');
    }

    let timestamp = getLocalTimestamp();

    // 统一按钮状态更新
    function updateButtonStatus() {
        const jsonButton = document.getElementById('downloadJsonButton');
        const mdButton = document.getElementById('downloadMdButton');
        const wordButton = document.getElementById('downloadWordButton');
        if (jsonButton && mdButton) {
            const hasResponse = state.targetResponse !== null;
            let platformName = 'AI对话';
            let statsText = '';
            const jsonData = JSON.parse(state.targetResponse);

            switch(state.platformType) {
                case 'deepseek':
                    platformName = `DeepSeek_${jsonData.data.biz_data.chat_session.title || 'deepseek-Chat'}`.replace(/[\/\\?%*:|"<>]/g, '-');
                    statsText = `Token用量: ${state.messageStats.totalTokens}`;
                    break;
                case 'ncn':
                    platformName = `AI-N_${jsonData.data.title || 'AI-Chat'}`.replace(/[\/\\?%*:|"<>]/g, '-');
                    statsText = `字数: ${state.messageStats.totalChars} | 附件: ${state.messageStats.fileCount}`;
                    break;
                case 'yuanbao':
                    platformName = `Yuanbao_${jsonData.sessionTitle || jsonData.title || 'Yuanbao-Chat'}`;
                    statsText = `对话轮次: ${state.messageStats.convTurns} | 文件: ${state.messageStats.fileCount}`;
                    break;
                case 'kimi':
                    platformName = `Kimi_${state.currentTitle}`;
                    statsText = `消息数: ${state.messageStats.convTurns} | 字数: ${state.messageStats.totalChars}`;
                    break;
                case 'tongyi':
                    platformName = `Tongyi_${state.currentTitle}`;
                    statsText = `对话轮次: ${state.messageStats.convTurns} | 字数: ${state.messageStats.totalChars}`;
                    break;
                case 'iflytek':
                    platformName = `讯飞星火_${state.currentTitle}`;
                    statsText = `对话轮次: ${state.messageStats.convTurns} | 字数: ${state.messageStats.totalChars}`;
                    break;
            }

            jsonButton.style.backgroundColor = hasResponse ? '#28a745' : '#007bff';
            mdButton.style.backgroundColor = state.convertedMd ? '#28a745' : '#007bff';
            wordButton.style.backgroundColor = state.convertedMd ? '#28a745' : '#007bff';
            jsonButton.title = `${platformName}数据已就绪\n${statsText}\n最后更新: ${state.lastUpdateTime}`;
            mdButton.title = `${platformName}数据已就绪\n${statsText}\n最后更新: ${state.lastUpdateTime}`;
            wordButton.title = `${platformName}数据已就绪\n${statsText}\n最后更新: ${state.lastUpdateTime}`;
        }
        console.log(state.platformType);
        console.log("检查标题2:",state.currentTitle);
    }

    // 新增Word导出函数
    function exportToWord() {
        if (!state.convertedMd) {
            alert('还没有发现有效的对话记录。\n请等待目标响应或进行一些对话。');
            return;
        }

        const jsonData = JSON.parse(state.targetResponse);
        let chatName = '';

        if (state.platformType === 'deepseek') {
            chatName = `DeepSeek_${jsonData.data.biz_data.chat_session.title || 'deepseek-Chat'}`;
        } else if (state.platformType === 'ncn') {
            chatName = `AI-N_${jsonData.data.title || 'AI-Chat'}`;
        } else if (state.platformType === 'yuanbao') {
            chatName = `Yuanbao_${jsonData.sessionTitle || jsonData.title || 'Yuanbao-Chat'}`;
        } else if (state.platformType === 'kimi') {
            chatName = `Kimi_${state.currentTitle || 'Kimi-Chat'}`;
        }

        chatName = chatName.replace(/[\/\\?%*:|"<>]/g, '-');
        const fileName = `${chatName}_${timestamp}.docx`;

        // 显示加载状态
        const wordButton = document.getElementById('downloadWordButton');
        const originalText = wordButton.innerHTML;
        wordButton.innerHTML = '<span class="gm-spinner"></span>生成中...';
        wordButton.disabled = true;

        GM_xmlhttpRequest({
            method: "POST",
            url: API_ENDPOINT_WORD,
            headers: {
                "Content-Type": "application/json"
            },
            data: JSON.stringify({
                markdown: state.convertedMd,
                title: chatName
            }),
            responseType: 'blob',
            onload: function(response) {
                wordButton.innerHTML = originalText;
                wordButton.disabled = false;

                if (response.status >= 200 && response.status < 300) {
                    try {
                        const blob = response.response;
                        const url = URL.createObjectURL(blob);
                        const a = document.createElement('a');
                        a.style.display = 'none';
                        a.href = url;
                        a.download = fileName;
                        document.body.appendChild(a);
                        a.click();
                        document.body.removeChild(a);
                        URL.revokeObjectURL(url);
                        log.info(`成功下载Word文件: ${fileName}`);
                    } catch (e) {
                        alert('下载文件时出错: ' + e.message);
                        log.error("处理Word下载时出错:", e);
                    }
                } else {
                    const reader = new FileReader();
                    reader.onload = function() {
                        try {
                            const errorResult = JSON.parse(this.result);
                            alert(`导出失败: ${errorResult.error || '未知错误'}`);
                        } catch (e) {
                            alert(`导出失败,状态码: ${response.status}。无法解析错误信息。`);
                        }
                    };
                    reader.readAsText(response.response);
                }
            },
            onerror: function(response) {
                wordButton.innerHTML = originalText;
                wordButton.disabled = false;
                alert(`请求错误: ${response.statusText || '无法连接到服务器'}`);
            }
        });
    }


    function createDownloadButtons() {
        const buttonContainer = document.createElement('div');
        const jsonButton = document.createElement('button');
        const mdButton = document.createElement('button');
        const wordButton = document.createElement('button');

        Object.assign(buttonContainer.style, {
            position: 'fixed',
            top: '45%',
            right: '10px',
            zIndex: '9999',
            display: 'flex',
            flexDirection: 'column',
            gap: '10px',
            opacity: '0.5',
            transition: 'opacity 0.3s ease',
            cursor: 'move'
        });

        const buttonStyles = {
            padding: '8px 12px',
            backgroundColor: '#007bff',
            color: '#ffffff',
            border: 'none',
            borderRadius: '5px',
            cursor: 'pointer',
            transition: 'all 0.3s ease',
            fontFamily: 'Arial, sans-serif',
            boxShadow: '0 2px 5px rgba(0,0,0,0.2)',
            whiteSpace: 'nowrap',
            fontSize: '14px'
        };

        jsonButton.id = 'downloadJsonButton';
        jsonButton.innerText = 'JSON';
        mdButton.id = 'downloadMdButton';
        mdButton.innerText = 'MD';
        wordButton.id = 'downloadWordButton';
        wordButton.innerText = 'Word';

        Object.assign(jsonButton.style, buttonStyles);
        Object.assign(mdButton.style, buttonStyles);
        Object.assign(wordButton.style, buttonStyles);

        // 添加Word按钮图标
        wordButton.innerHTML = `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" style="vertical-align: middle; margin-right: 5px;">
            <path d="M4 4.5V19C4 20.1046 4.89543 21 6 21H18C19.1046 21 20 20.1046 20 19V8.2468C20 7.61538 19.7893 7.00372 19.4029 6.5L16.5 3H6C4.89543 3 4 3.89543 4 5V5.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path>
            <path d="M16 3V7C16 7.55228 16.4477 8 17 8H20" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path>
            <path d="M8 13L10 17L12 13L14 17L16 13" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path>
        </svg>Word`;

        buttonContainer.onmouseenter = () => buttonContainer.style.opacity = '1';
        buttonContainer.onmouseleave = () => buttonContainer.style.opacity = '0.1';

        let isDragging = false;
        let currentX,currentY;
        let initialX,initialY;
        let xOffset = 0,yOffset = 0;


     buttonContainer.addEventListener('mousedown', dragStart);
     document.addEventListener('mousemove', drag);
     document.addEventListener('mouseup', dragEnd);

        function dragStart(e) {
            initialX = e.clientX - xOffset;
            initialY = e.clientY - yOffset;
            if (e.target === buttonContainer) {
                isDragging = true;
            }
        }

        function drag(e) {
            if (isDragging) {
                e.preventDefault();
                currentX = e.clientX - initialX;
                currentY = e.clientY - initialY;
                xOffset = currentX;
                yOffset = currentY;
                setTranslate(currentX, currentY, buttonContainer);
            }
        }

        function dragEnd() {
            isDragging = false;
        }

        function setTranslate(xPos, yPos, el) {
            el.style.transform = `translate(${xPos}px, ${yPos}px)`;
            el.style.webkitTransform = `translate(${xPos}px, ${yPos}px)`; // 兼容 WebKit
        }
        // 阻止按钮的 mousedown 冒泡影响拖动
        jsonButton.addEventListener('mousedown', (e) => e.stopPropagation());
        mdButton.addEventListener('mousedown', (e) => e.stopPropagation());
        wordButton.addEventListener('mousedown', (e) => e.stopPropagation());

    jsonButton.onclick = function() {
        if (!state.targetResponse) {
            alert('还没有发现有效的对话记录。\n请等待目标响应或进行一些对话。');
            return;
        }
        try {
            const jsonData = JSON.parse(state.targetResponse);
            let chatName = '';

            if (state.platformType === 'deepseek') {
                chatName = `DeepSeek_${jsonData.data.biz_data.chat_session.title || 'deepseek-Chat'}`;
            } else if (state.platformType === 'ncn') {
                chatName = `AI-N_${jsonData.data.title || 'n-Chat'}`;
            } else if (state.platformType === 'yuanbao') {
                chatName = `Yuanbao_${jsonData.sessionTitle || jsonData.title || 'Yuanbao-Chat'}`;
            }

            chatName = chatName.replace(/[\/\\?%*:|"<>]/g, '-');
            const fileName = `${chatName}_${timestamp}.json`;

            const blob = new Blob([state.targetResponse], { type: 'application/json' });
            const link = document.createElement('a');
            link.href = URL.createObjectURL(blob);
            link.download = fileName;
            link.click();

            log.info(`成功下载文件: ${fileName}`);
        } catch (e) {
            log.error('下载过程中出错:', e);
            alert('下载过程中发生错误,请查看控制台了解详情。');
        }
    };

    mdButton.onclick = function() {
        if (!state.convertedMd) {
            alert('还没有发现有效的对话记录。\n请等待目标响应或进行一些对话。');
            return;
        }
        try {
            const jsonData = JSON.parse(state.targetResponse);
            let chatName = '';

            if (state.platformType === 'deepseek') {
                chatName = `DeepSeek_${jsonData.data.biz_data.chat_session.title || 'deepseek-Chat'}`;
            } else if (state.platformType === 'ncn') {
                chatName = `AI-N_${jsonData.data.title || 'AI-Chat'}`;
            } else if (state.platformType === 'yuanbao') {
                chatName = `Yuanbao_${jsonData.sessionTitle || jsonData.title || 'Yuanbao-Chat'}`;
            }

            chatName = chatName.replace(/[\/\\?%*:|"<>]/g, '-');
            const fileName = `${chatName}_${timestamp}.md`;

            const blob = new Blob([state.convertedMd], { type: 'text/markdown' });
            const link = document.createElement('a');
            link.href = URL.createObjectURL(blob);
            link.download = fileName;
            link.click();
            //alert(jsonData,fileName);

            log.info(`成功下载文件: ${fileName}`);
        } catch (e) {
            log.error('下载过程中出错:', e);
            alert(`下载失败: ${e.message}`);
        }
    };
         // mdButton.onclick = alert("是否正常");
        wordButton.onclick = exportToWord;

        buttonContainer.appendChild(jsonButton);
        buttonContainer.appendChild(mdButton);
        buttonContainer.appendChild(wordButton);
        document.body.appendChild(buttonContainer);

        // 添加加载动画样式
        GM_addStyle(`
            .gm-spinner {
                border: 2px solid rgba(255,255,255,0.3);
                border-radius: 50%;
                border-top-color: #fff;
                width: 12px;
                height: 12px;
                animation: spin 1s linear infinite;
                display: inline-block;
                margin-right: 8px;
                vertical-align: middle;
            }
            @keyframes spin {
                0% { transform: rotate(0deg); }
                100% { transform: rotate(360deg); }
            }
        `);

    updateButtonStatus();
}

// 网络拦截增强版
function setupNetworkInterception() {
    // 拦截XHR请求
    const originalOpen = XMLHttpRequest.prototype.open;
    XMLHttpRequest.prototype.open = function(...args) {
        // 处理DeepSeek缓存参数
        if (args[1]?.includes('history_messages?chat_session_id')) {
            args[1] = args[1].split('&cache_version=')[0];
        }
        // 处理n.cn缓存参数
        if (args[1]?.includes('conversation/info?conversation_id')) {
            args[1] = args[1].split('&cache_version=')[0];
        }
        // 处理腾讯元宝缓存参数
        if (args[1]?.includes('/api/user/agent/conversation/v1/detail')) {
            args[1] = args[1].split('&cacheBust=')[0];
        }
        //处理Kimi参数
        if (args[1]?.includes('/api/chat/') && args[1]?.includes('/segment/scroll')) {
            this._isKimiRequest = true;
        }
        // 处理通义千问参数
        if (args[1]?.includes('/dialog/chat/list')) {
            this._isTongyiRequest = true;
        }
        // 讯飞星火请求标记
        if (args[1]?.includes('/iflygpt/u/chat_history/all/')) {
            this._isIflytekRequest = true;
        }

        this._requestURL = args[1];
        this.addEventListener('load', () => {
            if (this.responseURL) {
                    if (urlPatterns.deepseek.test(this.responseURL) ||
                        urlPatterns.ncn.test(this.responseURL) ||
                        urlPatterns.yuanbao.test(this.responseURL) ||
                        urlPatterns.kimi.test(this._requestURL) ||
                        urlPatterns.tongyi.test(this._requestURL)||
                        urlPatterns.iflytek.test(this._requestURL)) {
                        processTargetResponse(this.responseText, this.responseURL);
                     //console.log(this.responseText, this.responseURL);
                }
            }
        });
        originalOpen.apply(this, args);
    };



    // 拦截Fetch请求
    const originalFetch = window.fetch;
    window.fetch = async function(...args) {
        const url = args[0] instanceof Request ? args[0].url : args[0];
        let response;
        try {
            response = await originalFetch.apply(this, args);
            if (typeof url === 'string' &&
                (urlPatterns.yuanbao.test(url) ||urlPatterns.kimi.test(url)||
                 urlPatterns.tongyi.test(url)||urlPatterns.iflytek.test(url))) {
                //alert(url);
                const contentType = response.headers.get('content-type');
                if (contentType && contentType.includes('application/json')) {
                    const clonedResponse = response.clone();

                clonedResponse.text().then(text => {
                    processTargetResponse(text, url);
                }).catch(e => {
                    log.error(`解析fetch响应文本时出错 (${url}):`, e);
                });
                }
            }
        } catch (error) {
            log.error('Fetch request failed:', error);
            throw error;
        }
        return response;
    };
}
    setupNetworkInterception();

// 初始化
window.addEventListener('load', function() {
    //setupNetworkInterception();
    createDownloadButtons();
    log.info('增强版导出脚本已启动');
});

const observer = new MutationObserver((mutationsList, observer) => {
    // 检查是否已经存在按钮
    const jsonButton = document.getElementById('downloadJsonButton');
    const mdButton = document.getElementById('downloadMdButton');
    const wordButton = document.getElementById('downloadWordButton');

    // 如果按钮不存在,才重新创建
    if (!jsonButton || !mdButton) {
        log.info('检测到按钮丢失,正在重新创建...');
        createDownloadButtons();
    }
});

observer.observe(document.body, {
   // childList: true,
    subtree: true
});
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址