Export ChatGPT/Gemini/Grok conversations as Markdown

Export chat history from ChatGPT and Grok websites to normal format as Markdown, which can be opened via typora exactly.

Size

26.7 KB

Version

1.1.2

Created

Oct 9, 2025

Updated

14 days ago

1// ==UserScript==
2// @name		Export ChatGPT/Gemini/Grok conversations as Markdown
3// @name:zh-CN		Export ChatGPT/Gemini/Grok conversations as Markdown
4// @name:zh-TW		Export ChatGPT/Gemini/Grok conversations as Markdown
5// @name:ar		Export ChatGPT/Gemini/Grok conversations as Markdown
6// @name:bg		Export ChatGPT/Gemini/Grok conversations as Markdown
7// @name:cs		Export ChatGPT/Gemini/Grok conversations as Markdown
8// @name:da		Export ChatGPT/Gemini/Grok conversations as Markdown
9// @name:de		Export ChatGPT/Gemini/Grok conversations as Markdown
10// @name:el		Export ChatGPT/Gemini/Grok conversations as Markdown
11// @name:en		Export ChatGPT/Gemini/Grok conversations as Markdown
12// @name:eo		Export ChatGPT/Gemini/Grok conversations as Markdown
13// @name:es		Export ChatGPT/Gemini/Grok conversations as Markdown
14// @name:es-419		Export ChatGPT/Gemini/Grok conversations as Markdown
15// @name:fi		Export ChatGPT/Gemini/Grok conversations as Markdown
16// @name:fr		Export ChatGPT/Gemini/Grok conversations as Markdown
17// @name:fr-CA		Export ChatGPT/Gemini/Grok conversations as Markdown
18// @name:he		Export ChatGPT/Gemini/Grok conversations as Markdown
19// @name:hr		Export ChatGPT/Gemini/Grok conversations as Markdown
20// @name:hu		Export ChatGPT/Gemini/Grok conversations as Markdown
21// @name:id		Export ChatGPT/Gemini/Grok conversations as Markdown
22// @name:it		Export ChatGPT/Gemini/Grok conversations as Markdown
23// @name:ja		Export ChatGPT/Gemini/Grok conversations as Markdown
24// @name:ka		Export ChatGPT/Gemini/Grok conversations as Markdown
25// @name:ko		Export ChatGPT/Gemini/Grok conversations as Markdown
26// @name:nb		Export ChatGPT/Gemini/Grok conversations as Markdown
27// @name:nl		Export ChatGPT/Gemini/Grok conversations as Markdown
28// @name:pl		Export ChatGPT/Gemini/Grok conversations as Markdown
29// @name:pt-BR		Export ChatGPT/Gemini/Grok conversations as Markdown
30// @name:ro		Export ChatGPT/Gemini/Grok conversations as Markdown
31// @name:ru		Export ChatGPT/Gemini/Grok conversations as Markdown
32// @name:sv		Export ChatGPT/Gemini/Grok conversations as Markdown
33// @name:th		Export ChatGPT/Gemini/Grok conversations as Markdown
34// @name:tr		Export ChatGPT/Gemini/Grok conversations as Markdown
35// @name:uk		Export ChatGPT/Gemini/Grok conversations as Markdown
36// @name:ug		Export ChatGPT/Gemini/Grok conversations as Markdown
37// @name:vi		Export ChatGPT/Gemini/Grok conversations as Markdown
38// @description		Export chat history from ChatGPT and Grok websites to normal format as Markdown, which can be opened via typora exactly.
39// @description:zh-CN		将 ChatGPT 和 Grok 网站的聊天记录导出为普通 Markdown 格式,可在 Typora 中准确打开。
40// @description:zh-TW		將 ChatGPT 和 Grok 網站的聊天記錄導出為普通 Markdown 格式,可於 Typora 中精確打開。
41// @description:ar		تصدير محفوظات الدردشة من موقعي ChatGPT و Grok إلى تنسيق Markdown عادي يمكن فتحه عبر Typora بدقة.
42// @description:bg		Експортиране на историята на чатовете от уебсайтовете ChatGPT и Grok в обикновен Markdown формат, който може да се отвори точно чрез Typora.
43// @description:cs		Exportovat historii chatu z webů ChatGPT a Grok do obyčejného formátu Markdown, který lze přesně otevřít v Typora.
44// @description:da		Eksportér chat‑historik fra ChatGPT- og Grok‑websteder til normalt Markdown‑format, som kan åbnes præcist i Typora.
45// @description:de		Exportiere den Chatverlauf von den ChatGPT‑ und Grok‑Websites in ein normales Markdown‑Format, das exakt in Typora geöffnet werden kann.
46// @description:el		Εξαγωγή του ιστορικού συνομιλίας από τις ιστοσελίδες ChatGPT και Grok σε απλό μορφότυπο Markdown, που μπορεί να ανοιχτεί ακριβώς με το Typora.
47// @description:en		Export chat history from ChatGPT and Grok websites to normal format as Markdown, which can be opened via typora exactly.
48// @description:eo		Eksporti babilhistorion de ChatGPT- kaj Grok‑retejoj al norma Markdown‑formato, kiu povas esti malfermita ĝuste en Typora.
49// @description:es		Exportar el historial de chat de los sitios web ChatGPT y Grok a un formato normal Markdown, que se pueda abrir exactamente con Typora.
50// @description:es-419		Exportar el historial de chat de los sitios web ChatGPT y Grok a un formato normal Markdown, que se pueda abrir exactamente con Typora.
51// @description:fi		Vie ChatGPT:n ja Grok-verkkosivustojen chätähistoria tavalliseen Markdown‑muotoon, jonka Typora avaa täsmälleen oikein.
52// @description:fr		Exporter l’historique de discussion des sites ChatGPT et Grok vers un format Markdown normal, pouvant être ouvert exactement avec Typora.
53// @description:fr-CA		Exporter l’historique des conversations des sites ChatGPT et Grok dans un format Markdown standard, pouvant être ouvert précisément avec Typora.
54// @description:he		ייצוא היסטוריית הצ'אט מאתרי ChatGPT ו‑Grok לפורמט Markdown רגיל, שניתן לפתוח ב‑Typora בדיוק.
55// @description:hr		Izvezi povijest razgovora s web‑mjesta ChatGPT i Grok u obični Markdown format, koji se točno može otvoriti u Typora.
56// @description:hu		A ChatGPT és Grok webhelyek csevegési előzményeinek exportálása normál Markdown formátumba, amely pontosan megnyitható a Typora segítségével.
57// @description:id		Ekspor riwayat obrolan dari situs web ChatGPT dan Grok ke format Markdown biasa, yang bisa dibuka persis dengan Typora.
58// @description:it		Esporta la cronologia della chat dai siti ChatGPT e Grok in formato Markdown normale, che può essere aperto esattamente con Typora.
59// @description:ja		ChatGPT と Grok のウェブサイトからチャット履歴を通常の Markdown 形式にエクスポートし、Typora で正確に開けるようにします。
60// @description:ka		ChatGPT და Grok ვებსაიტების ჩატის ისტორიის ექსპორტი ნორმალურ Markdown ფორმატში, რომელიც Typora-ში ზუსტად იხსნება.
61// @description:ko		ChatGPT 및 Grok 웹사이트의 채팅 기록을 일반 Markdown 형식으로 내보내 Typora에서 정확하게 열 수 있습니다.
62// @description:nb		Eksporter chattehistorikk fra ChatGPT- og Grok-nettsteder til vanlig Markdown-format, som kan åpnes nøyaktig i Typora.
63// @description:nl		Exporteer de chatgeschiedenis van de ChatGPT- en Grok-websites naar normaal Markdown‑formaat, dat precies met Typora geopend kan worden.
64// @description:pl		Eksportuj historię czatów ze stron ChatGPT i Grok do zwykłego formatu Markdown, który można dokładnie otworzyć w Typora.
65// @description:pt-BR		Exporte o histórico de conversa dos sites ChatGPT e Grok para um formato Markdown normal, que possa ser aberto exatamente via Typora.
66// @description:ro		Exportă istoricul conversației de pe site‑urile ChatGPT și Grok într‑un format Markdown normal, care poate fi deschis exact în Typora.
67// @description:ru		Экспорт истории чата с сайтов ChatGPT и Grok в обычный формат Markdown, который можно точно открыть в Typora.
68// @description:sv		Exportera chatthistorik från ChatGPT- och Grok-webbplatser till vanligt Markdown-format, som kan öppnas exakt i Typora.
69// @description:th		ส่งออกประวัติแชทจากเว็บไซต์ ChatGPT และ Grok ไปยังรูปแบบ Markdown ปกติ ซึ่งสามารถเปิดได้อย่างถูกต้องผ่าน Typora
70// @description:tr		ChatGPT ve Grok web sitelerinden sohbet geçmişini normal Markdown formatına aktarın; Typora ile tam olarak açılabilir.
71// @description:uk		Експортувати історію чату з веб‑сайтів ChatGPT і Grok у звичайний формат Markdown, який точно відкривається в Typora.
72// @description:ug		ChatGPT ۋە Grok تور بەتلىرىدىكى چەت تارىخنى ئادەتتىكى Markdown فورماتىغا چىقارسىڭىز بولىدۇ، بۇنى Typora قوشۇلمىسىدە تولۇق ئاچىشقا بولىدۇ.
73// @description:vi		Xuất lịch sử trò chuyện từ các trang ChatGPT và Grok sang định dạng Markdown bình thường, có thể mở chính xác bằng Typora.
74// @namespace		Elior_Chatgpt_XX
75// @version		1.1.2
76// @author		Elior
77// @icon		data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAAkZJREFUWEfNV8tSwzAMtPtFlKFMbm2/rO2XEW4ZygBfVMO6lkdWLFtpe6CXwMSWVqvVI94t+D29DDvv3YFd2aW/Rzy9d+94fn1MR6tZbzm43gxvzjlyZrkCMCcLkCaA59fhGEIRsck5P9QDogK4JeoGuvH7PO1r72cAUp5B+cN/Ibj9z+cU9UK/GYD1ZggP98wMfp+nwmfxj5H2MQR3okigE9hfoJUiHRmARXAkKKRptbpWxeXiRoBJ97coxR4YLswMoEc9XaoArQqsFxClIgLoHf7rAdGJOJdTwRpUZIUz5b3TBB1tRgCt6LlyCYBwgM5Ya1LRQUtXYMG3oodzAES+0dXIGC4KwzkN3B7d11jAexUARZnYKaKJF6/UxjQk2g8QILVfqo4EXCvtUUaSKxZRsmhmAOggVQNXPk9RAqfq4CYAMgVKhXB2mgCq9Ij8aSnIjOE8aYXrQDA5a7JgoApAaCCXFoyj+UhhcV1w9feEqKYgtdc4PLjiNbqTUwRTsCUDERToIiQAeLJWm7chGKZWnCdbqgzOwF0AKH9wwMsrBLfl5YdGpKWgpQGA8635L/PHh0itEfGeQVpplWEE0GnFvXaa54HsGbDbW25iK04A1KWTZsHSQdTbLYhN0zjWFsuKk9rUrC5YxTg2juTYCyBA2fNJlLVqqXmvLiS9VEhDVIZUHXInaOyV9ZWMLvQ2o3sX1uZSalHuPQBMazljYvHnmJV2fu7/fppxlIaltRa8+jlmZkBaZR8h2/Su+DxPAyx/tFj08gu12CtmhuPs2AAAAABJRU5ErkJggg==
78// @match		*://chatgpt.com/*
79// @match		*://grok.com/*
80// @match		*://gemini.google.com/*
81// @license		MIT
82// @run-at		document-idle
83// @grant		GM_registerMenuCommand
84// @grant		GM_openInTab
85// @grant		GM.openInTab
86// @grant		GM_addStyle
87// @grant		GM_setValue
88// @grant		GM_getValue
89// @grant		GM_xmlhttpRequest
90// @downloadURL		https://update.greasyfork.org/scripts/543471/Export%20ChatGPTGeminiGrok%20conversations%20as%20Markdown.user.js
91// @updateURL		https://update.greasyfork.org/scripts/543471/Export%20ChatGPTGeminiGrok%20conversations%20as%20Markdown.meta.js
92// ==/UserScript==
93(function () {
94    'use strict';
95
96	
97    /*!
98	* Copyright (c) 2024 - 2025, Elior. All rights reserved.
99	*
100	* Permission is hereby granted, free of charge, to any person obtaining a copy
101	* of this software and associated documentation files (the "Software"), to deal
102	* in the Software without restriction, including without limitation the rights
103	* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
104	* copies of the Software, and to permit persons to whom the Software is
105	* furnished to do so, subject to the following conditions:
106	*
107	* The above copyright notice and this permission notice shall be included in
108	* all copies or substantial portions of the Software.
109	*
110	* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
111	* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
112	*
113	* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
114	* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
115	* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
116	* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
117	* SOFTWARE.
118	*/
119
120
121    const CommonUtil = {
122	  onPageLoad: function(callback) {
123	    if (document.readyState === 'complete') {
124	      callback();
125	    } else {
126	      window.addEventListener('DOMContentLoaded', callback, { once: true });
127	      window.addEventListener('load', callback, { once: true });
128	    }
129	  },
130	  addStyle: function(style) {
131	    GM_addStyle(style);
132	  },
133	  createElement: function(tag, options = {}) {
134	    const element = document.createElement(tag);
135	    if (options.text) {
136	      element.textContent = options.text;
137	    }
138	    if (options.html) {
139	      element.innerHTML = options.html;
140	    }
141	    if (options.style) {
142	      Object.assign(element.style, options.style);
143	    }
144	    if (options.className) {
145	      element.className = options.className;
146	    }
147	    if (options.attributes) {
148	      for (let [key, value] of Object.entries(options.attributes)) {
149	        element.setAttribute(key, value);
150	      }
151	    }
152	    if (options.childrens) {
153	      options.childrens.forEach((child) => {
154	        element.appendChild(child);
155	      });
156	    }
157	    return element;
158	  },
159	  openInTab: function(url, options = { 'active': true, 'insert': true, 'setParent': true }) {
160	    GM.openInTab(url, options);
161	  },
162	  waitForElementByInterval: function(selector, target = document.body, allowEmpty = true, delay = 10, maxDelay = 10 * 1e3) {
163	    return new Promise((resolve) => {
164	      let totalDelay = 0;
165	      let element = target.querySelector(selector);
166	      let result = allowEmpty ? !!element : !!element && !!element.innerHTML;
167	      if (result) {
168	        resolve(element);
169	      }
170	      const elementInterval = setInterval(() => {
171	        if (totalDelay >= maxDelay) {
172	          clearInterval(elementInterval);
173	          resolve(null);
174	        }
175	        element = target.querySelector(selector);
176	        result = allowEmpty ? !!element : !!element && !!element.innerHTML;
177	        if (result) {
178	          clearInterval(elementInterval);
179	          resolve(element);
180	        } else {
181	          totalDelay += delay;
182	        }
183	      }, delay);
184	    });
185	  }
186    };
187
188    const HtmlToMarkdown = {
189	  to: function(html, platform) {
190	    const parser = new DOMParser();
191	    const doc = parser.parseFromString(html, 'text/html');
192	    const isChatGPT = platform === 'chatGPT', isGemini = platform === 'gemini', isGrok = platform === 'grok';
193	    if (!isGemini) {
194	      doc.querySelectorAll('span.katex-html').forEach((element) => element.remove());
195	    }
196	    doc.querySelectorAll('mrow').forEach((mrow) => mrow.remove());
197	    doc.querySelectorAll('annotation[encoding="application/x-tex"]').forEach((element) => {
198	      if (element.closest('.katex-display')) {
199	        const latex = element.textContent;
200	        const trimmedLatex = latex.trim();
201	        element.replaceWith(`
202$$
203${trimmedLatex}
204$$
205`);
206	      } else {
207	        const latex = element.textContent;
208	        const trimmedLatex = latex.trim();
209	        element.replaceWith(`$${trimmedLatex}$`);
210	      }
211	    });
212	    doc.querySelectorAll('strong, b').forEach((bold) => {
213	      const markdownBold = `**${bold.textContent}**`;
214	      bold.parentNode.replaceChild(document.createTextNode(markdownBold), bold);
215	    });
216	    doc.querySelectorAll('em, i').forEach((italic) => {
217	      const markdownItalic = `*${italic.textContent}*`;
218	      italic.parentNode.replaceChild(document.createTextNode(markdownItalic), italic);
219	    });
220	    doc.querySelectorAll('p code').forEach((code) => {
221	      const markdownCode = `\`${code.textContent}\``;
222	      code.parentNode.replaceChild(document.createTextNode(markdownCode), code);
223	    });
224	    doc.querySelectorAll('a').forEach((link) => {
225	      const markdownLink = `[${link.textContent}](${link.href})`;
226	      link.parentNode.replaceChild(document.createTextNode(markdownLink), link);
227	    });
228	    doc.querySelectorAll('img').forEach((img) => {
229	      const markdownImage = `![${img.alt}](${img.src})`;
230	      img.parentNode.replaceChild(document.createTextNode(markdownImage), img);
231	    });
232	    if (isChatGPT) {
233	      doc.querySelectorAll('pre').forEach((pre) => {
234	        const codeType = pre.querySelector('div > div:first-child')?.textContent || '';
235	        const markdownCode = pre.querySelector('div > div:nth-child(3) > code')?.textContent || pre.textContent;
236	        pre.innerHTML = `
237\`\`\`${codeType}
238${markdownCode}
239\`\`\``;
240	      });
241	    } else if (isGrok) {
242	      doc.querySelectorAll('div.not-prose').forEach((div) => {
243	        const codeType = div.querySelector('div > div > span')?.textContent || '';
244	        const markdownCode = div.querySelector('div > div:nth-child(3) > code')?.textContent || div.textContent;
245	        div.innerHTML = `
246\`\`\`${codeType}
247${markdownCode}
248\`\`\``;
249	      });
250	    } else if (isGemini) {
251	      doc.querySelectorAll('code-block').forEach((div) => {
252	        const codeType = div.querySelector('div > div > span')?.textContent || '';
253	        const markdownCode = div.querySelector('div > div:nth-child(2) > div > pre')?.textContent || div.textContent;
254	        div.innerHTML = `
255\`\`\`${codeType}
256${markdownCode}
257\`\`\``;
258	      });
259	    }
260	    doc.querySelectorAll('ul').forEach((ul) => {
261	      let markdown2 = '';
262	      ul.querySelectorAll(':scope > li').forEach((li) => {
263	        markdown2 += `- ${li.textContent.trim()}
264`;
265	      });
266	      ul.parentNode.replaceChild(document.createTextNode('\n' + markdown2.trim()), ul);
267	    });
268	    doc.querySelectorAll('ol').forEach((ol) => {
269	      let markdown2 = '';
270	      ol.querySelectorAll(':scope > li').forEach((li, index) => {
271	        markdown2 += `${index + 1}. ${li.textContent.trim()}
272`;
273	      });
274	      ol.parentNode.replaceChild(document.createTextNode('\n' + markdown2.trim()), ol);
275	    });
276	    for (let i = 1; i <= 6; i++) {
277	      doc.querySelectorAll(`h${i}`).forEach((header) => {
278	        const markdownHeader = `
279${'#'.repeat(i)} ${header.textContent}
280`;
281	        header.parentNode.replaceChild(document.createTextNode(markdownHeader), header);
282	      });
283	    }
284	    doc.querySelectorAll('p').forEach((p) => {
285	      const markdownParagraph = '\n' + p.textContent + '\n';
286	      p.parentNode.replaceChild(document.createTextNode(markdownParagraph), p);
287	    });
288	    doc.querySelectorAll('table').forEach((table) => {
289	      let markdown2 = '';
290	      table.querySelectorAll('thead tr').forEach((tr) => {
291	        tr.querySelectorAll('th').forEach((th) => {
292	          markdown2 += `| ${th.textContent} `;
293	        });
294	        markdown2 += '|\n';
295	        tr.querySelectorAll('th').forEach(() => {
296	          markdown2 += '| ---- ';
297	        });
298	        markdown2 += '|\n';
299	      });
300	      table.querySelectorAll('tbody tr').forEach((tr) => {
301	        tr.querySelectorAll('td').forEach((td) => {
302	          markdown2 += `| ${td.textContent} `;
303	        });
304	        markdown2 += '|\n';
305	      });
306	      table.parentNode.replaceChild(document.createTextNode('\n' + markdown2.trim() + '\n'), table);
307	    });
308	    let markdown = doc.body.innerHTML.replace(/<[^>]*>/g, '');
309	    markdown = markdown.replaceAll(/- &gt;/g, '- $\\gt$');
310	    markdown = markdown.replaceAll(/>/g, '>');
311	    markdown = markdown.replaceAll(/</g, '<');
312	    markdown = markdown.replaceAll(//g, '>=');
313	    markdown = markdown.replaceAll(//g, '<=');
314	    markdown = markdown.replaceAll(//g, '\\neq');
315	    return markdown.trim();
316	  }
317    };
318
319    const Download = {
320	  start: function(data, filename, type) {
321	    var file = new Blob([data], { type });
322	    if (window.navigator.msSaveOrOpenBlob) {
323	      window.navigator.msSaveOrOpenBlob(file, filename);
324	    } else {
325	      var a = document.createElement('a'), url = URL.createObjectURL(file);
326	      a.href = url;
327	      a.download = filename;
328	      document.body.appendChild(a);
329	      a.click();
330	      setTimeout(function() {
331	        document.body.removeChild(a);
332	        window.URL.revokeObjectURL(url);
333	      }, 0);
334	    }
335	  }
336    };
337
338    const Chat = {
339	  sanitizeFilename: function(input, replacement = '_') {
340	    const illegalRe = /[\/\\\?\%\*\:\|"<>\.]/g;
341	    const controlRe = /[\x00-\x1f\x80-\x9f]/g;
342	    const reservedRe = /^\.+$/;
343	    const windowsReservedRe = /^(con|prn|aux|nul|com[1-9]|lpt[1-9])$/i;
344	    let name = input.replace(illegalRe, replacement).replace(controlRe, replacement).replace(/\s+/g, ' ').trim();
345	    if (reservedRe.test(name))
346	      name = 'file';
347	    if (windowsReservedRe.test(name))
348	      name = `file_${name}`;
349	    return name || 'untitled';
350	  },
351	  getConversationElements: function() {
352	    const currentUrl = window.location.href;
353	    const result = [];
354	    let platform = '';
355	    let title = '';
356	    if (currentUrl.includes('openai.com') || currentUrl.includes('chatgpt.com')) {
357	      platform = 'chatGPT';
358	      title = document.querySelector('#history a[data-active]')?.textContent;
359	      result.push(...document.querySelectorAll('div[data-message-id]'));
360	    } else if (currentUrl.includes('grok.com')) {
361	      platform = 'grok';
362	      result.push(...document.querySelectorAll('div.message-bubble'));
363	    } else if (currentUrl.includes('gemini.google.com')) {
364	      platform = 'gemini';
365	      title = document.querySelector('conversations-list div.selected')?.textContent;
366	      const userQueries = document.querySelectorAll('user-query-content');
367	      const modelResponses = document.querySelectorAll('model-response');
368	      for (let i = 0; i < userQueries.length; i++) {
369	        if (i < modelResponses.length) {
370	          result.push(userQueries[i]);
371	          result.push(modelResponses[i]);
372	        } else {
373	          result.push(userQueries[i]);
374	        }
375	      }
376	    }
377	    return { 'result': result, 'platform': platform, 'title': title };
378	  },
379	  exportChatAsMarkdown: function() {
380	    let markdownContent = '';
381	    const { result, platform, title } = this.getConversationElements();
382	    const filename = (this.sanitizeFilename(title) || 'chat_export') + '.md';
383	    for (let i = 0; i < result.length; i += 2) {
384	      if (!result[i + 1])
385	        break;
386	      let userText = result[i].textContent.trim();
387	      let answerHtml = result[i + 1].innerHTML.trim();
388	      userText = HtmlToMarkdown.to(userText, platform);
389	      answerHtml = HtmlToMarkdown.to(answerHtml, platform);
390	      markdownContent += `
391# Q:
392${userText}
393# A:
394${answerHtml}`;
395	    }
396	    markdownContent = markdownContent.replace(/&amp;/g, '&');
397	    if (markdownContent) {
398	      Download.start(markdownContent, filename, 'text/markdown');
399	    }
400	  },
401	  exportChatAsPDF: function() {
402	    console.log('Exporting chat as PDF...');
403	    const { result, title } = this.getConversationElements();
404	    
405	    if (result.length === 0) {
406	      console.error('No conversation elements found');
407	      return;
408	    }
409	    
410	    // Create a new window with the conversation content
411	    const printWindow = window.open('', '_blank');
412	    
413	    let htmlContent = `
414<!DOCTYPE html>
415<html>
416<head>
417  <title>${title || 'Chat Export'}</title>
418  <style>
419    body {
420      font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
421      max-width: 800px;
422      margin: 40px auto;
423      padding: 20px;
424      line-height: 1.6;
425      color: #333;
426    }
427    h1 {
428      color: #2c3e50;
429      border-bottom: 2px solid #3498db;
430      padding-bottom: 10px;
431      margin-top: 30px;
432    }
433    .question {
434      background-color: #f8f9fa;
435      padding: 15px;
436      border-left: 4px solid #3498db;
437      margin: 20px 0;
438    }
439    .answer {
440      background-color: #ffffff;
441      padding: 15px;
442      border-left: 4px solid #2ecc71;
443      margin: 20px 0;
444    }
445    pre {
446      background-color: #f4f4f4;
447      padding: 10px;
448      border-radius: 5px;
449      overflow-x: auto;
450    }
451    code {
452      background-color: #f4f4f4;
453      padding: 2px 6px;
454      border-radius: 3px;
455      font-family: 'Courier New', monospace;
456    }
457    @media print {
458      body {
459        margin: 0;
460        padding: 20px;
461      }
462    }
463  </style>
464</head>
465<body>
466  <h1>${title || 'Chat Export'}</h1>
467`;
468	    
469	    for (let i = 0; i < result.length; i += 2) {
470	      if (!result[i + 1]) break;
471	      
472	      const userText = result[i].innerHTML.trim();
473	      const answerHtml = result[i + 1].innerHTML.trim();
474	      
475	      htmlContent += `
476  <div class="question">
477    <h2>Question:</h2>
478    ${userText}
479  </div>
480  <div class="answer">
481    <h2>Answer:</h2>
482    ${answerHtml}
483  </div>
484`;
485	    }
486	    
487	    htmlContent += `
488</body>
489</html>
490`;
491	    
492	    printWindow.document.write(htmlContent);
493	    printWindow.document.close();
494	    
495	    // Wait for content to load, then trigger print dialog
496	    printWindow.onload = function() {
497	      setTimeout(() => {
498	        printWindow.print();
499	      }, 250);
500	    };
501	  }
502    };
503
504    var css_248z = '.chat-gpt-document-block{align-items:center;border:1px solid #e5e5e5;border-radius:35px;display:flex;font-size:15px;justify-content:center;left:50%;padding:5px 15px;position:fixed;top:9px;transform:translateX(-50%);z-index:99999999999!important;gap:10px}.chat-gpt-document-icon-sm{margin-right:5px}.chat-gpt-document-btn-content{align-items:center;display:flex}.chat-export-btn{background:transparent;border:none;cursor:pointer;padding:8px 12px;border-radius:20px;font-size:14px;display:flex;align-items:center;gap:5px;transition:background-color 0.2s}.chat-export-btn:hover{background-color:#f0f0f0}';
505
506    const Export = {
507	  addStyle: function() {
508	    CommonUtil.addStyle(css_248z);
509	  },
510	  createSvgIcon: function() {
511	    const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
512	    svg.setAttribute('class', 'chat-gpt-document-icon-sm');
513	    svg.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
514	    svg.setAttribute('fill', 'none');
515	    svg.setAttribute('viewBox', '0 0 24 24');
516	    svg.setAttribute('width', '16');
517	    svg.setAttribute('height', '16');
518	    svg.setAttribute('stroke-width', '1.5');
519	    svg.setAttribute('stroke', 'currentColor');
520	    const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
521	    path.setAttribute('stroke-linecap', 'round');
522	    path.setAttribute('stroke-linejoin', 'round');
523	    path.setAttribute('d', 'M19.5 14.25v-2.625a3.375 3.375 0 0 0-3.375-3.375h-1.5A1.125 1.125 0 0 1 13.5 7.125v-1.5a3.375 3.375 0 0 0-3.375-3.375H8.25m.75 12 3 3m0 0 3-3m-3 3v-6m-1.5-9H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 0 0-9-9Z');
524	    svg.appendChild(path);
525	    return svg;
526	  },
527	  generateHtml: function() {
528	    const outerDiv = CommonUtil.createElement('div', {
529	      className: 'chat-gpt-document-block'
530	    });
531	    
532	    // Create Markdown export button
533	    const markdownBtn = CommonUtil.createElement('button', {
534	      className: 'chat-export-btn',
535	      childrens: [
536	        this.createSvgIcon(),
537	        CommonUtil.createElement('span', {
538	          text: 'Export as Markdown'
539	        })
540	      ]
541	    });
542	    
543	    // Create PDF export button
544	    const pdfBtn = CommonUtil.createElement('button', {
545	      className: 'chat-export-btn',
546	      childrens: [
547	        this.createSvgIcon(),
548	        CommonUtil.createElement('span', {
549	          text: 'Export as PDF'
550	        })
551	      ]
552	    });
553	    
554	    outerDiv.appendChild(markdownBtn);
555	    outerDiv.appendChild(pdfBtn);
556	    
557	    (document.body || document.documentElement).appendChild(outerDiv);
558	    
559	    markdownBtn.addEventListener('click', function() {
560	      Chat.exportChatAsMarkdown();
561	    });
562	    
563	    pdfBtn.addEventListener('click', function() {
564	      Chat.exportChatAsPDF();
565	    });
566	  },
567	  start: function() {
568	    this.addStyle();
569	    this.generateHtml();
570	  }
571    };
572
573    (() => {
574	  if (typeof trustedTypes !== 'undefined' && trustedTypes.defaultPolicy === null) {
575	    let s = (s2) => s2;
576	    trustedTypes.createPolicy('default', { createHTML: s, createScriptURL: s, createScript: s });
577	  }
578    })();
579
580    Export.start();
581
582}());
Export ChatGPT/Gemini/Grok conversations as Markdown | Robomonkey