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