吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 2460|回复: 44
收起左侧

[其他转载] 【油猴脚本】DeepSeek一键导出为md和UI优化

  [复制链接]
t17773839515 发表于 2026-3-30 18:04
本帖最后由 t17773839515 于 2026-3-30 18:07 编辑

最近网页版访问deepseek,用AI给朋友设计几套试题卷
找了个一键导出为md的油猴脚本,就是这个页面优化了,不太适合用做卷子,
学习了代码,将一些图标优化了

这是两版的导出对比,可能有人更喜欢原版,放最后面了
7ede0668-3aa7-4fde-9ac1-b08e8638a288.png

修改版
[JavaScript] 纯文本查看 复制代码
// ==UserScript==
// @name                 DeepSeek Chat Exporter
// @name:zh-CN           DeepSeek 对话导出工具
// @description          Export DeepSeek chat to Markdown with accurate formatting, including code blocks, tables, and math formulas.
// @description:zh-CN    深度优化排版,高保真还原 DeepSeek 聊天的标题、代码块、表格和公式。
// @namespace            https://github.com/AstridStark25963/deepseek-chat-exporter
// @version              1.0.0
// [url=home.php?mod=space&uid=686208]@AuThor[/url]               AstridStark25963
// @license              MIT
// @icon                 data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjIwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB2aWV3Qm94PSIwIDAgMjQgMjQiIGZpbGw9IiM2MzY2ZjEiPjxwYXRoIGZpbGw9IiM2MzY2ZjEiIGQ9Ik0yMy43NSA0LjkyN2MtLjI0NS0uMTItLjM0LjEwOC0uNDgyLjIyNGMtLjA0OS4wMzgtLjA5LjA4Ny0uMTMxLjEzYy0uMzU3LjM4NC0uNzczLjYzNC0xLjMxNS42MDRjLS43OTYtLjA0NC0xLjQ3NC4yMDctMi4wNzQuODE4Yy0uMTI3LS43NTQtLjU1MS0xLjIwMy0xLjE5NS0xLjQ5MmMtLjMzOC0uMTUtLjY4LS4zLS45MTUtLjYyNmMtLjE2NS0uMjMxLS4yMS0uNDktLjI5My0uNzQ0Yy0uMDUyLS4xNTMtLjEwNS0uMzEtLjI4LS4zMzdjLS4xOTItLjAzLS4yNjYuMTMtLjM0MS4yNjVjLS4zLjU1LS40MTYgMS4xNTgtLjQwNiAxLjc3MmMuMDI3IDEuMzgyLjYwOCAyLjQ4MiAxLjc2MiAzLjI2NmMuMTMyLjA5LjE2Ni4xOC4xMjQuMzExYy0uMDc5LjI3LS4xNzIuNTMxLS4yNTUuOGMtLjA1Mi4xNzMtLjEzLjIxMS0uMzE0LjEzNUE1LjMgNS4zIDAgMCAxIDE1Ljk3IDguOTJjLS44Mi0uNzk3LTEuNTYzLTEuNjc3LTIuNDg5LTIuMzY2YTExIDExIDAgMCAwLS42Ni0uNDU0Yy0uOTQ0LS45MjIuMTI1LTEuNjc5LjM3Mi0xLjc2OGMuMjU5LS4wOTMuMDktLjQxNi0uNzQ3LS40MTJjLS44MzUuMDA0LTEuNi4yODUtMi41NzQuNjU5Yy0uMTQzLjA1Ny0uMzI2LjE1My0uNDQ2LjEzYTkuMiA5LjIgMCAwIDAtMi43NjMtLjA5NmMtMS44MDYuMjAzLTMuMjUgMS4wNi00LjMxIDIuNTI1Yy0xLjI3NSAxLjc2LTEuNTc0IDMuNzU5LTEuMjA3IDUuODQ2Yy4zODUgMi4xOTcgMS41MDIgNC4wMTkgMy4yMiA1LjQ0MmMxLjc4IDEuNDc0IDMuODMgMi4xOTcgNi4xNjkgMi4wNThjMS40Mi0uMDgxIDMuMDAzLS4yNzMgNC43ODYtMS43ODljLjQ1LjIyNC45MjIuMzEzIDEuNzA3LjM4MWMuNjAzLjA1NyAxLjE4NC0uMDMgMS42MzQtLjEyM2MuNzA0LS4xNS42NTUtLjgwNC40LS45MjZjLTIuMDY1LS45NjYtMS42MTItLjU3My0yLjAyNC0uODljMS4wNS0xLjI0OCAyLjYzMi0yLjU0NCAzLjI1LTYuNzQxYy4wNDktLjMzNC4wMDctLjU0MyAwLS44MTRjLS4wMDMtLjE2My4wMzQtLjIyOC4yMi0uMjQ3YTQgNCAwIDAgMCAxLjQ4Mi0uNDU3YzEuMzM4LS43MzQgMS44NjctMS45MzkgMS45OTUtMy4zODVjLjAxOS0uMjItLjAwNC0uNDUtLjIzNi0uNTY1bS0xMS42NTIgMTMuMDFjLTIuMDAyLTEuNTgtMi45NzItMi4xLTMuMzczLTIuMDc4Yy0uMzc1LjAyMS0uMzA4LjQ1Mi0uMjI1LjczM2MuMDg2LjI3Ny4xOTguNDY4LjM1Ni43MTFjLjEwOS4xNjIuMTg0LjQwMi0uMTA4LjU4Yy0uNjQ1LjQwMy0xLjc2Ni0uMTM0LTEuODItLjE2Yy0xLjMwMy0uNzctMi4zOTQtMS43OS0zLjE2My0zLjE4MmMtLjc0MS0xLjM0Mi0xLjE3Mi0yLjc4LTEuMjQzLTQuMzE1Yy0uMDItLjM3Mi4wOS0uNTAzLjQ1Ni0uNTdhNC41IDQuNSAwIDAgMSAxLjQ2Ni0uMDM3YzIuMDQzLjMgMy43ODIgMS4yMTggNS4yNCAyLjY3Yy44MzIuODI5IDEuNDYyIDEuODE3IDIuMTEgMi43ODNjLjY5IDEuMDI3IDEuNDMyIDIuMDA0IDIuMzc3IDIuODA0Yy4zMzMuMjgxLjYuNDk1Ljg1NC42NTNjLS43NjguMDg1LTIuMDUuMTA0LTIuOTI3LS41OTJtLjk2LTYuMTk5YS4yOTQuMjk0IDAgMSAxIC41ODggMGEuMjk0LjI5NCAwIDAgMS0uMjk2LjI5NmEuMjkuMjkgMCAwIDEtLjI5My0uMjk2bTIuOTggMS41MzdjLS4xOTIuMDc4LS4zODMuMTQ2LS41NjYuMTU0YTEuMiAxLjIgMCAwIDEtLjc2NS0uMjQ1Yy0uMjYyLS4yMi0uNDUtLjM0My0uNTMtLjczYTEuNyAxLjcgMCAwIDEgLjAxNi0uNTY2Yy4wNjgtLjMxNS0uMDA4LS41MTYtLjIyOC0uN2MtLjE4LS4xNS0uNDA4LS4xOS0uNjYtLjE5YS41LjUgMCAwIDEtLjI0NC0uMDc2Yy0uMTA1LS4wNTMtLjE5MS0uMTg0LS4xMDktLjM0NWExIDEgMCAwIDEgLjE4NS0uMjAxYy4zNC0uMTk1LjczNC0uMTMgMS4wOTguMDE1Yy4zMzcuMTM5LjU5Mi4zOTMuOTU5Ljc1MmMuMzc1LjQzNC40NDIuNTU1LjY1Ni44OGMuMTY4LjI1Ni4zMjMuNTE4LjQyOC44MThjLjA2My4xODYtLjAyLjM0LS4yNC40MzQiLz48L3N2Zz4=
// @match                *://chat.deepseek.com/*
// @run-at               document-idle
// @grant                none
// ==/UserScript==

(function() {
    'use strict';

    const style = document.createElement('style');
    style.innerHTML = `
        #ds-export-btn {
            position: fixed;
            bottom: 20px;
            right: 20px;
            z-index: 999999;
            min-width: 50px;
            height: 50px;
            padding: 0 12px;
            border-radius: 999px;
            background-color: #4d6bfe;
            color: white;
            border: none;
            cursor: pointer;
            box-shadow: 0 4px 12px rgba(77, 107, 254, 0.3);
            display: flex;
            align-items: center;
            justify-content: center;
            transition: all 0.2s cubic-bezier(0.25, 0.8, 0.25, 1);
        }

        #ds-export-btn:hover {
            transform: scale(1.1);
            box-shadow: 0 6px 16px rgba(77, 107, 254, 0.5);
            background-color: #3d5be0;
        }

        #ds-export-btn:active {
            transform: scale(0.95);
        }

        #ds-export-btn .ds-export-label {
            font-size: 12px;
            font-weight: 600;
            line-height: 1;
            letter-spacing: 0.02em;
        }
    `;
    document.head.appendChild(style);

    class DOMToMarkdown {
        constructor() {}

        convert(element) {
            return this._traverse(element).trim();
        }

        _isCitationLink(node) {
            if (!node || node.nodeType !== Node.ELEMENT_NODE) return false;
            if (node.classList.contains('ds-markdown-cite')) return true;
            if (node.tagName.toLowerCase() === 'a' && node.querySelector('.ds-markdown-cite')) return true;
            return false;
        }

        _traverse(node) {
            if (!node) return "";
            if (node.nodeType === Node.TEXT_NODE) return node.textContent;
            if (node.nodeType === Node.ELEMENT_NODE) {
                const tagName = node.tagName.toLowerCase();

                if (node.classList.contains('ds-markdown-cite')) {
                    const num = node.textContent.replace(/[^0-9]/g, '');
                    if (tagName === 'a') {
                        const url = node.getAttribute('href') || '';
                        return `[${num}](${url})`;
                    }
                    return num;
                }

                if (node.classList.contains('md-code-block')) return this._processCodeBlock(node);
                if (node.classList.contains('katex-display') || node.classList.contains('katex')) return this._processMath(node);

                if (node.classList.contains('ds-icon-button') ||
                    node.classList.contains('ds-atom-button') ||
                    node.style.display === 'none' ||
                    node.classList.contains('ds-icon')) {
                    return "";
                }

                let content = "";
                const children = Array.from(node.childNodes);
                children.forEach((child, index) => {
                    if (index > 0) {
                        const prev = children[index - 1];
                        if (this._isCitationLink(child) && this._isCitationLink(prev)) {
                            content += " ";
                        }
                    }
                    content += this._traverse(child);
                });

                switch (tagName) {
                    case 'h1': return `# ${content}\n\n`;
                    case 'h2': return `## ${content}\n\n`;
                    case 'h3': return `### ${content}\n\n`;
                    case 'h4': return `#### ${content}\n\n`;
                    case 'h5': return `##### ${content}\n\n`;
                    case 'h6': return `###### ${content}\n\n`;
                    case 'p': return `${content}\n\n`;
                    case 'br': return `\n`;
                    case 'hr': return `\n---\n\n`;
                    case 'strong': case 'b': return `**${content}**`;
                    case 'em': case 'i': return `*${content}*`;
                    case 'del': case 's': return `~~${content}~~`;
                    case 'code': return `\`${content}\``;
                    case 'blockquote': return content.split('\n').map(l => l.trim() ? `> ${l}` : '>').join('\n') + '\n\n';
                    case 'ul': return this._processList(node, false);
                    case 'ol': return this._processList(node, true);
                    case 'li': return content;
                    case 'a': return `[${content}](${node.getAttribute('href') || ''})`;
                    case 'img': return `![${node.getAttribute('alt') || 'image'}](${node.getAttribute('src')})`;
                    case 'table': return this._processTable(node);
                    case 'tbody': case 'thead': case 'tr': case 'td': case 'th': return content;
                    default: return content;
                }
            }
            return "";
        }

        _processCodeBlock(node) {
            let lang = "";
            const banner = node.querySelector('.md-code-block-banner-wrap');
            if (banner) {
                const clone = banner.cloneNode(true);
                clone.querySelectorAll('button').forEach(btn => btn.remove());
                lang = clone.textContent.trim().toLowerCase();
            }
            const pre = node.querySelector('pre');
            const code = pre ? pre.textContent : "";
            return `\n\`\`\`${lang}\n${code}\n\`\`\`\n\n`;
        }

        _processMath(node) {
            const annotation = node.querySelector('annotation[encoding="application/x-tex"]');
            if (annotation) {
                const tex = annotation.textContent;
                const isDisplay = node.classList.contains('katex-display') || node.tagName.toLowerCase() === 'div';
                return isDisplay ? `\n$$${tex}$$\n` : `$${tex}$`;
            }
            return node.textContent;
        }

        _processList(node, isOrdered) {
            let markdown = "";
            const items = Array.from(node.children).filter(n => n.tagName.toLowerCase() === 'li');
            items.forEach((li, index) => {
                let liContent = "";
                const liChildren = Array.from(li.childNodes);
                liChildren.forEach((child, i) => {
                     if (i > 0) {
                        const prev = liChildren[i - 1];
                        if (this._isCitationLink(child) && this._isCitationLink(prev)) liContent += " ";
                    }
                    liContent += this._traverse(child);
                });
                const finalContent = liContent.split('\n').map((l, i) => i === 0 ? l : `    ${l}`).join('\n');
                const prefix = isOrdered ? `${index + 1}.` : `*`;
                markdown += `${prefix} ${finalContent.trim()}\n`;
            });
            return markdown + '\n';
        }

        _processTable(node) {
            const rows = Array.from(node.querySelectorAll('tr'));
            if (rows.length === 0) return "";
            let markdown = "\n";
            const headerCells = Array.from(rows[0].querySelectorAll('th, td'));
            const headers = headerCells.map(c => this._traverse(c).trim());
            markdown += `| ${headers.join(' | ')} |\n`;
            markdown += `| ${headers.map(() => '---').join(' | ')} |\n`;
            for (let i = 1; i < rows.length; i++) {
                const cells = Array.from(rows[i].querySelectorAll('td, th'));
                const rowData = cells.map(c => this._traverse(c).replace(/\n/g, '<br>').trim());
                markdown += `| ${rowData.join(' | ')} |\n`;
            }
            return markdown + "\n";
        }
    }

    class DeepseekParser {
        constructor() {
            this.selectors = {
                messageBubble: '.ds-message',
                aiContent: '.ds-markdown',
                thinkContent: '.ds-think-content',
            };
            this.converter = new DOMToMarkdown();
        }

        getChatTitle() {
            return document.title.replace(' - DeepSeek', '').trim() || 'DeepSeek_Chat';
        }

        getAllMessages() {
            const nodes = document.querySelectorAll(this.selectors.messageBubble);
            const messages = [];

            nodes.forEach(node => {
                const aiContent = node.querySelector(this.selectors.aiContent);

                if (aiContent) {
                    let finalMarkdown = "";

                    const searchNode = Array.from(node.querySelectorAll('div')).find(div => {
                         return div.innerText && div.innerText.includes('Read') && div.innerText.includes('web pages') && div.querySelector('.ds-icon');
                    });
                    if (searchNode && !finalMarkdown.includes('DeepSearch')) {
                        const textSpan = Array.from(searchNode.querySelectorAll('span')).find(s => s.innerText.includes('web pages'));
                        const searchText = textSpan ? textSpan.innerText : "Search Results";
                        finalMarkdown += `> **DeepSearch:** ${searchText}\n>\n\n`;
                    }

                    const thinkNode = node.querySelector(this.selectors.thinkContent);
                    if (thinkNode) {
                        const thinkMarkdownNode = thinkNode.querySelector('.ds-markdown');
                        if (thinkMarkdownNode) {
                            const rawThought = this.converter.convert(thinkMarkdownNode);
                            finalMarkdown += `> **DeepThinking...**\n>\n`;
                            finalMarkdown += rawThought.split('\n').map(line => `> ${line}`).join('\n');
                            finalMarkdown += `\n\n---\n\n`;
                        }
                    }

                    const allMarkdownNodes = Array.from(node.querySelectorAll(this.selectors.aiContent));
                    const contentNode = allMarkdownNodes.find(n => !n.closest(this.selectors.thinkContent));

                    if (contentNode) {
                        finalMarkdown += this.converter.convert(contentNode);
                    }

                    messages.push({ role: 'DeepSeek', content: finalMarkdown });

                } else {
                    messages.push({ role: 'User', content: node.innerText.trim() });
                }
            });

            return messages;
        }
    }

    class ExporterUI {
        constructor() {
            this.parser = new DeepseekParser();
            this.initButton();
        }

        initButton() {
            if (document.getElementById('ds-export-btn')) return;
            const btn = document.createElement('button');
            btn.id = 'ds-export-btn';

            btn.innerHTML = `<span class="ds-export-label">导出</span>`;

            btn.title = "Export Chat to Markdown";

            btn.onclick = () => this.exportChat();
            document.body.appendChild(btn);
        }

        async exportChat() {
            const messages = this.parser.getAllMessages();
            if (messages.length === 0) {
                alert('未找到对话记录,请确保页面已加载完毕。');
                return;
            }

            const title = this.parser.getChatTitle();
            let markdown = `# ${title}\n\n`;

            messages.forEach((msg, index) => {
                const roleTitle = msg.role === 'DeepSeek' ? '&#129302; DeepSeek' : '&#128100; User';
                markdown += `## ${roleTitle}\n\n`;
                markdown += `${msg.content}\n\n`;

                if (msg.role === 'DeepSeek' && index < messages.length - 1) {
                    markdown += `---\n\n`;
                }
            });

            this.downloadFile(markdown, title);
        }

        downloadFile(content, title) {
            const now = new Date();
            const timestamp = now.toISOString().replace(/[-:T]/g, '').slice(0, 14);
            const safeTitle = title.replace(/[\\/:*?"<>|]/g, '_').substring(0, 50);
            const filename = `${safeTitle}_${timestamp}.md`;
            const blob = new Blob([content], { type: 'text/markdown' });
            const url = URL.createObjectURL(blob);
            const a = document.createElement('a');
            a.href = url;
            a.download = filename;
            document.body.appendChild(a);
            a.click();
            document.body.removeChild(a);
            URL.revokeObjectURL(url);
        }
    }

    setTimeout(() => {
        new ExporterUI();
    }, 2000);

})();


原版
[JavaScript] 纯文本查看 复制代码
// ==UserScript==
// @name                 DeepSeek Chat Exporter
// @name:zh-CN           DeepSeek 对话导出工具
// @description          Export DeepSeek chat to Markdown with accurate formatting, including code blocks, tables, and math formulas.
// @description:zh-CN    深度优化排版,高保真还原 DeepSeek 聊天的标题、代码块、表格和公式。
// @namespace            https://github.com/AstridStark25963/deepseek-chat-exporter
// @version              1.0.0
// @author               AstridStark25963
// @license              MIT
// @icon                 data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjIwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB2aWV3Qm94PSIwIDAgMjQgMjQiIGZpbGw9IiM2MzY2ZjEiPjxwYXRoIGZpbGw9IiM2MzY2ZjEiIGQ9Ik0yMy43NSA0LjkyN2MtLjI0NS0uMTItLjM0LjEwOC0uNDgyLjIyNGMtLjA0OS4wMzgtLjA5LjA4Ny0uMTMxLjEzYy0uMzU3LjM4NC0uNzczLjYzNC0xLjMxNS42MDRjLS43OTYtLjA0NC0xLjQ3NC4yMDctMi4wNzQuODE4Yy0uMTI3LS43NTQtLjU1MS0xLjIwMy0xLjE5NS0xLjQ5MmMtLjMzOC0uMTUtLjY4LS4zLS45MTUtLjYyNmMtLjE2NS0uMjMxLS4yMS0uNDktLjI5My0uNzQ0Yy0uMDUyLS4xNTMtLjEwNS0uMzEtLjI4LS4zMzdjLS4xOTItLjAzLS4yNjYuMTMtLjM0MS4yNjVjLS4zLjU1LS40MTYgMS4xNTgtLjQwNiAxLjc3MmMuMDI3IDEuMzgyLjYwOCAyLjQ4MiAxLjc2MiAzLjI2NmMuMTMyLjA5LjE2Ni4xOC4xMjQuMzExYy0uMDc5LjI3LS4xNzIuNTMxLS4yNTUuOGMtLjA1Mi4xNzMtLjEzLjIxMS0uMzE0LjEzNUE1LjMgNS4zIDAgMCAxIDE1Ljk3IDguOTJjLS44Mi0uNzk3LTEuNTYzLTEuNjc3LTIuNDg5LTIuMzY2YTExIDExIDAgMCAwLS42Ni0uNDU0Yy0uOTQ0LS45MjIuMTI1LTEuNjc5LjM3Mi0xLjc2OGMuMjU5LS4wOTMuMDktLjQxNi0uNzQ3LS40MTJjLS44MzUuMDA0LTEuNi4yODUtMi41NzQuNjU5Yy0uMTQzLjA1Ny0uMzI2LjE1My0uNDQ2LjEzYTkuMiA5LjIgMCAwIDAtMi43NjMtLjA5NmMtMS44MDYuMjAzLTMuMjUgMS4wNi00LjMxIDIuNTI1Yy0xLjI3NSAxLjc2LTEuNTc0IDMuNzU5LTEuMjA3IDUuODQ2Yy4zODUgMi4xOTcgMS41MDIgNC4wMTkgMy4yMiA1LjQ0MmMxLjc4IDEuNDc0IDMuODMgMi4xOTcgNi4xNjkgMi4wNThjMS40Mi0uMDgxIDMuMDAzLS4yNzMgNC43ODYtMS43ODljLjQ1LjIyNC45MjIuMzEzIDEuNzA3LjM4MWMuNjAzLjA1NyAxLjE4NC0uMDMgMS42MzQtLjEyM2MuNzA0LS4xNS42NTUtLjgwNC40LS45MjZjLTIuMDY1LS45NjYtMS42MTItLjU3My0yLjAyNC0uODljMS4wNS0xLjI0OCAyLjYzMi0yLjU0NCAzLjI1LTYuNzQxYy4wNDktLjMzNC4wMDctLjU0MyAwLS44MTRjLS4wMDMtLjE2My4wMzQtLjIyOC4yMi0uMjQ3YTQgNCAwIDAgMCAxLjQ4Mi0uNDU3YzEuMzM4LS43MzQgMS44NjctMS45MzkgMS45OTUtMy4zODVjLjAxOS0uMjItLjAwNC0uNDUtLjIzNi0uNTY1bS0xMS42NTIgMTMuMDFjLTIuMDAyLTEuNTgtMi45NzItMi4xLTMuMzczLTIuMDc4Yy0uMzc1LjAyMS0uMzA4LjQ1Mi0uMjI1LjczM2MuMDg2LjI3Ny4xOTguNDY4LjM1Ni43MTFjLjEwOS4xNjIuMTg0LjQwMi0uMTA4LjU4Yy0uNjQ1LjQwMy0xLjc2Ni0uMTM0LTEuODItLjE2Yy0xLjMwMy0uNzctMi4zOTQtMS43OS0zLjE2My0zLjE4MmMtLjc0MS0xLjM0Mi0xLjE3Mi0yLjc4LTEuMjQzLTQuMzE1Yy0uMDItLjM3Mi4wOS0uNTAzLjQ1Ni0uNTdhNC41IDQuNSAwIDAgMSAxLjQ2Ni0uMDM3YzIuMDQzLjMgMy43ODIgMS4yMTggNS4yNCAyLjY3Yy44MzIuODI5IDEuNDYyIDEuODE3IDIuMTEgMi43ODNjLjY5IDEuMDI3IDEuNDMyIDIuMDA0IDIuMzc3IDIuODA0Yy4zMzMuMjgxLjYuNDk1Ljg1NC42NTNjLS43NjguMDg1LTIuMDUuMTA0LTIuOTI3LS41OTJtLjk2LTYuMTk5YS4yOTQuMjk0IDAgMSAxIC41ODggMGEuMjk0LjI5NCAwIDAgMS0uMjk2LjI5NmEuMjkuMjkgMCAwIDEtLjI5My0uMjk2bTIuOTggMS41MzdjLS4xOTIuMDc4LS4zODMuMTQ2LS41NjYuMTU0YTEuMiAxLjIgMCAwIDEtLjc2NS0uMjQ1Yy0uMjYyLS4yMi0uNDUtLjM0My0uNTMtLjczYTEuNyAxLjcgMCAwIDEgLjAxNi0uNTY2Yy4wNjgtLjMxNS0uMDA4LS41MTYtLjIyOC0uN2MtLjE4LS4xNS0uNDA4LS4xOS0uNjYtLjE5YS41LjUgMCAwIDEtLjI0NC0uMDc2Yy0uMTA1LS4wNTMtLjE5MS0uMTg0LS4xMDktLjM0NWExIDEgMCAwIDEgLjE4NS0uMjAxYy4zNC0uMTk1LjczNC0uMTMgMS4wOTguMDE1Yy4zMzcuMTM5LjU5Mi4zOTMuOTU5Ljc1MmMuMzc1LjQzNC40NDIuNTU1LjY1Ni44OGMuMTY4LjI1Ni4zMjMuNTE4LjQyOC44MThjLjA2My4xODYtLjAyLjM0LS4yNC40MzQiLz48L3N2Zz4=
// @match                *://chat.deepseek.com/*
// @run-at               document-idle
// @grant                none
// ==/UserScript==
 
(function() {
    'use strict';
 
    const style = document.createElement('style');
    style.innerHTML = `
        #ds-export-btn {
            position: fixed;
            bottom: 20px;
            right: 20px;
            z-index: 999999;
            width: 50px;
            height: 50px;
            border-radius: 50%;
            background-color: #4d6bfe;
            color: white;
            border: none;
            cursor: pointer;
            box-shadow: 0 4px 12px rgba(77, 107, 254, 0.3);
            display: flex;
            align-items: center;
            justify-content: center;
            transition: all 0.2s cubic-bezier(0.25, 0.8, 0.25, 1);
        }
 
        #ds-export-btn:hover {
            transform: scale(1.1);
            box-shadow: 0 6px 16px rgba(77, 107, 254, 0.5);
            background-color: #3d5be0;
        }
 
        #ds-export-btn:active {
            transform: scale(0.95);
        }
 
        .ds-export-icon {
            width: 24px;
            height: 24px;
            fill: none;
            stroke: currentColor;
            stroke-width: 2;
            stroke-linecap: round;
            stroke-linejoin: round;
        }
    `;
    document.head.appendChild(style);
 
    class DOMToMarkdown {
        constructor() {}
 
        convert(element) {
            return this._traverse(element).trim();
        }
 
        _isCitationLink(node) {
            if (!node || node.nodeType !== Node.ELEMENT_NODE) return false;
            if (node.classList.contains('ds-markdown-cite')) return true;
            if (node.tagName.toLowerCase() === 'a' && node.querySelector('.ds-markdown-cite')) return true;
            return false;
        }
 
        _traverse(node) {
            if (!node) return "";
            if (node.nodeType === Node.TEXT_NODE) return node.textContent;
            if (node.nodeType === Node.ELEMENT_NODE) {
                const tagName = node.tagName.toLowerCase();
 
                if (node.classList.contains('ds-markdown-cite')) {
                    const num = node.textContent.replace(/[^0-9]/g, '');
                    if (tagName === 'a') {
                        const url = node.getAttribute('href') || '';
                        return `[${num}](${url})`;
                    }
                    return num;
                }
 
                if (node.classList.contains('md-code-block')) return this._processCodeBlock(node);
                if (node.classList.contains('katex-display') || node.classList.contains('katex')) return this._processMath(node);
 
                if (node.classList.contains('ds-icon-button') ||
                    node.classList.contains('ds-atom-button') ||
                    node.style.display === 'none' ||
                    node.classList.contains('ds-icon')) {
                    return "";
                }
 
                let content = "";
                const children = Array.from(node.childNodes);
                children.forEach((child, index) => {
                    if (index > 0) {
                        const prev = children[index - 1];
                        if (this._isCitationLink(child) && this._isCitationLink(prev)) {
                            content += " ";
                        }
                    }
                    content += this._traverse(child);
                });
 
                switch (tagName) {
                    case 'h1': return `# ${content}\n\n`;
                    case 'h2': return `## ${content}\n\n`;
                    case 'h3': return `### ${content}\n\n`;
                    case 'h4': return `#### ${content}\n\n`;
                    case 'h5': return `##### ${content}\n\n`;
                    case 'h6': return `###### ${content}\n\n`;
                    case 'p': return `${content}\n\n`;
                    case 'br': return `\n`;
                    case 'hr': return `\n---\n\n`;
                    case 'strong': case 'b': return `**${content}**`;
                    case 'em': case 'i': return `*${content}*`;
                    case 'del': case 's': return `~~${content}~~`;
                    case 'code': return `\`${content}\``;
                    case 'blockquote': return content.split('\n').map(l => l.trim() ? `> ${l}` : '>').join('\n') + '\n\n';
                    case 'ul': return this._processList(node, false);
                    case 'ol': return this._processList(node, true);
                    case 'li': return content;
                    case 'a': return `[${content}](${node.getAttribute('href') || ''})`;
                    case 'img': return `![${node.getAttribute('alt') || 'image'}](${node.getAttribute('src')})`;
                    case 'table': return this._processTable(node);
                    case 'tbody': case 'thead': case 'tr': case 'td': case 'th': return content;
                    default: return content;
                }
            }
            return "";
        }
 
        _processCodeBlock(node) {
            let lang = "";
            const banner = node.querySelector('.md-code-block-banner-wrap');
            if (banner) {
                const clone = banner.cloneNode(true);
                clone.querySelectorAll('button').forEach(btn => btn.remove());
                lang = clone.textContent.trim().toLowerCase();
            }
            const pre = node.querySelector('pre');
            const code = pre ? pre.textContent : "";
            return `\n\`\`\`${lang}\n${code}\n\`\`\`\n\n`;
        }
 
        _processMath(node) {
            const annotation = node.querySelector('annotation[encoding="application/x-tex"]');
            if (annotation) {
                const tex = annotation.textContent;
                const isDisplay = node.classList.contains('katex-display') || node.tagName.toLowerCase() === 'div';
                return isDisplay ? `\n$$${tex}$$\n` : `$${tex}$`;
            }
            return node.textContent;
        }
 
        _processList(node, isOrdered) {
            let markdown = "";
            const items = Array.from(node.children).filter(n => n.tagName.toLowerCase() === 'li');
            items.forEach((li, index) => {
                let liContent = "";
                const liChildren = Array.from(li.childNodes);
                liChildren.forEach((child, i) => {
                     if (i > 0) {
                        const prev = liChildren[i - 1];
                        if (this._isCitationLink(child) && this._isCitationLink(prev)) liContent += " ";
                    }
                    liContent += this._traverse(child);
                });
                const finalContent = liContent.split('\n').map((l, i) => i === 0 ? l : `    ${l}`).join('\n');
                const prefix = isOrdered ? `${index + 1}.` : `*`;
                markdown += `${prefix} ${finalContent.trim()}\n`;
            });
            return markdown + '\n';
        }
 
        _processTable(node) {
            const rows = Array.from(node.querySelectorAll('tr'));
            if (rows.length === 0) return "";
            let markdown = "\n";
            const headerCells = Array.from(rows[0].querySelectorAll('th, td'));
            const headers = headerCells.map(c => this._traverse(c).trim());
            markdown += `| ${headers.join(' | ')} |\n`;
            markdown += `| ${headers.map(() => '---').join(' | ')} |\n`;
            for (let i = 1; i < rows.length; i++) {
                const cells = Array.from(rows[i].querySelectorAll('td, th'));
                const rowData = cells.map(c => this._traverse(c).replace(/\n/g, '<br>').trim());
                markdown += `| ${rowData.join(' | ')} |\n`;
            }
            return markdown + "\n";
        }
    }
 
    class DeepseekParser {
        constructor() {
            this.selectors = {
                messageBubble: '.ds-message',
                aiContent: '.ds-markdown',
                thinkContent: '.ds-think-content',
            };
            this.converter = new DOMToMarkdown();
        }
 
        getChatTitle() {
            return document.title.replace(' - DeepSeek', '').trim() || 'DeepSeek_Chat';
        }
 
        getAllMessages() {
            const nodes = document.querySelectorAll(this.selectors.messageBubble);
            const messages = [];
 
            nodes.forEach(node => {
                const aiContent = node.querySelector(this.selectors.aiContent);
 
                if (aiContent) {
                    let finalMarkdown = "";
 
                    const searchNode = Array.from(node.querySelectorAll('div')).find(div => {
                         return div.innerText && div.innerText.includes('Read') && div.innerText.includes('web pages') && div.querySelector('.ds-icon');
                    });
                    if (searchNode && !finalMarkdown.includes('DeepSearch')) {
                        const textSpan = Array.from(searchNode.querySelectorAll('span')).find(s => s.innerText.includes('web pages'));
                        const searchText = textSpan ? textSpan.innerText : "Search Results";
                        finalMarkdown += `> &#127760; **DeepSearch:** ${searchText}\n>\n\n`;
                    }
 
                    const thinkNode = node.querySelector(this.selectors.thinkContent);
                    if (thinkNode) {
                        const thinkMarkdownNode = thinkNode.querySelector('.ds-markdown');
                        if (thinkMarkdownNode) {
                            const rawThought = this.converter.convert(thinkMarkdownNode);
                            finalMarkdown += `> &#128173; **DeepThinking...**\n>\n`;
                            finalMarkdown += rawThought.split('\n').map(line => `> ${line}`).join('\n');
                            finalMarkdown += `\n\n---\n\n`;
                        }
                    }
 
                    const allMarkdownNodes = Array.from(node.querySelectorAll(this.selectors.aiContent));
                    const contentNode = allMarkdownNodes.find(n => !n.closest(this.selectors.thinkContent));
 
                    if (contentNode) {
                        finalMarkdown += this.converter.convert(contentNode);
                    }
 
                    messages.push({ role: 'DeepSeek', content: finalMarkdown });
 
                } else {
                    messages.push({ role: 'User', content: node.innerText.trim() });
                }
            });
 
            return messages;
        }
    }
 
    class ExporterUI {
        constructor() {
            this.parser = new DeepseekParser();
            this.initButton();
        }
 
        initButton() {
            if (document.getElementById('ds-export-btn')) return;
            const btn = document.createElement('button');
            btn.id = 'ds-export-btn';
 
            btn.innerHTML = `<svg class="ds-export-icon" viewBox="0 0 24 24"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4M7 10l5 5 5-5M12 15V3"/></svg>`;
 
            btn.title = "Export Chat to Markdown";
 
            btn.onclick = () => this.exportChat();
            document.body.appendChild(btn);
        }
 
        async exportChat() {
            const messages = this.parser.getAllMessages();
            if (messages.length === 0) {
                alert('未找到对话记录,请确保页面已加载完毕。');
                return;
            }
 
            const title = this.parser.getChatTitle();
            let markdown = `# ${title}\n\n`;
 
            messages.forEach((msg, index) => {
                const roleTitle = msg.role === 'DeepSeek' ? '&#129302; DeepSeek' : '&#128100; User';
                markdown += `## ${roleTitle}\n\n`;
                markdown += `${msg.content}\n\n`;
 
                if (msg.role === 'DeepSeek' && index < messages.length - 1) {
                    markdown += `---\n\n`;
                }
            });
 
            this.downloadFile(markdown, title);
        }
 
        downloadFile(content, title) {
            const now = new Date();
            const timestamp = now.toISOString().replace(/[-:T]/g, '').slice(0, 14);
            const safeTitle = title.replace(/[\\/:*?"<>|]/g, '_').substring(0, 50);
            const filename = `${safeTitle}_${timestamp}.md`;
            const blob = new Blob([content], { type: 'text/markdown' });
            const url = URL.createObjectURL(blob);
            const a = document.createElement('a');
            a.href = url;
            a.download = filename;
            document.body.appendChild(a);
            a.click();
            document.body.removeChild(a);
            URL.revokeObjectURL(url);
        }
    }
 
    setTimeout(() => {
        new ExporterUI();
    }, 2000);
 
})();

免费评分

参与人数 7吾爱币 +12 热心值 +6 收起 理由
menoooooos + 1 + 1 我很赞同!
苏紫方璇 + 7 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
lHHao + 1 谢谢@Thanks!
Vincent93 + 1 谢谢@Thanks!
SherlockProel + 1 + 1 感谢老哥,爱用多发
CGR + 1 + 1 用心讨论,共获提升!
helian147 + 1 + 1 热心回复!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

pyjiujiu 发表于 2026-4-14 19:54
本帖最后由 pyjiujiu 于 2026-4-15 15:21 编辑

ds 每问一个问题,都会隐藏 虚拟列表的之前元素,,要保存全部对话,可以手动滑上去 ,再保存(尝试优化脚本,还是没搞定)
pyjiujiu 发表于 2026-4-15 21:43
本帖最后由 pyjiujiu 于 2026-4-16 11:40 编辑
xiawan 发表于 2026-4-15 19:03
https://github.com/endolith/DeepSeek-Chat-Exporter
可以使用这个,作者一直更新。

谢推荐,等 ds v4版 出来 再研究研究 ,,最近  web 界面一直在迭代,v4 版出来 可能才会稳定 (@xiawan)
akhh7992hdd 发表于 2026-3-30 18:56
xjzhangjun 发表于 2026-3-30 19:06
谢谢大佬分享
小鸟会飞 发表于 2026-3-30 20:11
好用吗 非常感谢
屋檐风飘过 发表于 2026-3-30 20:21
谢谢大佬的分享!
lmyx2008 发表于 2026-3-30 20:45
又见大佬
tshxxst 发表于 2026-3-30 21:11
太厉害了,请详细讲讲怎么用
INSQK 发表于 2026-3-30 22:15
谢谢分析
jeonlee 发表于 2026-3-30 22:18
非常有用&#4314;(′&#9673;&#10085;&#9673;`&#4314;)感谢大佬分享
xiadaxue 发表于 2026-3-30 22:19
大佬就是厉害啊,学习了
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - 52pojie.cn ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2026-5-16 04:54

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表