Created
December 12, 2025 12:36
-
-
Save LukasMFR/6865ef67aee37a8c677928234072bfbf to your computer and use it in GitHub Desktop.
JavaScript snippet to export a ChatGPT conversation from the web UI to a clean Markdown file, with correct user/assistant attribution, code block preservation, and basic media placeholders. Designed to be run directly in the browser console (Safari/Chrome/Firefox).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| (() => { | |
| function formatDate(date = new Date()) { | |
| return date.toISOString().split("T")[0]; | |
| } | |
| function escapeMarkdown(text) { | |
| return text | |
| .replace(/\\/g, "\\\\") | |
| .replace(/\*/g, "\\*") | |
| .replace(/_/g, "\\_") | |
| .replace(/`/g, "\\`") | |
| .replace(/\n{3,}/g, "\n\n"); | |
| } | |
| function processMessageContent(element) { | |
| const clone = element.cloneNode(true); | |
| // Replace <pre><code> blocks | |
| clone.querySelectorAll("pre").forEach((pre) => { | |
| const code = pre.innerText.trim(); | |
| const langMatch = pre | |
| .querySelector("code") | |
| ?.className?.match(/language-([a-zA-Z0-9]+)/); | |
| const lang = langMatch ? langMatch[1] : ""; | |
| pre.replaceWith(`\n\n\`\`\`${lang}\n${code}\n\`\`\`\n`); | |
| }); | |
| // Replace images and canvas with placeholders | |
| clone.querySelectorAll("img, canvas").forEach((el) => { | |
| el.replaceWith("[Image or Canvas]"); | |
| }); | |
| return escapeMarkdown(clone.innerText.trim()); | |
| } | |
| function getUserDisplayName() { | |
| // Best-effort attempts (depends on UI versions) | |
| const candidates = [ | |
| '[data-testid="user-menu-button"]', | |
| 'button[aria-label*="Account"]', | |
| 'button[aria-label*="account"]', | |
| 'button[aria-label*="Profil"]', | |
| 'button[aria-label*="Profile"]', | |
| ]; | |
| for (const sel of candidates) { | |
| const el = document.querySelector(sel); | |
| const txt = el?.innerText?.trim(); | |
| if (txt && txt.length >= 2 && txt.length <= 40) return txt; | |
| } | |
| // Fallback: ask once | |
| const asked = (window.__CHAT_EXPORT_USERNAME__ || "").trim(); | |
| if (asked) return asked; | |
| const name = prompt( | |
| "What first name / display name do you want to use for YOUR messages?", | |
| "You" | |
| ); | |
| window.__CHAT_EXPORT_USERNAME__ = (name || "You").trim(); | |
| return window.__CHAT_EXPORT_USERNAME__ || "You"; | |
| } | |
| // 1) Robust selection of messages | |
| let messageNodes = Array.from(document.querySelectorAll('[data-message-author-role]')); | |
| // Fallback if the attribute does not exist (different UI) | |
| if (messageNodes.length === 0) { | |
| messageNodes = Array.from(document.querySelectorAll('div[class*="group"]')); | |
| } | |
| const userName = getUserDisplayName(); | |
| const lines = []; | |
| const title = document.title?.trim() || "Conversation with ChatGPT"; | |
| const date = formatDate(); | |
| const url = window.location.href; | |
| lines.push(`# ${title}\n`); | |
| lines.push(`**Date:** ${date}`); | |
| lines.push(`**Source:** [chat.openai.com](${url})\n`); | |
| lines.push(`---\n`); | |
| messageNodes.forEach((node) => { | |
| // 2) Reliable role detection | |
| let role = node.getAttribute?.("data-message-author-role"); | |
| // If we're on the "group" fallback, try to find a parent that has the role | |
| if (!role) { | |
| const parentWithRole = node.closest?.('[data-message-author-role]'); | |
| role = parentWithRole?.getAttribute?.("data-message-author-role"); | |
| } | |
| const sender = | |
| role === "user" ? userName : | |
| role === "assistant" ? "ChatGPT" : | |
| role ? role : "Unknown"; | |
| // 3) Content extraction | |
| const block = | |
| node.querySelector?.('[data-message-content]') || | |
| node.querySelector?.(".markdown, .prose, .whitespace-pre-wrap"); | |
| if (!block) return; | |
| const content = processMessageContent(block); | |
| if (!content) return; | |
| lines.push(`### **${sender}**\n`); | |
| lines.push(content); | |
| lines.push("\n---\n"); | |
| }); | |
| const markdown = lines.join("\n").trim(); | |
| const blob = new Blob([markdown], { type: "text/markdown" }); | |
| const a = document.createElement("a"); | |
| a.download = `ChatGPT_Conversation_${date}.md`; | |
| a.href = URL.createObjectURL(blob); | |
| document.body.appendChild(a); | |
| a.click(); | |
| document.body.removeChild(a); | |
| })(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment