ChatGPT Export to Markdown (Enhanced with Data)

Export ChatGPT conversation to a Markdown file across multiple sites with correct content extraction.

  1. // ==UserScript==
  2. // @name ChatGPT Export to Markdown (Enhanced with Data)
  3. // @namespace https://chatgpt.com
  4. // @version 1.3
  5. // @description Export ChatGPT conversation to a Markdown file across multiple sites with correct content extraction.
  6. // @match https://chat.openai.com/*
  7. // @match https://chatgpt.com/*
  8. // @match https://*.chatgpt.com/*
  9. // @require https://cdn.jsdelivr.net/npm/@kudoai/chatgpt.js@3.2.0/dist/chatgpt.min.js
  10. // @grant none
  11. // ==/UserScript==
  12.  
  13. (async () => {
  14. 'use strict';
  15.  
  16. // Ensure chatgpt.js is loaded
  17. await chatgpt.isLoaded();
  18.  
  19. // Function to create and insert the export button
  20. const createExportButton = () => {
  21. // Check if the button already exists
  22. if (document.getElementById('export-to-markdown-btn')) {
  23. return;
  24. }
  25.  
  26. // Create and style the export button
  27. const button = document.createElement('button');
  28. button.id = 'export-to-markdown-btn';
  29. button.innerHTML = 'Export to Markdown';
  30. button.style.position = 'fixed';
  31. button.style.bottom = '20px';
  32. button.style.right = '20px';
  33. button.style.padding = '10px';
  34. button.style.zIndex = '10000'; // High zIndex to ensure visibility
  35. button.style.backgroundColor = '#4CAF50';
  36. button.style.color = '#fff';
  37. button.style.border = 'none';
  38. button.style.borderRadius = '5px';
  39. button.style.cursor = 'pointer';
  40. document.body.appendChild(button);
  41.  
  42. console.log('Export button added.');
  43.  
  44. // Button click handler to export conversation
  45. button.addEventListener('click', async () => {
  46. try {
  47. // Fetch the current conversation data
  48. const chatData = await chatgpt.getChatData("active", ["msg"], "both");
  49. const chatTitleData = await chatgpt.getChatData("active", "title");
  50.  
  51. // Use the chat's title or a fallback
  52. const chatTitle = chatTitleData.title || 'ChatGPT_Conversation';
  53. const sanitizedTitle = chatTitle.replace(/[<>:"/\\|?*]+/g, ''); // Remove invalid filename characters
  54.  
  55. // Convert the conversation data to a markdown format
  56. let markdownContent = `# ${chatTitle}\n\n`;
  57. chatData.forEach((msg, index) => {
  58. markdownContent += `### Message ${index + 1}\n\n`;
  59. markdownContent += `**User:**\n\n${msg.user}\n\n`;
  60. markdownContent += `**ChatGPT:**\n\n${msg.chatgpt}\n\n---\n`;
  61. });
  62.  
  63. // Create a Blob object from the markdown content
  64. const blob = new Blob([markdownContent], { type: 'text/markdown' });
  65.  
  66. // Create a link element to download the markdown file
  67. const link = document.createElement('a');
  68. link.href = URL.createObjectURL(blob);
  69. link.download = `${sanitizedTitle}.md`;
  70. link.click();
  71.  
  72. // Notify the user
  73. chatgpt.notify('✅ Exported to Markdown successfully!', 'topRight', 2);
  74. } catch (error) {
  75. chatgpt.notify('❌ Failed to export conversation!', 'topRight', 2);
  76. console.error('Export to Markdown failed:', error);
  77. }
  78. });
  79. };
  80.  
  81. // MutationObserver to ensure button is always present
  82. const observer = new MutationObserver(() => {
  83. createExportButton(); // Ensure the button is recreated if it's removed
  84. });
  85.  
  86. // Start observing changes in the body
  87. observer.observe(document.body, { childList: true, subtree: true });
  88.  
  89. // Initial delay before inserting the button to ensure the page is fully loaded
  90. setTimeout(createExportButton, 3000); // Delay by 3 seconds to ensure all dynamic content is loaded
  91. })();