您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
支持DeepSeek,纳米AI,腾讯元宝,kimi,通义千问和讯飞星火的对话导出功能,支持JSON、Markdown和word格式,豆包中的图片也能保存
// ==UserScript== // @name AI对话导出word/json/md - DeepSeek/纳米/腾讯元宝/Kimi/通义千问/讯飞星火/豆包 // @namespace http://tampermonkey.net/ // @version 2025.9.25-1 // @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/* // @match *://*.doubao.com/* // @grant GM_xmlhttpRequest // @grant GM_addStyle // @license MIT // ==/UserScript== (function() { 'use strict'; console.log('🚀 脚本开始加载', new Date().toISOString()); // 配置常量 const CONFIG = { API_ENDPOINT_WORD: 'https://api.any2card.com/api/md-to-word', DEFAULT_TITLE: '未命名对话', BUTTON_OPACITY: 0.3, BUTTON_HOVER_OPACITY: 1, SPINNER_ANIMATION_DURATION: '1s', REL_AND_KNOWLEDGE: false // 默认值为false,表示默认不导出参考链接和知识库 }; // 平台URL模式 const PLATFORM_PATTERNS = { deepseek: /chat_session_id=/, ncn: /conversation\/info\?conversation_id=/, yuanbao: /\/api\/user\/agent\/conversation\/v1\/detail/, kimi: /(\/apiv2\/kimi\.chat\.v1\.ChatService\/ListMessages|\/api\/chat\/[^\/]+\/messages|\/api\/chat|chat_session_id)/,///\/apiv2\/kimi\.chat\.v1\.ChatService\/ListMessages/ kimi_title: /\/apiv2\/kimi\.chat\.v1\.ChatService\/GetChat/,//kimi标题信息API tongyi: /\/dialog\/chat\/list/, iflytek: /\/iflygpt\/u\/chat_history\/all\//, doubao:/\/im\/chain\/single/, //过期alice\/message\/list/ doubao_title:/\/im\/conversation\/info/ // 豆包标题会话信息API }; // 状态管理 const state = { targetResponse: null, lastUpdateTime: null, convertedMd: null, platformType: null, messageStats: { totalTokens: 0, totalChars: 0, fileCount: 0, questions: 0, convTurns: 0 }, currentTitle: CONFIG.DEFAULT_TITLE, kimiSessionId: null, kimiTitleCache: null, authToken: null }; // 日志工具 const logger = { info: (msg, data) => console.log(`[AI对话导出] INFO: ${msg}`, data || ''), warn: (msg, data) => console.warn(`[AI对话导出] WARN: ${msg}`, data || ''), error: (msg, error) => console.error(`[AI对话导出] ERROR: ${msg}`, error || '') }; // 工具函数 const utils = { formatTimestamp: (timestamp) => { if (!timestamp) return 'N/A'; let dt; try { // 1. 检查是否是数字(Unix 时间戳) const ts = typeof timestamp === 'string' ? parseInt(timestamp) : timestamp; if (typeof timestamp === 'number') { // 判断是秒级还是毫秒级(通常毫秒级时间戳 >= 1e12) dt = new Date(timestamp < 1e12 ? timestamp * 1000 : timestamp); } // 2. 检查是否是字符串(ISO 8601 或类似格式) else if (typeof timestamp === 'string') { //dt = new Date(timestamp); dt = new Date(parseInt(timestamp) < 1e12 ? parseInt(timestamp) * 1000 : parseInt(timestamp)); } // 3. 其他情况(如已经是 Date 对象) else { dt = new Date(timestamp); } // 检查日期是否有效 if (isNaN(dt.getTime())) { return 'Invalid Date'; } // 使用瑞典格式 (YYYY-MM-DD HH:MM:SS) 并替换特殊字符 return dt.toLocaleString('sv').replace(/[T \/]/g, '_'); } catch (e) { logger.error('时间戳格式化错误', e); return 'Invalid Date'; } }, adjustHeaderLevels: (text, increaseBy = 1) => { if (!text) return ''; return text.replace(/^(#+)(\s*)(.*?)\s*$/gm, (match, hashes, space, content) => { return '#'.repeat(hashes.length + increaseBy) + ' ' + content.trim(); }); }, 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, '-'); }, sanitizeFilename: (name) => { return name.replace(/[\/\\?%*:|"<>]/g, '-'); }, createBlobDownload: (content, type, filename) => { try { const blob = new Blob([content], { type }); 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(); setTimeout(() => { document.body.removeChild(a); URL.revokeObjectURL(url); }, 100); return true; } catch (e) { logger.error('创建Blob下载失败', e); return false; } } }; // 平台特定的转换器 const platformConverters = { deepseek: (data) => { let mdContent = []; // 1. 安全的数据访问 const bizData = data?.data?.biz_data || {}; const chatSession = bizData.chat_session || {}; const chatMessages = bizData.chat_messages || []; const title = chatSession.title || CONFIG.DEFAULT_TITLE; const totalTokens = chatMessages.reduce((acc, msg) => acc + (msg.accumulated_token_usage || 0), 0); // 2. 标题和统计信息 mdContent.push(`# DeepSeek对话 - ${title}`); mdContent.push(`\n> 统计信息:累计Token用量 ${totalTokens}`); mdContent.push(`> 生成时间:${new Date().toLocaleString()}\n`); // 3. 处理消息 chatMessages.forEach((msg,index) => { if (msg.role === 'USER') return; // 跳过用户消息 const role = msg.role === 'ASSISTANT' ? 'Assistant' : 'Human'; const timestamp = utils.formatTimestamp(msg.inserted_at); mdContent.push(`## ${role}`); mdContent.push(`*${timestamp}*\n`); // 4. 处理文件信息 if (msg.files?.length > 0) { mdContent.push('### 文件信息'); msg.files.forEach(file => { mdContent.push(`- 名称: ${file.file_name}`); mdContent.push(`- 大小: ${(file.file_size / 1024).toFixed(1)}KB`); mdContent.push(`- Token用量: ${file.token_usage}`); }); mdContent.push(''); } // 5. 处理消息片段(思考过程和回复内容) let content = ''; let thinkingContent = ''; if (msg.fragments?.length > 0) { const citations = {}; msg.fragments.forEach(fragment => { if (fragment.cite_index !== null) { citations[fragment.cite_index] = fragment.url; } switch (fragment.type) { case 'THINK': thinkingContent += fragment.content + '\n'; break; case 'RESPONSE': content += fragment.content + '\n'; break; case 'TIP': content += `\n> 💡 ${fragment.content}\n`; break; case 'REQUEST': content += fragment.content + '\n'; break; } }); 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, '.'); } // 6. 添加思考过程(如果存在) if (thinkingContent.trim()) { const thinkFragment = msg.fragments.find(f => f.type === 'THINK'); const thinkTime = thinkFragment?.elapsed_secs ? `(${thinkFragment.elapsed_secs.toFixed(1)}s)` : ''; content += `\n**思考过程 ${thinkTime}:**\n${thinkingContent.trim()}\n`; } // 7. 数学公式格式化 - 智能处理 content = content.replace(/(\\\([\s\S]*?\\\)|\\\[[\s\S]*?\\\]|\$\$.*?\$\$)/g, (match) => { // 提取公式内容(移除各种边界符号) let formula = match.replace(/^\\\(|\\\)$|^\\\[|\\\]$|\$\$/g, ''); // 转换为单行 formula = formula.replace(/\s+/g, ' ').trim(); // 返回统一的 $...$ 格式 return `$${formula}$`; }); mdContent.push(content.trim() + '\n'); mdContent.push('---\n'); }); return mdContent.join('\n'); }, ncn: (data) => { let mdContent = []; const meta = data.data || {}; mdContent.push(`# 纳米AI对话记录 - ${meta.title || CONFIG.DEFAULT_TITLE}`); mdContent.push(`**生成时间**: ${utils.formatTimestamp(data.timestamp) || '未知'}`); const totalChars = meta.messages?.reduce((acc, msg) => acc + (msg.result?.length || 0), 0) || 0; const questions = meta.messages?.reduce((acc, msg) => acc + (msg.ask_further?.length || 0), 0) || 0; mdContent.push(`\n> 统计信息:总字数 ${totalChars} | 后续问题 ${questions} 个`); meta.messages?.forEach(msg => { if (msg.file?.length) { mdContent.push('### 附件信息'); msg.file.forEach(file => { mdContent.push(`- ${file.title} (${(file.size / 1024).toFixed(1)}KB)`); }); } // 用户提问和AI回复 mdContent.push(`\n## 用户提问\n${msg.prompt || '无内容'}`); // 处理AI回复内容,移除引用标记 let cleanResult = msg.result || '无内容'; // 移除类似[[3]()][[7]()]的引用标记 cleanResult = cleanResult.replace(/\[\[\d+\]\(\)\]/g, ''); // 移除多余的换行和空格 cleanResult = cleanResult.replace(/\n{3,}/g, '\n').trim(); mdContent.push(`\n## AI回复\n${cleanResult}`); //mdContent.push(`## AI回复\n${msg.result || '无内容'}`); // 推荐追问 if (msg.ask_further?.length) { mdContent.push('### 推荐追问'); msg.ask_further.forEach(q => { mdContent.push(`- ${q.content}`); }); } //是否需要导出,通过全局配置进行选择 if(CONFIG.REL_AND_KNOWLEDGE){ // 参考链接 if (msg.refer_search?.length) { mdContent.push('\n### 参考来源'); msg.refer_search.forEach(ref => { mdContent.push(`- [${ref.title || '无标题'}](${ref.url}) - ${ref.summary || '无摘要'}`); if (ref.date) mdContent.push(` - 发布日期: ${ref.date}`); if (ref.site) mdContent.push(` - 来源网站: ${ref.site}`); }); } // 用户知识库引用 if (msg.user_knowledge?.length) { mdContent.push('\n### 知识库引用'); const uniqueFiles = new Map(); msg.user_knowledge.forEach(knowledge => { if (knowledge.file_name && !uniqueFiles.has(knowledge.file_id)) { uniqueFiles.set(knowledge.file_id, knowledge); mdContent.push(`- ${knowledge.file_name} (来自文件夹: ${knowledge.folder_id || '未知'})`); } }); } } mdContent.push('\n---\n'); }); return mdContent.join('\n'); }, yuanbao: (data) => { if (!data?.convs || !Array.isArray(data.convs)) { logger.error('无效的元宝数据', data); return '# 错误:无效的JSON数据\n\n无法解析对话内容。'; } let markdownContent = []; const title = data.sessionTitle || data.title || '元宝对话记录'; markdownContent.push(`# ${title}\n`); if (data.multiMediaInfo?.length > 0) { markdownContent.push('**包含的多媒体文件:**\n'); data.multiMediaInfo.forEach(media => { markdownContent.push(`* [${media.fileName || '未知文件'}](${media.url || '#'}) (${media.type || '未知类型'})\n`); }); markdownContent.push('---\n'); } const sortedConvs = [...data.convs].sort((a, b) => (a.index || 0) - (b.index || 0)); sortedConvs.forEach(turn => { if (turn.speaker === 'human') return; const timestamp = utils.formatTimestamp(turn.createTime); const index = turn.index !== undefined ? turn.index : 'N/A'; if (turn.speaker === 'ai') { markdownContent.push(`\n## AI (轮次 ${index})\n`); let modelDisplay = '未知模型'; let primaryPluginId = '无插件'; if (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.push(`*时间: ${timestamp} | 模型: ${modelDisplay} | 插件: \`${primaryPluginId}\`*\n\n`); turn.speechesV2?.forEach(speech => { speech.content?.forEach(block => { switch (block.type) { case 'text': markdownContent.push(`${utils.adjustHeaderLevels(block.msg || '', 1)}\n\n`); break; case 'think': markdownContent.push(`> **[思考过程]** ${block.title || ''}\n>\n`); (block.content || '无思考内容').split('\n').forEach(line => { markdownContent.push(`> ${line}\n`); }); markdownContent.push('\n'); break; case 'searchGuid': markdownContent.push(`**${block.title || '搜索结果'}** (查询: \`${block.botPrompt || 'N/A'}\` | 主题: ${block.topic || 'N/A'})\n`); block.docs?.forEach((doc, docIndex) => { markdownContent.push(`* [${docIndex + 1}] [${doc.title || '无标题'}](${doc.url || '#'}) (${doc.sourceName || '未知来源'})\n * > ${doc.quote || '无引用'}\n`); }); markdownContent.push('\n'); break; case 'image': case 'code': case 'pdf': markdownContent.push(`*文件:* [${block.fileName || '未知文件'}](${block.url || '#'}) (类型: ${block.type})\n\n`); break; } }); }); } markdownContent.push('\n---\n'); }); return markdownContent.join('\n').replace(/\n---\n$/, '').trim(); }, kimi: async (data) => { let mdContent = []; const title = await fetchKimiChatTitle() || 'Kimi对话记录'; //const title = state.currentTitle || 'Kimi对话记录'; mdContent.push(`# ${title}\n`); // 处理消息数组 data.messages?.forEach((message, index) => { const role = message.role === 'user' ? '👤 用户' : '🤖 Kimi'; const timestamp = utils.formatTimestamp(message.createTime); mdContent.push(`## ${role} - ${timestamp}\n`); // 处理blocks中的内容 message.blocks?.forEach(block => { if (block.text?.content) { // 文本内容 mdContent.push(block.text.content + '\n'); } else if (block.artifact) { // 特殊内容(如PPT、文件等) if (block.artifact.type === 'ARTIFACT_TYPE_SLIDES_JSON') { mdContent.push('### 📊 生成的PPT内容\n'); try { const slidesData = JSON.parse(block.artifact.content); mdContent.push(`**标题:** ${slidesData.outline?.pptTitle || '无标题'}\n`); mdContent.push(`**页数:** ${slidesData.outline?.slidesCount || 0}\n`); // 可以进一步解析PPT内容 } catch (e) { mdContent.push('*[PPT内容解析失败]*\n'); } } } else if (block.slidesView) { // 幻灯片视图信息 mdContent.push(`### 🎨 幻灯片视图\n`); mdContent.push(`- 名称: ${block.slidesView.name}\n`); mdContent.push(`- 状态: ${block.slidesView.status}\n`); if (block.slidesView.coverUrl) { mdContent.push(`- 封面: \n`); } } }); mdContent.push('\n---\n'); }); return mdContent.join('\n'); }, tongyi: (data) => { let mdContent = []; mdContent.push(`# 通义千问对话记录`); const convTurns = data.data?.length || 0; const totalChars = data.data?.reduce((acc, msg) => { return acc + (msg.contents?.reduce((sum, content) => { if (content.contentType === 'text' && content.content) { return sum + content.content.length; } else if (content.contentType === 'plugin' && content.content) { // 计算插件内容中的文本长度 try { const pluginData = JSON.parse(content.content); if (pluginData.pluginCall) { const callData = JSON.parse(pluginData.pluginCall); return sum + (callData.prompt?.length || 0); } } catch (e) { return sum; } } return sum; }, 0) || 0); }, 0) || 0; mdContent.push(`\n> 统计信息:对话轮次 ${convTurns} | 总字数 ${totalChars}`); if (data.data?.length) { const sortedMessages = [...data.data].sort((a, b) => a.createTime - b.createTime); sortedMessages.forEach(msg => { const time = utils.formatTimestamp ? utils.formatTimestamp(msg.createTime) : new Date(msg.createTime).toLocaleString(); const role = msg.senderType === 'USER' ? '用户' : '助手'; mdContent.push(`## ${role} (${time})\n`); msg.contents?.forEach(content => { if (content.content) { if (content.contentType === 'text') { // 处理普通文本 let text = content.content.replace(/\n/g, '\n\n'); text = text.replace(/```([\s\S]*?)```/g, '\n```$1```\n'); mdContent.push(text); } else if (content.contentType === 'plugin') { // 处理插件内容(文生图等) try { const pluginData = JSON.parse(content.content); mdContent.push('### 插件调用信息'); if (pluginData.pluginCall) { const callData = JSON.parse(pluginData.pluginCall); mdContent.push(`**功能:** ${callData.toolName || '未知'}`); mdContent.push(`**提示词:** ${callData.prompt || '无'}`); mdContent.push(`**尺寸:** ${callData.size || '未指定'}`); } if (pluginData.pluginResult?.taskResult?.wanx_text_to_image?.imageList) { const images = pluginData.pluginResult.taskResult.wanx_text_to_image.imageList; mdContent.push('### 生成的图片'); images.forEach((img, index) => { mdContent.push(`**图片 ${index + 1}:**`); mdContent.push(`- 分辨率:${img.resolution}`); mdContent.push(`- 状态:${img.isSecurity ? '安全' : '待审核'}`); mdContent.push(`- 预览:`); mdContent.push(`- 原图:[点击下载](${img.url})`); }); } if (pluginData.pluginResult?.status) { mdContent.push(`**任务状态:** ${pluginData.pluginResult.status}`); } } catch (e) { mdContent.push('`[插件内容解析错误]`'); } } } }); mdContent.push('\n---\n'); }); } else { mdContent.push('\n暂无对话内容'); } return mdContent.join('\n'); }, iflytek: (data) => { let mdContent = []; mdContent.push(`# 讯飞星火对话记录`); const convTurns = data.data?.[0]?.historyList?.length || 0; const totalChars = data.data?.[0]?.historyList?.reduce((acc, msg) => { return acc + (msg.message?.length || 0) + (msg.answer?.length || 0); }, 0) || 0; mdContent.push(`\n> 统计信息:对话轮次 ${convTurns} | 总字数 ${totalChars}`); data.data?.[0]?.historyList?.forEach(msg => { if (msg.type === 0) { const time = utils.formatTimestamp(msg.createTime); mdContent.push(`## 用户 (${time})\n\n${msg.message}\n`); } else if (msg.message) { const time = utils.formatTimestamp(msg.createTime); 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) { logger.error('解析来源信息失败', e); } } mdContent.push(`## AI助手 (${time})\n\n${msg.message}${sourceInfo}\n`); } mdContent.push('---\n'); }); return mdContent.join('\n'); }, // 豆包消息转换 doubao: (data) => { let mdContent = []; //const title = data.downlink_body?.get_conv_info_downlink_body?.conversation_info?.name||'豆包对话记录'; const title = state.currentTitle || '豆包对话记录'; // 获取正确的消息数组 const messages = data.downlink_body?.pull_singe_chain_downlink_body?.messages || []; mdContent.push(`# ${title}\n`); // 统计信息 const totalMessages = messages.length; let totalChars = 0; // 处理消息内容 - 按时间正序排列 const sortedMessages = [...messages].sort((a, b) => a.create_time - b.create_time); sortedMessages.forEach(msg => { const time = utils.formatTimestamp(msg.create_time); const role = msg.user_type === 1 ? '用户' : '豆包AI'; mdContent.push(`## ${role} (${time})`); // 处理消息内容 let content = ''; let messageChars = 0; try { if (msg.content_type === 1) { // 用户文本消息 const contentObj = typeof msg.content === 'string' ? JSON.parse(msg.content || '{}') : msg.content; content = contentObj.text || JSON.stringify(contentObj) || ''; messageChars = content.length; } else if (msg.content_type === 9999) { // AI复合消息 // 方法1: 解析content字段(JSON数组) if (msg.content) { const contentArray = typeof msg.content === 'string' ? JSON.parse(msg.content || '[]') : msg.content; if (Array.isArray(contentArray)) { contentArray.forEach(item => { if (item.block_type === 10000 && item.content?.text_block?.text) { // 文本块 content += item.content.text_block.text + '\n\n'; messageChars += item.content.text_block.text.length; } else if (item.block_type === 2074 && item.content?.creation_block) { // 图片块 const mediaContent = platformConverters.processDoubaoMediaContent(item); content += mediaContent + '\n\n'; } }); } } // 方法2: 使用content_block(如果存在) if (!content.trim() && msg.content_block?.length) { msg.content_block.forEach(block => { if (block.block_type === 10000 && block.content?.text_block?.text) { content += block.content.text_block.text + '\n\n'; messageChars += block.content.text_block.text.length; } else if (block.block_type === 2074) { const mediaContent = platformConverters.processDoubaoMediaContent(block); content += mediaContent + '\n\n'; } }); } } totalChars += messageChars; } catch (e) { console.error('解析消息内容失败', e); content = `解析错误: ${e.message}`; } // 清理内容格式 content = content.replace(/```([\s\S]*?)```/g, '\n```$1```\n') .replace(/\n{3,}/g, '\n\n') .trim(); mdContent.push(content || '(无内容)'); mdContent.push('\n---\n'); }); // 插入统计信息到合适位置 if (mdContent.length > 1) { mdContent.splice(1, 0, `> 统计信息:消息数 ${totalMessages} | 总字数 ${totalChars}\n`); } return mdContent.join('\n'); }, // 处理豆包的媒体内容 - 修正版本 processDoubaoMediaContent: (blockOrMsg) => { let content = []; try { let contentObj; if (blockOrMsg.content && typeof blockOrMsg.content === 'string') { contentObj = JSON.parse(blockOrMsg.content); } else { contentObj = blockOrMsg.content || {}; } // 从creation_block或直接获取creations const creations = contentObj.creation_block?.creations || contentObj.creations || []; creations.forEach(creation => { if (creation.type === 1 && creation.image) { const img = creation.image; // 获取图片URL(按优先级) const imageUrl = img.image_ori_raw?.url || img.image_raw?.url || img.image_ori?.url || img.image_preview?.url; if (imageUrl) { content.push(``); // 添加生成信息 if (img.gen_params?.prompt) { content.push(`**提示词:** ${img.gen_params.prompt}`); } } } }); } catch (e) { console.error('处理媒体内容失败', e); content.push('*[图片内容解析失败]*'); } return content.join('\n\n'); } , // 豆包聊天列表转换器 doubao_chat: (data) => { let mdContent = []; mdContent.push(`# 豆包对话列表\n`); // 统计信息 const totalChats = data.data?.length || 0; mdContent.push(`> 统计信息:共 ${totalChats} 个对话\n`); // 处理对话列表 data.data?.forEach(chat => { const time = utils.formatTimestamp(chat.create_time); mdContent.push(`## ${chat.title || '未命名对话'} (${time})`); mdContent.push(`- 对话ID: ${chat.conversation_id}`); mdContent.push(`- 最后更新时间: ${new Date(chat.update_time * 1000).toLocaleString()}`); mdContent.push(`- 消息数: ${chat.message_count || 0}`); mdContent.push('\n---\n'); }); return mdContent.join('\n'); } }; // 核心处理函数 async function processTargetResponse(text, url) { try { //console.log('=== 开始处理响应 ==='); //console.log('URL:', url); //console.log('响应长度:', text.length); let detectedPlatform = null; for (const [platform, pattern] of Object.entries(PLATFORM_PATTERNS)) { if (pattern.test(url)) { detectedPlatform = platform; break; } } //console.log(detectedPlatform); if (!detectedPlatform) { console.log('未匹配到任何平台,退出处理'); return; } /* if (detectedPlatform==='kimi') { text = await fetchkimiMessage(); } */ 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); const match = url.match(/(\/apiv2\/kimi\.chat\.v1\.ChatService\/ListMessages|\/api\/chat\/[^\/]+\/messages|\/api\/chat|chat_session_id)/);//Kimi标题 // 平台特定处理 switch(detectedPlatform) { case 'deepseek': state.convertedMd = platformConverters.deepseek(jsonData); state.messageStats.totalTokens = jsonData.data?.biz_data?.chat_messages?.reduce( (acc, msg) => acc + (msg.accumulated_token_usage || 0), 0) || 0; state.currentTitle = jsonData.data?.biz_data?.chat_session?.title || 'deepseek-Chat'; break; case 'ncn': state.convertedMd = platformConverters.ncn(jsonData); state.messageStats.totalChars = jsonData.data?.messages?.reduce( (acc, msg) => acc + (msg.result?.length || 0), 0) || 0; state.messageStats.questions = jsonData.data?.messages?.reduce( (acc, msg) => acc + (msg.ask_further?.length || 0), 0) || 0; state.currentTitle = jsonData.data?.title || 'AI-Chat'; break; case 'yuanbao': state.convertedMd = platformConverters.yuanbao(jsonData); state.messageStats.convTurns = jsonData.convs?.length || 0; state.currentTitle = jsonData.sessionTitle || jsonData.title || 'Yuanbao-Chat'; state.messageStats.fileCount = jsonData.multiMediaInfo?.length || 0; break; case 'kimi': case 'kimi_title': if (match?.[1]) { state.kimiSessionId = match[1]; state.currentTitle = await fetchKimiChatTitle(); //console.log('测试Kimi_case'); } //console.log('测试Kimicase_title'); // 如果是标题API,只更新标题不处理内容 if (state.platformType === 'kimi_title') { state.currentTitle = jsonData.chat?.name || 'kimi对话'; logger.info(`更新kimi标题: ${state.currentTitle}`); return; // 标题API不需要处理消息内容 } state.convertedMd = await platformConverters.kimi(jsonData); state.messageStats.totalChars = jsonData.messages?.reduce( (acc, item) => acc + (JSON.stringify(item.contents).length || 0), 0) || 0; break; case 'tongyi': state.convertedMd = platformConverters.tongyi(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 = '通义千问对话'; break; case 'iflytek': state.convertedMd = platformConverters.iflytek(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 = '讯飞星火对话'; break; case 'doubao': case 'doubao_title': // 如果是标题API,只更新标题不处理内容 if (state.platformType === 'doubao_title') { state.currentTitle = jsonData.downlink_body?.get_conv_info_downlink_body?.conversation_info?.name || '豆包对话'; logger.info(`更新豆包标题: ${state.currentTitle}`); return; // 标题API不需要处理消息内容 } // 正常处理对话内容 state.convertedMd = platformConverters.doubao(jsonData); state.messageStats.totalChars = jsonData.downlink_body?.pull_singe_chain_downlink_body?.messages?.reduce((acc, msg) => { try { const contentObj = JSON.parse(msg.content || '{}'); return acc + (contentObj.text?.length || 0); } catch { return acc + (msg.content?.length || 0); } }, 0) || 0; state.messageStats.convTurns = jsonData.downlink_body?.pull_singe_chain_downlink_body?.messages?.length || 0; // 如果当前标题还是默认值,尝试从对话数据中获取 if (state.currentTitle === CONFIG.DEFAULT_TITLE || state.currentTitle === '豆包对话') { state.currentTitle = jsonData.downlink_body?.get_conv_info_downlink_body?.conversation_info?.name || '豆包对话'; } break; } ui.updateButtonStatus(); logger.info(`成功处理${detectedPlatform.toUpperCase()}响应`); } catch (e) { logger.error('响应处理错误', e); } } // Kimi相关功能 // sessionId提取 function getKimiSessionId() { const currentUrl = window.location.href; // 匹配多种可能的URL格式 const match = currentUrl.match(/(?:chat|chat_session)\/([a-zA-Z0-9-]+)(?:\/|$)/); return match ? match[1] : null; } // 拦截XHR请求获取授权令牌 function setupAuthInterceptor() { const originalOpen = XMLHttpRequest.prototype.open; const originalSetRequestHeader = XMLHttpRequest.prototype.setRequestHeader; XMLHttpRequest.prototype.open = function(method, url) { this._requestURL = url; if (url.includes('/api/chat/') && !url.includes('/api/chat/list')) { logger.info(`拦截到Kimi API请求: ${method} ${url}`); this._isChatAPI = true; } return originalOpen.apply(this, arguments); }; XMLHttpRequest.prototype.setRequestHeader = function(header, value) { if (this._isChatAPI && header.toLowerCase() === 'authorization') { state.authToken = value; logger.info('获取到Authorization头'); } return originalSetRequestHeader.apply(this, arguments); }; } // 获取Kimi对话标题 async function fetchKimiChatTitle() { if (state.kimiTitleCache) return state.kimiTitleCache; //console.log('Kimi标题获取测试'); state.kimiSessionId = getKimiSessionId(); if (!state.kimiSessionId) { logger.warn('无法获取sessionId'); return "Kimi对话"; } // 等待获取授权令牌 let retry = 0; while (!state.authToken && retry < 5) { logger.info('等待获取授权令牌...'); await new Promise(resolve => setTimeout(resolve, 500)); retry++; } if (!state.authToken) { logger.warn('未能获取授权令牌'); return "Kimi对话"; } try { const url = `https://www.kimi.com/api/chat/${state.kimiSessionId}`; logger.info(`请求标题API: ${url}`); const response = await fetch(url, { headers: { 'Authorization': state.authToken, 'Content-Type': 'application/json', }, credentials: 'include' }); if (!response.ok) { logger.warn(`API错误: ${response.status}`); return "Kimi对话"; } const data = await response.json(); logger.info('标题_API响应类型:', typeof data); logger.info('API响应:', data); //const kimidata =await fetchkimiMessage(); //console.log('标题里面输出对话:',kimidata); state.kimiTitleCache = data.name || "Kimi对话"; return state.kimiTitleCache; } catch (error) { logger.error('获取标题失败:', error); return "Kimi对话"; } } // 获取Kimi对话信息 async function fetchkimiMessage() { if (state.kimiTitleCache) return state.kimiTitleCache; state.kimiSessionId = getKimiSessionId(); if (!state.kimiSessionId) { logger.warn('无法获取sessionId'); return "Kimi信息"; } // 等待获取授权令牌 let retry = 0; while (!state.authToken && retry < 5) { logger.info('等待获取授权令牌...'); await new Promise(resolve => setTimeout(resolve, 500)); retry++; } if (!state.authToken) { logger.warn('未能获取授权令牌'); return "Kimi信息"; } try { const url = `https://www.kimi.com/apiv2/kimi.chat.v1.ChatService/ListMessages`; logger.info(`请求信息API: ${url}`); // 修正:使用POST方法并添加请求体 const requestBody = { chat_id: state.kimiSessionId, // 需要传递chatId参数 // 可能还需要其他参数,如: // limit: 100, // cursor: "" }; const response = await fetch(url, { method: 'POST', // 改为POST方法 headers: { 'Authorization': state.authToken, 'Content-Type': 'application/json', }, body: JSON.stringify(requestBody), // 添加请求体 mode: 'cors', referrer: window.location.href, // 使用当前页面作为referrer credentials: 'include' }); if (!response.ok) { logger.warn(`API错误: ${response.status}`); return "Kimi信息"; } // 检查Content-Type确认是JSON const contentType = response.headers.get('content-type'); if (!contentType || !contentType.includes('application/json')) { const rawText = await response.text(); console.warn('响应不是JSON:', rawText.substring(0, 200)); return null; } const data = await response.json(); //const data = JSON.stringify(restext, null, 2) logger.info('Kimi_API响应类型:', typeof data); logger.info('kimi_API响应:', data); //console.log('kimi_API响应:', JSON.stringify(data, null, 2)); //state.kimiTitleCache = data.name || "Kimi对话"; return data; } catch (error) { logger.error('获取kimi信息失败:', error); return "Kimi信息"; } } // 导出功能 // 提取获取文件名的公共逻辑 function getExportFilename(extension = '') { const jsonData = JSON.parse(state.targetResponse || '{}'); let platformPrefix = ''; let defaultSuffix = 'Chat'; switch(state.platformType) { case 'deepseek': platformPrefix = 'DeepSeek'; defaultSuffix = 'deepseek-Chat'; break; case 'ncn': platformPrefix = 'AI-N'; defaultSuffix = 'AI-Chat'; break; case 'yuanbao': platformPrefix = 'Yuanbao'; defaultSuffix = 'Yuanbao-Chat'; break; case 'kimi': platformPrefix = 'Kimi'; defaultSuffix = 'Kimi-Chat'; break; case 'tongyi': platformPrefix = 'Tongyi'; defaultSuffix = 'Tongyi-Chat'; break; case 'iflytek': platformPrefix = 'Iflytek'; defaultSuffix = 'Iflytek-Chat'; break; case 'doubao': case 'doubao_title': platformPrefix = 'Doubao'; defaultSuffix = state.platformType === 'doubao_chat' ? 'ChatList' : 'Chat'; break; default: platformPrefix = 'AI'; defaultSuffix = 'Chat'; } const title = state.currentTitle || jsonData.data?.biz_data?.chat_session?.title || jsonData.data?.title || jsonData.sessionTitle ||jsonData.downlink_body?.get_conv_info_downlink_body?.conversation_info?.name||'豆包对话'||(state.platformType === 'doubao_chat' ? '豆包列表' : '豆包其他')|| defaultSuffix; const sanitizedTitle = utils.sanitizeFilename(`${platformPrefix}_${title}`); return `${sanitizedTitle}_${utils.getLocalTimestamp()}${extension ? '.' + extension : ''}`; } const exportHandlers = { json: () => { if (!state.targetResponse) { alert('还没有发现有效的对话记录。\n请等待目标响应或进行一些对话。'); return false; } try { const fileName = getExportFilename('json'); return utils.createBlobDownload( state.targetResponse, 'application/json', fileName ); } catch (e) { logger.error('JSON导出失败', e); alert('导出过程中发生错误,请查看控制台了解详情。'); return false; } }, markdown: () => { if (!state.convertedMd) { alert('还没有发现有效的对话记录。\n请等待目标响应或进行一些对话。'); return false; } try { const fileName = getExportFilename('md'); return utils.createBlobDownload( state.convertedMd, 'text/markdown', fileName ); } catch (e) { logger.error('Markdown导出失败', e); alert(`导出失败: ${e.message}`); return false; } }, word: () => { if (!state.convertedMd) { alert('还没有发现有效的对话记录。\n请等待目标响应或进行一些对话。'); return; } try { const fileName = getExportFilename('docx'); const chatName = fileName.replace(/_[\d_-]+\.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: CONFIG.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(); setTimeout(() => { document.body.removeChild(a); URL.revokeObjectURL(url); }, 100); logger.info(`成功下载Word文件: ${fileName}`); } catch (e) { alert('下载文件时出错: ' + e.message); logger.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 || '无法连接到服务器'}`); } }); } catch (e) { logger.error('Word导出初始化失败', e); alert('导出初始化失败: ' + e.message); } } }; // UI相关功能 const ui = { updateButtonStatus: () => { const jsonButton = document.getElementById('downloadJsonButton'); const mdButton = document.getElementById('downloadMdButton'); const wordButton = document.getElementById('downloadWordButton'); if (!jsonButton || !mdButton || !wordButton) return; const hasResponse = state.targetResponse !== null; let platformName = 'AI对话'; let statsText = ''; try { const jsonData = JSON.parse(state.targetResponse || '{}'); switch(state.platformType) { case 'deepseek': platformName = `DeepSeek_${jsonData.data?.biz_data?.chat_session?.title || 'deepseek-Chat'}`; statsText = `Token用量: ${state.messageStats.totalTokens}`; break; case 'ncn': platformName = `AI-N_${jsonData.data?.title || 'AI-Chat'}`; 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 = `xinghuo_${state.currentTitle}`; statsText = `对话轮次: ${state.messageStats.convTurns} | 字数: ${state.messageStats.totalChars}`; break; case 'doubao': case 'doubao_title': platformName = state.currentTitle ||jsonData.downlink_body?.get_conv_info_downlink_body?.conversation_info?.name ||'豆包对话'; statsText = state.platformType === 'doubao_title' ? `对话数: ${state.messageStats.convTurns}` : `消息数: ${state.messageStats.convTurns} | 字数: ${state.messageStats.totalChars} | 附件: ${state.messageStats.fileCount}`; break; } } catch (e) { logger.error('更新按钮状态时解析JSON失败', e); } // 更新按钮状态和样式 [jsonButton, mdButton, wordButton].forEach(button => { button.style.backgroundColor = hasResponse ? '#28a745' : '#007bff'; button.dataset.tooltip = `${platformName}数据已就绪\n${statsText}\n最后更新: ${state.lastUpdateTime}`; // 悬停效果 button.onmouseenter = () => { button.style.transform = 'translateY(-2px)'; button.style.boxShadow = '0 6px 8px rgba(0,0,0,0.15)'; }; button.onmouseleave = () => { button.style.transform = ''; button.style.boxShadow = '0 2px 5px rgba(0,0,0,0.2)'; }; }); // 特殊处理MD和Word按钮 mdButton.style.backgroundColor = state.convertedMd ? '#28a745' : '#007bff'; wordButton.style.backgroundColor = state.convertedMd ? '#28a745' : '#007bff'; // 禁用状态 jsonButton.disabled = !hasResponse; mdButton.disabled = !state.convertedMd; wordButton.disabled = !state.convertedMd; }, createDownloadButtons: () => { // 如果按钮已存在,则不再创建 if (document.getElementById('downloadJsonButton')) return; const buttonContainer = document.createElement('div'); buttonContainer.id = 'exportButtonsContainer'; 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: CONFIG.BUTTON_OPACITY, 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.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`; // 添加按钮的 classname [jsonButton, mdButton, wordButton].forEach(btn => { btn.className = 'export-button'; }); Object.assign(jsonButton.style, buttonStyles); Object.assign(mdButton.style, buttonStyles); Object.assign(wordButton.style, buttonStyles); // 鼠标悬停效果 buttonContainer.onmouseenter = () =>{ buttonContainer.style.opacity = CONFIG.BUTTON_HOVER_OPACITY; buttonContainer.style.boxShadow = '0 8px 16px rgba(0,0,0,0.15)'; }; buttonContainer.onmouseleave = () =>{ buttonContainer.style.opacity = CONFIG.BUTTON_OPACITY; buttonContainer.style.boxShadow = '0 4px 12px rgba(0,0,0,0.1)'; } // 拖动功能 let isDragging = false; let initialX, initialY, xOffset = 0, yOffset = 0; buttonContainer.addEventListener('mousedown', (e) => { initialX = e.clientX - xOffset; initialY = e.clientY - yOffset; if (e.target === buttonContainer) { isDragging = true; } }); document.addEventListener('mousemove', (e) => { if (isDragging) { e.preventDefault(); xOffset = e.clientX - initialX; yOffset = e.clientY - initialY; buttonContainer.style.transform = `translate(${xOffset}px, ${yOffset}px)`; } }); document.addEventListener('mouseup', () => { isDragging = false; }); // 阻止按钮的 mousedown 冒泡影响拖动 [jsonButton, mdButton, wordButton].forEach(btn => { btn.addEventListener('mousedown', (e) => e.stopPropagation()); }); // 按钮点击事件 jsonButton.onclick = exportHandlers.json; mdButton.onclick = exportHandlers.markdown; wordButton.onclick = exportHandlers.word; // 组装按钮 buttonContainer.appendChild(jsonButton); buttonContainer.appendChild(mdButton); buttonContainer.appendChild(wordButton); document.body.appendChild(buttonContainer); // 自动折叠/展开功能 let isCollapsed = false; let collapseTimeout; const collapseDelay = 2000; // 2秒后自动折叠 // 自动折叠函数 const autoCollapse = () => { collapseTimeout = setTimeout(() => { buttonContainer.classList.add('collapsed'); isCollapsed = true; }, collapseDelay); }; // 初始设置自动折叠 autoCollapse(); // 鼠标进入展开 buttonContainer.addEventListener('mouseenter', () => { clearTimeout(collapseTimeout); if (isCollapsed) { buttonContainer.classList.remove('collapsed'); isCollapsed = false; } }); // 鼠标离开后延迟折叠 buttonContainer.addEventListener('mouseleave', () => { if (!isCollapsed) { autoCollapse(); } }); // 点击折叠按钮也可展开 buttonContainer.addEventListener('click', (e) => { if (isCollapsed && e.target === buttonContainer) { buttonContainer.classList.remove('collapsed'); isCollapsed = false; clearTimeout(collapseTimeout); } }); // 确保拖动时不折叠 buttonContainer.addEventListener('mousedown', () => { clearTimeout(collapseTimeout); }); // 添加加载动画样式 GM_addStyle(` .export-button { transition: all 0.2s ease; } .export-button:hover { background-color: #0056b3 !important; } .export-button:disabled { opacity: 0.6; cursor: not-allowed; background-color: #6c757d !important; } .gm-spinner { border: 2px solid rgba(255,255,255,0.3); border-radius: 50%; border-top-color: #fff; width: 12px; height: 12px; animation: spin ${CONFIG.SPINNER_ANIMATION_DURATION} linear infinite; display: inline-block; margin-right: 8px; vertical-align: middle; } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } /* 添加折叠/展开相关样式 */ #exportButtonsContainer.collapsed { width: 40px; height: 40px; overflow: hidden; padding: 5px; } #exportButtonsContainer.collapsed .export-button { display: none; } #exportButtonsContainer.collapsed::before { content: "↓↓↓"; position: absolute; width: 30px; height: 30px; background: #007bff; color: white; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 16px; cursor: pointer; box-shadow: 0 2px 5px rgba(0,0,0,0.2); } #exportButtonsContainer:not(.collapsed)::before { display: none; } #exportButtonsContainer { transition: all 0.3s ease; } /* 标题相关样式 */ [data-tooltip] { position: relative; } [data-tooltip]:hover::after { content: attr(data-tooltip); position: absolute; right: 100%; top: 50%; transform: translateY(-50%); background:rgba(228,17,136,0.7); color: #fff; padding: 8px 12px; border-radius: 4px; font-size: 16px; font-family: "方正小标宋简体","黑体", Arial, sans-serif; white-space: pre; margin-right: 10px; pointer-events: none; opacity: 0.1; transition: opacity 0.3s; } [data-tooltip]:hover::after { opacity: 1; } `); ui.updateButtonStatus(); } }; // 网络拦截 function setupNetworkInterception() { // 拦截XHR请求 const originalOpen = XMLHttpRequest.prototype.open; XMLHttpRequest.prototype.open = function(...args) { // 处理缓存参数 if (args[1]?.includes('history_messages?chat_session_id')) { args[1] = args[1].split('&cache_version=')[0]; } else if (args[1]?.includes('conversation/info?conversation_id')) { args[1] = args[1].split('&cache_version=')[0]; } else if (args[1]?.includes('/api/user/agent/conversation/v1/detail')) { args[1] = args[1].split('&cacheBust=')[0]; } this._requestURL = args[1]; const url = args[1]; //console.log('XHR.open被调用:', url); this.addEventListener('load', () => { if (this.responseURL) { //console.log('XHR请求完成:', this.responseURL); // 添加调试 for (const [platform, pattern] of Object.entries(PLATFORM_PATTERNS)) { if (pattern.test(this.responseURL)) { processTargetResponse(this.responseText, this.responseURL); break; } } } }); 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; //console.log('🔍 Fetch请求被调用:', url); //console.log('⏰ 时间线调试 - 脚本启动:', performance.now()); try { response = await originalFetch.apply(this, args); if (typeof url === 'string') { for (const [platform, pattern] of Object.entries(PLATFORM_PATTERNS)) { if (pattern.test(url)) { const contentType = response.headers.get('content-type'); if (contentType?.includes('application/json')) { const clonedResponse = response.clone(); clonedResponse.text().then(text => { processTargetResponse(text, url); }).catch(e => { logger.error(`解析fetch响应文本时出错`, { url, error: e }); }); } break; } } } } catch (error) { logger.error('Fetch请求失败', error); throw error; } return response; }; } setupNetworkInterception(); setupAuthInterceptor(); // Kimi标题授权拦截器 // 初始化 function initialize() { // setupNetworkInterception(); ui.createDownloadButtons(); logger.info('增强版导出脚本已启动'); // 使用MutationObserver确保按钮存在 const observer = new MutationObserver((mutations) => { if (!document.getElementById('downloadJsonButton') || !document.getElementById('downloadMdButton')) { logger.info('检测到按钮丢失,正在重新创建...'); ui.createDownloadButtons(); } }); observer.observe(document.body, { childList: true, subtree: true }); } // 页面加载完成后初始化 if (document.readyState === 'complete') { initialize(); } else { window.addEventListener('load', initialize); } })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址