Note:copy below script code and paste in console window for which chat gpt chat history you want.then enter.
(async () => {
"use strict";
const SCROLL_STEP_RATIO = 1.8;
const MAX_SCROLL_PASSES = 220;
const IDLE_PASSES_TO_STOP = 4;
const PDF_LIB_URL = "https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.10.1/html2pdf.bundle.min.js";
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
function createProgressPanel() {
const panel = document.createElement("div");
panel.style.position = "fixed";
panel.style.top = "18px";
panel.style.right = "18px";
panel.style.zIndex = "2147483647";
panel.style.width = "320px";
panel.style.background = "#ffffff";
panel.style.border = "1px solid #d8dee8";
panel.style.borderRadius = "14px";
panel.style.boxShadow = "0 12px 30px rgba(15, 23, 42, 0.18)";
panel.style.padding = "14px";
panel.style.font = '13px/1.4 "Segoe UI", Arial, sans-serif';
panel.style.color = "#111827";
const title = document.createElement("div");
title.textContent = "Loading conversation...";
title.style.fontWeight = "700";
const status = document.createElement("div");
status.textContent = "Preparing fast auto-scroll";
status.style.marginTop = "6px";
status.style.color = "#4b5563";
const cancel = document.createElement("button");
cancel.type = "button";
cancel.textContent = "Cancel";
cancel.style.marginTop = "12px";
cancel.style.border = "1px solid #cbd5e1";
cancel.style.background = "#f8fafc";
cancel.style.borderRadius = "8px";
cancel.style.padding = "6px 12px";
cancel.style.cursor = "pointer";
panel.appendChild(title);
panel.appendChild(status);
panel.appendChild(cancel);
document.body.appendChild(panel);
const state = { cancelled: false };
cancel.addEventListener("click", () => {
state.cancelled = true;
status.textContent = "Cancelling...";
cancel.disabled = true;
});
return {
isCancelled: () => state.cancelled,
update: (text) => {
status.textContent = text;
},
done: () => {
panel.remove();
}
};
}
function loadScript(url) {
return new Promise((resolve, reject) => {
const existing = Array.from(document.querySelectorAll("script")).find((s) => s.src === url);
if (existing && window.html2pdf) {
resolve();
return;
}
const script = document.createElement("script");
script.src = url;
script.async = true;
script.onload = () => resolve();
script.onerror = () => reject(new Error(`Failed to load script: ${url}`));
document.head.appendChild(script);
});
}
function chooseExportFormat() {
const raw = (prompt("Export format? Type WORD or PDF", "WORD") || "WORD").trim().toLowerCase();
return raw === "pdf" ? "pdf" : "word";
}
function chooseExportScope() {
const help = [
"Export scope?",
"1 = Complete conversation",
"2 = Single ChatGPT response (latest)",
"3 = Selected messages (highlight text first)",
"4 = ChatGPT responses only"
].join("\n");
const raw = (prompt(help, "1") || "1").trim().toLowerCase();
if (raw === "2" || raw === "single") {
return "single";
}
if (raw === "3" || raw === "selected") {
return "selected";
}
if (raw === "4" || raw === "assistant" || raw === "chatgpt") {
return "assistant";
}
return "all";
}
function getSelectionMessageNodes() {
const selection = window.getSelection();
if (!selection || !selection.rangeCount || selection.isCollapsed) {
return [];
}
const range = selection.getRangeAt(0);
const all = Array.from(document.querySelectorAll("[data-message-author-role]"));
return all.filter((node) => {
try {
return range.intersectsNode(node);
} catch (_error) {
return false;
}
});
}
function normalizeText(text) {
return (text || "").replace(/\r\n/g, "\n").trim();
}
function cleanFileName(name) {
return (name || "chatgpt-chat")
.replace(/[\\/:*?"<>|]/g, "-")
.replace(/\s+/g, " ")
.trim()
.slice(0, 80);
}
function normalizePossibleTitle(value) {
return (value || "")
.replace(/\s+/g, " ")
.replace(/\s*-\s*ChatGPT\s*$/i, "")
.trim();
}
function pickBestTitle(candidates) {
const cleaned = candidates
.map((x) => normalizePossibleTitle(x))
.filter((x) => x && x.length > 1 && !/^chatgpt$/i.test(x));
if (!cleaned.length) {
return "chatgpt-chat";
}
// Prefer longer descriptive titles over short/generic labels.
cleaned.sort((a, b) => b.length - a.length);
return cleaned[0];
}
function resolveConversationTitle() {
const titleCandidates = [];
// Browser title is usually the most reliable conversation title.
titleCandidates.push(document.title || "");
const metaOg = document.querySelector('meta[property="og:title"]')?.getAttribute("content") || "";
const metaTwitter = document.querySelector('meta[name="twitter:title"]')?.getAttribute("content") || "";
titleCandidates.push(metaOg, metaTwitter);
const selectorCandidates = [
'[data-testid="conversation-title"]',
'a[aria-current="page"]',
'nav [aria-current="page"]',
'main header h1',
'header h1'
];
selectorCandidates.forEach((selector) => {
const el = document.querySelector(selector);
if (el) {
titleCandidates.push(el.textContent || "", el.getAttribute("title") || "");
}
});
return pickBestTitle(titleCandidates);
}
function escapeHtml(input) {
return (input || "")
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/\"/g, """)
.replace(/'/g, "'");
}
function blobToDataUrl(blob) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(reader.result);
reader.onerror = () => reject(new Error("Failed to read image blob."));
reader.readAsDataURL(blob);
});
}
async function imageUrlToDataUrl(url) {
if (!url) {
throw new Error("Image URL is empty.");
}
if (url.startsWith("data:")) {
return url;
}
const response = await fetch(url, { credentials: "include" });
if (!response.ok) {
throw new Error(`Image fetch failed with ${response.status}`);
}
const blob = await response.blob();
return blobToDataUrl(blob);
}
function imageElementToDataUrl(imgEl) {
try {
if (!imgEl || !imgEl.naturalWidth || !imgEl.naturalHeight) {
return null;
}
const canvas = document.createElement("canvas");
canvas.width = imgEl.naturalWidth;
canvas.height = imgEl.naturalHeight;
const ctx = canvas.getContext("2d");
if (!ctx) {
return null;
}
ctx.drawImage(imgEl, 0, 0);
return canvas.toDataURL("image/png");
} catch (_error) {
return null;
}
}
function getImageSource(imgEl) {
if (!imgEl) {
return "";
}
return (
imgEl.currentSrc ||
imgEl.getAttribute("src") ||
imgEl.getAttribute("data-src") ||
imgEl.getAttribute("data-original") ||
""
);
}
function getScrollableContainer() {
const all = Array.from(document.querySelectorAll("div, main, section"));
const candidates = all.filter((el) => {
const style = window.getComputedStyle(el);
const canScroll = /(auto|scroll)/.test(style.overflowY);
return canScroll && el.scrollHeight > el.clientHeight + 120;
});
candidates.sort((a, b) => b.scrollHeight - a.scrollHeight);
return candidates[0] || document.scrollingElement || document.documentElement;
}
function textHash(input) {
const str = input || "";
let hash = 0;
for (let i = 0; i < str.length; i += 1) {
hash = (hash << 5) - hash + str.charCodeAt(i);
hash |= 0;
}
return String(hash);
}
function getStableMessageId(node) {
return (
node.getAttribute("data-message-id") ||
node.getAttribute("data-testid") ||
node.getAttribute("id") ||
""
);
}
function getNodeFingerprint(node) {
const role = (node.getAttribute("data-message-author-role") || "").toLowerCase();
const stableId = getStableMessageId(node);
const text = normalizeText(node.innerText).replace(/\s+/g, " ");
const shortText = text.slice(0, 1000);
const imageCount = node.querySelectorAll("img").length;
return `${role}|id:${stableId}|txt:${textHash(shortText)}|img:${imageCount}`;
}
function collectVisibleMessages(store, sequenceState) {
const visibleNodes = Array.from(document.querySelectorAll("[data-message-author-role]"));
visibleNodes.forEach((node) => {
const key = getNodeFingerprint(node);
const existing = store.get(key);
if (!existing) {
store.set(key, {
key,
node,
firstSeen: sequenceState.value
});
sequenceState.value += 1;
} else {
existing.node = node;
}
});
}
async function collectEntireConversation(progress) {
const container = getScrollableContainer();
const messagesMap = new Map();
if (!container) {
return [];
}
// Start at top and collect strictly while moving top -> bottom.
progress.update("Phase 1/1: Jumping to top...");
container.scrollTop = 0;
await sleep(140);
const step = Math.max(200, Math.floor(container.clientHeight * SCROLL_STEP_RATIO));
// Collect while sweeping top -> bottom so ordering remains stable.
progress.update("Phase 1/1: Sweeping top to bottom...");
const sequenceState = { value: 0 };
let idlePasses = 0;
let previousCount = -1;
let previousScroll = -1;
for (let pass = 0; pass < MAX_SCROLL_PASSES; pass += 1) {
if (progress.isCancelled()) {
return [];
}
collectVisibleMessages(messagesMap, sequenceState);
if (pass % 8 === 0) {
progress.update(`Phase 1/1: Sweeping... ${messagesMap.size} messages captured`);
}
const currentCount = messagesMap.size;
if (currentCount === previousCount) {
idlePasses += 1;
} else {
idlePasses = 0;
}
previousCount = currentCount;
const beforeTop = container.scrollTop;
container.scrollTop = Math.min(container.scrollHeight, beforeTop + step);
await sleep(40);
const atBottom = container.scrollTop + container.clientHeight >= container.scrollHeight - 4;
const stuck = container.scrollTop === previousScroll;
previousScroll = container.scrollTop;
if (atBottom) {
collectVisibleMessages(messagesMap, sequenceState);
await sleep(100);
collectVisibleMessages(messagesMap, sequenceState);
if (idlePasses >= IDLE_PASSES_TO_STOP || stuck) {
break;
}
}
}
const collected = Array.from(messagesMap.values());
collected.sort((a, b) => a.firstSeen - b.firstSeen);
return collected.map((x) => x.node);
}
function getMessageContentNode(messageNode) {
const candidates = [
".markdown",
".prose",
".whitespace-pre-wrap",
"[data-testid='conversation-turn-content']"
];
for (const selector of candidates) {
const el = messageNode.querySelector(selector);
if (el && normalizeText(el.innerText)) {
return el;
}
}
return messageNode;
}
async function buildMessagePayload(messageNode) {
const contentNode = getMessageContentNode(messageNode);
const text = normalizeText(contentNode.innerText);
const clonedContent = contentNode.cloneNode(true);
const clonedImages = Array.from(clonedContent.querySelectorAll("img"));
const sourceImages = Array.from(contentNode.querySelectorAll("img"));
await Promise.all(
clonedImages.map(async (img, index) => {
const srcImg = sourceImages[index];
const src = getImageSource(srcImg) || getImageSource(img);
if (!src) {
img.remove();
return;
}
try {
const dataUrl = await imageUrlToDataUrl(src);
img.setAttribute("src", dataUrl);
} catch (_error) {
const drawnData = imageElementToDataUrl(srcImg || img);
if (drawnData) {
img.setAttribute("src", drawnData);
} else {
// Keep original source as fallback if inlining fails.
img.setAttribute("src", src);
}
}
img.removeAttribute("srcset");
img.removeAttribute("sizes");
img.setAttribute("loading", "eager");
})
);
const html = clonedContent.innerHTML && clonedContent.innerHTML.trim().length
? clonedContent.innerHTML
: escapeHtml(contentNode.innerText).replace(/\n/g, "<br>");
return { text, html };
}
function filterMessagesByScope(messageNodes, scope) {
if (scope === "assistant") {
return messageNodes.filter(
(node) => (node.getAttribute("data-message-author-role") || "").toLowerCase() === "assistant"
);
}
if (scope === "single") {
const latestAssistant = [...messageNodes]
.reverse()
.find((node) => (node.getAttribute("data-message-author-role") || "").toLowerCase() === "assistant");
return latestAssistant ? [latestAssistant] : [];
}
if (scope === "selected") {
const selectedNodes = getSelectionMessageNodes();
if (selectedNodes.length) {
return selectedNodes;
}
alert("No selected messages found. Please highlight message text first. Exporting complete conversation instead.");
return messageNodes;
}
return messageNodes;
}
function buildMetaLabel(scope) {
if (scope === "assistant") {
return "ChatGPT responses only";
}
if (scope === "single") {
return "Single ChatGPT response";
}
if (scope === "selected") {
return "Selected messages";
}
return "Complete conversation";
}
function scopeSuffix(scope) {
if (scope === "assistant") {
return "assistant-only";
}
if (scope === "single") {
return "single-response";
}
if (scope === "selected") {
return "selected";
}
return "full-chat";
}
async function exportAsPdf(htmlDoc, fileNameBase) {
await loadScript(PDF_LIB_URL);
if (!window.html2pdf) {
throw new Error("PDF engine did not load.");
}
const holder = document.createElement("div");
holder.style.position = "fixed";
holder.style.left = "-100000px";
holder.style.top = "0";
holder.style.width = "1280px";
holder.style.background = "#ffffff";
holder.innerHTML = htmlDoc;
document.body.appendChild(holder);
const target = holder.querySelector("body") || holder;
const opt = {
margin: [8, 8, 8, 8],
filename: `${fileNameBase}.pdf`,
image: { type: "jpeg", quality: 0.98 },
html2canvas: { scale: 2, useCORS: true, allowTaint: true },
jsPDF: { unit: "mm", format: "a4", orientation: "portrait" },
pagebreak: { mode: ["css", "legacy"] }
};
await window.html2pdf().set(opt).from(target).save();
holder.remove();
}
function exportAsWord(htmlDoc, fileNameBase) {
const blob = new Blob([htmlDoc], { type: "application/msword;charset=utf-8" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = `${fileNameBase}.doc`;
document.body.appendChild(a);
a.click();
a.remove();
URL.revokeObjectURL(url);
return a.download;
}
const exportFormat = chooseExportFormat();
const exportScope = chooseExportScope();
const progress = createProgressPanel();
const collectedNodes = await collectEntireConversation(progress);
progress.done();
if (!collectedNodes.length) {
alert("Export cancelled or no messages collected.");
return;
}
const messageNodes = filterMessagesByScope(collectedNodes, exportScope);
if (!messageNodes.length) {
alert("No messages matched your export scope.");
return;
}
const entriesRaw = await Promise.all(
messageNodes.map(async (node) => {
const roleRaw = (node.getAttribute("data-message-author-role") || "").toLowerCase();
const role = roleRaw === "assistant" ? "ChatGPT" : roleRaw === "user" ? "You" : "System";
const payload = await buildMessagePayload(node);
return { role, text: payload.text, html: payload.html, roleRaw };
})
);
const entries = entriesRaw.filter((x) => x.text.length > 0);
if (!entries.length) {
alert("Messages were found, but text content could not be extracted.");
return;
}
const title = cleanFileName(resolveConversationTitle());
const now = new Date();
const stamp = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-${String(
now.getDate()
).padStart(2, "0")}`;
const scopeName = buildMetaLabel(exportScope);
const fileNameBase = `${title}_${scopeSuffix(exportScope)}_${stamp}`;
const messagesHtml = entries
.map((e) => {
const roleClass = e.roleRaw === "user" ? "user" : e.roleRaw === "assistant" ? "assistant" : "system";
return `
<article class="msg ${roleClass}">
<div class="role">${escapeHtml(e.role)}</div>
<div class="content">${e.html}</div>
</article>
`;
})
.join("\n");
const htmlDoc = `<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>${escapeHtml(title)}</title>
<style>
:root {
--page-bg: #f3f6fb;
--ink: #1f2937;
--muted: #5b6470;
--card: #ffffff;
--user-accent: #2864dc;
--assistant-accent: #16a085;
--system-accent: #7a5ccf;
--code-bg: #f1f3f6;
--code-ink: #1a1f2a;
--line: #dde2ea;
}
* { box-sizing: border-box; }
body {
margin: 0;
color: var(--ink);
font: 15px/1.6 "Segoe UI", Tahoma, Arial, sans-serif;
background:
radial-gradient(circle at 10% 0%, #ffffff 0%, #eaf0ff 35%, transparent 60%),
radial-gradient(circle at 100% 10%, #e8fff8 0%, transparent 35%),
var(--page-bg);
padding: 28px;
}
.sheet {
max-width: 900px;
margin: 0 auto;
background: var(--card);
border: 1px solid var(--line);
border-radius: 18px;
box-shadow: 0 12px 35px rgba(12, 22, 44, 0.08);
padding: 28px;
}
.head h1 {
margin: 0;
font-size: 26px;
line-height: 1.25;
}
.meta {
margin-top: 6px;
color: var(--muted);
font-size: 13px;
}
.meta + .meta {
margin-top: 2px;
}
.timeline {
margin-top: 22px;
display: grid;
gap: 14px;
}
.msg {
border: 1px solid var(--line);
border-left-width: 6px;
border-radius: 14px;
padding: 14px 16px;
background: #fff;
}
.msg.user { border-left-color: var(--user-accent); background: #f8fbff; }
.msg.assistant { border-left-color: var(--assistant-accent); }
.msg.system { border-left-color: var(--system-accent); background: #fbfaff; }
.role {
font-size: 12px;
letter-spacing: 0.05em;
text-transform: uppercase;
font-weight: 700;
color: var(--muted);
margin-bottom: 8px;
}
.content h1, .content h2, .content h3, .content h4 {
margin: 0.8em 0 0.35em;
line-height: 1.3;
}
.content h1 { font-size: 24px; }
.content h2 { font-size: 21px; }
.content h3 { font-size: 18px; }
.content h4 { font-size: 16px; }
.content p { margin: 0.4em 0 0.75em; }
.content ul, .content ol { margin: 0.4em 0 0.9em 1.4em; }
.content li { margin: 0.22em 0; }
.content blockquote {
margin: 0.8em 0;
border-left: 4px solid #c5d4f2;
padding: 0.3em 0.8em;
color: #3e4b63;
background: #f8fbff;
}
.content pre {
margin: 0.8em 0;
background: var(--code-bg);
color: var(--code-ink);
border: 1px solid #d7dde7;
border-radius: 12px;
padding: 12px 14px;
overflow: auto;
white-space: pre-wrap;
word-break: break-word;
font: 13px/1.5 Consolas, "Courier New", monospace;
}
.content code {
background: #eef2f8;
border: 1px solid #d8dfe9;
border-radius: 6px;
padding: 1px 5px;
font: 12px Consolas, "Courier New", monospace;
}
.content pre code {
background: transparent;
border: none;
border-radius: 0;
padding: 0;
font: inherit;
}
.content table {
border-collapse: collapse;
margin: 0.8em 0;
width: 100%;
}
.content img {
display: block;
max-width: 100%;
height: auto;
margin: 0.7em 0;
border: 1px solid #d7dde7;
border-radius: 10px;
}
.content th,
.content td {
border: 1px solid var(--line);
padding: 6px 8px;
text-align: left;
vertical-align: top;
}
</style>
</head>
<body>
<main class="sheet">
<header class="head">
<h1>${escapeHtml(title)}</h1>
<div class="meta">Exported on ${escapeHtml(now.toLocaleString())}</div>
<div class="meta">Scope: ${escapeHtml(scopeName)} | Format: ${escapeHtml(exportFormat.toUpperCase())}</div>
</header>
<section class="timeline">
${messagesHtml}
</section>
</main>
</body>
</html>`;
let downloadName = "";
if (exportFormat === "pdf") {
await exportAsPdf(htmlDoc, fileNameBase);
downloadName = `${fileNameBase}.pdf`;
} else {
downloadName = exportAsWord(htmlDoc, fileNameBase);
}
const container = getScrollableContainer();
if (container) {
container.scrollTop = container.scrollHeight;
}
console.log(`Export complete: ${downloadName}`);
alert(`Exported ${entries.length} messages to ${downloadName}`);
})();