您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Greasy Fork is available in English.
优雅导出 Claude 对话记录,支持 JSON 和 Markdown 格式
当前为
// ==UserScript== // @name Claude Conversation Exporter Plus // @namespace http://tampermonkey.net/ // @version 4.6 // @description 优雅导出 Claude 对话记录,支持 JSON 和 Markdown 格式 // @author Gao + GPT-4 + Claude // @license Custom License // @match https://*.claudesvip.top/chat/* // @match https://*.claude.ai/chat/* // @match https://*.fuclaude.com/chat/* // @grant none // ==/UserScript== /* 您可以在个人设备上使用和修改该代码。 不得将该代码或其修改版本重新分发、再发布或用于其他公众渠道。 保留所有权利,未经授权不得用于商业用途。 */ (function() { 'use strict'; // 状态追踪 let state = { targetResponse: null, lastUpdateTime: null, convertedMd: null }; // 日志函数 const log = { info: (msg) => console.log(`[Claude Saver] ${msg}`), error: (msg, e) => console.error(`[Claude Saver] ${msg}`, e) }; // 正则表达式用于匹配目标 URL const targetUrlPattern = /\/chat_conversations\/[\w-]+\?tree=True&rendering_mode=messages&render_all_tools=true/; // 响应处理函数(处理符合匹配模式的响应) function processTargetResponse(text, url) { try { if (targetUrlPattern.test(url)) { state.targetResponse = text; state.lastUpdateTime = new Date().toLocaleTimeString(); updateButtonStatus(); log.info(`成功捕获目标响应 (${text.length} bytes) 来自: ${url}`); // 转换为Markdown state.convertedMd = convertJsonToMd(JSON.parse(text)); log.info('成功将JSON转换为Markdown'); } } catch (e) { log.error('处理目标响应时出错:', e); } } // 更新按钮状态 function updateButtonStatus() { const jsonButton = document.getElementById('downloadJsonButton'); const mdButton = document.getElementById('downloadMdButton'); if (jsonButton && mdButton) { const hasResponse = state.targetResponse !== null; jsonButton.style.backgroundColor = hasResponse ? '#28a745' : '#007bff'; mdButton.style.backgroundColor = state.convertedMd ? '#28a745' : '#007bff'; const statusText = hasResponse ? `最后更新: ${state.lastUpdateTime} 数据已准备好` : '等待目标响应中...'; jsonButton.title = statusText; mdButton.title = statusText; } } // 创建下载按钮 function createDownloadButtons() { // JSON 下载按钮 const jsonButton = document.createElement('button'); const mdButton = document.createElement('button'); const buttonStyles = { padding: '10px 15px', 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', marginRight: '10px' }; jsonButton.id = 'downloadJsonButton'; jsonButton.innerText = '下载 JSON'; mdButton.id = 'downloadMdButton'; mdButton.innerText = '下载 Markdown'; Object.assign(jsonButton.style, buttonStyles); Object.assign(mdButton.style, buttonStyles); // 鼠标悬停效果 const onMouseOver = (button) => { button.style.transform = 'scale(1.05)'; button.style.boxShadow = '0 4px 8px rgba(0,0,0,0.3)'; }; const onMouseOut = (button) => { button.style.transform = 'scale(1)'; button.style.boxShadow = '0 2px 5px rgba(0,0,0,0.2)'; }; jsonButton.onmouseover = () => onMouseOver(jsonButton); jsonButton.onmouseout = () => onMouseOut(jsonButton); mdButton.onmouseover = () => onMouseOver(mdButton); mdButton.onmouseout = () => onMouseOut(mdButton); // 下载 JSON 功能 jsonButton.onclick = function() { if (!state.targetResponse) { alert(`还没有发现有效的对话记录。 请等待目标响应或进行一些对话。`); return; } try { const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, -5); const chatName = document.title.trim().replace(/\s+-\s+Claude$/, '').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('下载过程中发生错误,请查看控制台了解详情。'); } }; // 下载 Markdown 功能 mdButton.onclick = function() { if (!state.convertedMd) { alert(`还没有发现有效的对话记录。 请等待目标响应或进行一些对话。`); return; } try { const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, -5); const chatName = document.title.trim().replace(/\s+-\s+Claude$/, '').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(); log.info(`成功下载文件: ${fileName}`); } catch (e) { log.error('下载过程中出错:', e); alert('下载过程中发生错误,请查看控制台了解详情。'); } }; const buttonContainer = document.createElement('div'); buttonContainer.style.position = 'fixed'; buttonContainer.style.top = '45%'; buttonContainer.style.right = '10px'; buttonContainer.style.transform = 'translateY(-50%)'; buttonContainer.style.zIndex = '9999'; buttonContainer.style.display = 'flex'; buttonContainer.style.flexDirection = 'row'; buttonContainer.appendChild(jsonButton); buttonContainer.appendChild(mdButton); document.body.appendChild(buttonContainer); updateButtonStatus(); } // JSON 转 Markdown 转换函数 function convertJsonToMd(data) { let mdContent = []; const title = document.title.trim().replace(/\s+-\s+Claude$/, ''); mdContent.push(`# ${title}\n`); for (const message of data['chat_messages']) { const sender = message['sender'].charAt(0).toUpperCase() + message['sender'].slice(1); mdContent.push(`## ${sender}`); const createdAt = message['created_at'] || ''; const updatedAt = message['updated_at'] || ''; const timestamp = createdAt === updatedAt ? `*${createdAt}*` : `*${createdAt} (updated)*`; mdContent.push(timestamp); const content = processContent(message['content']); // 如果是assistant的消息,调整其中的标题级别 if (sender.toLowerCase() === 'assistant') { mdContent.push(adjustHeadingLevel(content)); } else { mdContent.push(`${content}\n`); } } return mdContent.join('\n'); } // 调整Markdown标题级别 function adjustHeadingLevel(text, increaseLevel = 2) { const codeBlockPattern = /```[\s\S]*?```/g; let segments = []; let match; // 提取代码块,并用占位符替代 let lastIndex = 0; while ((match = codeBlockPattern.exec(text)) !== null) { segments.push(text.substring(lastIndex, match.index)); segments.push(match[0]); // 保留代码块原样 lastIndex = codeBlockPattern.lastIndex; } segments.push(text.substring(lastIndex)); // 调整标题级别 segments = segments.map(segment => { if (segment.startsWith('```')) { return segment; // 保留代码块原样 } else { let lines = segment.split('\n'); lines = lines.map(line => { if (line.trim().startsWith('#')) { const currentLevel = (line.match(/^#+/) || [''])[0].length; return '#'.repeat(currentLevel + increaseLevel) + line.slice(currentLevel); } return line; }); return lines.join('\n'); } }); return segments.join(''); } // 处理消息内容,提取纯文本并处理LaTeX公式 function processContent(content) { if (Array.isArray(content)) { let textParts = []; for (const item of content) { if (item.type === 'text') { let text = item.text || ''; text = processLatex(text); text = text.replace(/(?<!\n)(\n\| .*? \|\n\|[-| ]+\|\n(?:\| .*? \|\n)+)/g, '\n$1'); // 在表格前插入一个空行 textParts.push(text); } } return textParts.join('\n'); } return String(content); } // 处理LaTeX公式 function processLatex(text) { // 区分行内公式和独立公式 text = text.replace(/\$\$(.+?)\$\$/gs, (match, formula) => { if (formula.includes('\n')) { // 这是独立公式 return `$$${formula}$$`; } else { // 这是行内公式 return `$${formula}$`; } }); return text; } // 监听 fetch 请求 const originalFetch = window.fetch; window.fetch = async function(...args) { const response = await originalFetch.apply(this, args); const url = args[0]; log.info(`捕获到 fetch 请求: ${url}`); if (targetUrlPattern.test(url)) { try { log.info(`匹配到目标 URL: ${url}`); const clonedResponse = response.clone(); clonedResponse.text().then(text => { processTargetResponse(text, url); }).catch(e => { log.error('解析fetch响应时出错:', e); }); } catch (e) { log.error('克隆fetch响应时出错:', e); } } return response; }; // 页面加载完成后立即创建按钮 window.addEventListener('load', function() { createDownloadButtons(); // 使用 MutationObserver 确保按钮始终存在 const observer = new MutationObserver(() => { if (!document.getElementById('downloadJsonButton') || !document.getElementById('downloadMdButton')) { log.info('检测到按钮丢失,正在重新创建...'); createDownloadButtons(); } }); observer.observe(document.body, { childList: true, subtree: true }); log.info('Claude 保存脚本已启动'); }); })();