ChatGPT Markdown Export

Export ChatGPT conversations to clean, readable Markdown with proper role labels and preserved code formatting.

当前为 2025-06-13 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name ChatGPT Markdown Export
  3. // @namespace https://github.com/dankCodeNugs/greasyScripts
  4. // @version 1.0.0
  5. // @description Export ChatGPT conversations to clean, readable Markdown with proper role labels and preserved code formatting.
  6. // @author Anonymous
  7. // @match https://chat.openai.com/*
  8. // @match https://chatgpt.com/*
  9. // @license Dual license anti-copyleft & BSD-2-Clause
  10. // @grant none
  11. // ==/UserScript==
  12.  
  13. (function () {
  14. 'use strict';
  15.  
  16. const BUTTON_ID = 'export-md-button';
  17.  
  18. const style = `
  19. #${BUTTON_ID} {
  20. position: fixed;
  21. top: 10px;
  22. left: 50%;
  23. transform: translateX(-50%);
  24. z-index: 9999;
  25. padding: 6px 14px;
  26. font-size: 14px;
  27. font-weight: 500;
  28. background: rgba(255, 255, 255, 0.1);
  29. color: #fff;
  30. border: 1px solid rgba(255, 255, 255, 0.4);
  31. border-radius: 6px;
  32. backdrop-filter: blur(6px);
  33. cursor: pointer;
  34. transition: background 0.2s, color 0.2s;
  35. }
  36. #${BUTTON_ID}:hover {
  37. background: rgba(255, 255, 255, 0.2);
  38. color: #000;
  39. }
  40. `;
  41.  
  42. function injectStyle() {
  43. const tag = document.createElement('style');
  44. tag.textContent = style;
  45. document.head.appendChild(tag);
  46. }
  47.  
  48. function sanitizeClone(el) {
  49. const clone = el.cloneNode(true);
  50. clone.querySelectorAll('button, .copy-button, .absolute, .overflow-hidden, svg').forEach(e => e.remove());
  51. return clone;
  52. }
  53.  
  54. function extractMarkdownFromElement(el) {
  55. const blocks = [];
  56. const walker = document.createTreeWalker(el, NodeFilter.SHOW_ELEMENT, null, false);
  57.  
  58. while (walker.nextNode()) {
  59. const node = walker.currentNode;
  60.  
  61. if (node.tagName === 'PRE' && node.querySelector('code')) {
  62. const code = node.querySelector('code').textContent.trim();
  63. blocks.push("```");
  64. blocks.push(code);
  65. blocks.push("```");
  66. walker.currentNode = node;
  67. } else if (node.tagName === 'P') {
  68. const text = node.textContent.trim();
  69. if (text.length > 0) {
  70. blocks.push(text);
  71. }
  72. }
  73. }
  74.  
  75. if (blocks.length === 0) {
  76. blocks.push(el.innerText.trim());
  77. }
  78.  
  79. return blocks.join('\n\n');
  80. }
  81.  
  82. function getSpeakerFromRole(role) {
  83. if (role === 'user') return 'User';
  84. if (role === 'assistant') return 'ChatGPT';
  85. return 'Unknown';
  86. }
  87.  
  88. function collectMessages() {
  89. const transcript = [];
  90.  
  91. const messages = document.querySelectorAll('main div[data-message-author-role]');
  92.  
  93. messages.forEach(msg => {
  94. const role = msg.getAttribute('data-message-author-role');
  95. const speaker = getSpeakerFromRole(role);
  96.  
  97. let content = '';
  98. const markdownEl = msg.querySelector('.markdown, .prose');
  99. const plainEl = msg.querySelector('.whitespace-pre-wrap');
  100.  
  101. if (markdownEl) {
  102. const clean = sanitizeClone(markdownEl);
  103. content = extractMarkdownFromElement(clean);
  104. } else if (plainEl) {
  105. content = plainEl.textContent.trim();
  106. }
  107.  
  108. if (content && content.length > 0) {
  109. transcript.push(`### ${speaker}:\n\n${content}`);
  110. }
  111. });
  112.  
  113. return transcript.join('\n\n---\n\n');
  114. }
  115.  
  116. function downloadMarkdown(content) {
  117. const blob = new Blob([content], { type: 'text/markdown' });
  118. const url = URL.createObjectURL(blob);
  119. const a = document.createElement('a');
  120. const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
  121. a.href = url;
  122. a.download = `chatgpt-session-${timestamp}.md`;
  123. document.body.appendChild(a);
  124. a.click();
  125. document.body.removeChild(a);
  126. URL.revokeObjectURL(url);
  127. }
  128.  
  129. function addExportButton() {
  130. const btn = document.createElement('button');
  131. btn.id = BUTTON_ID;
  132. btn.textContent = 'Export to MD';
  133. btn.addEventListener('click', () => {
  134. const md = collectMessages();
  135. if (md.length === 0) {
  136. alert('No messages found.');
  137. return;
  138. }
  139. downloadMarkdown(md);
  140. });
  141. document.body.appendChild(btn);
  142. }
  143.  
  144. const waitForMain = setInterval(() => {
  145. const main = document.querySelector('main');
  146. if (main && !document.getElementById(BUTTON_ID)) {
  147. clearInterval(waitForMain);
  148. injectStyle();
  149. addExportButton();
  150. }
  151. }, 500);
  152. })();

QingJ © 2025

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