]*>/g, "\n#### ") // replace h4 tags with markdown h4
100 | .replace(/<\/h4>/g, "\n");
101 |
102 | console.log('After structural elements:', result);
103 |
104 | // Handle code blocks - process pre/code combinations FIRST
105 | result = result.replace(/]*>]*>([\s\S]*?)<\/code><\/pre>/g, (match, code) => {
106 | const cleanCode = code
107 | .replace(/</g, '<')
108 | .replace(/>/g, '>')
109 | .replace(/&/g, '&')
110 | .replace(/"/g, '"')
111 | .replace(/ /g, ' ')
112 | .trim();
113 | console.log('Processing code block:', cleanCode);
114 |
115 | // Check for language class to determine markdown syntax
116 | const languageMatch = match.match(/class="[^"]*language-([^"\s]+)/);
117 | const language = languageMatch ? languageMatch[1] : '';
118 |
119 | if (language) {
120 | return "\n```" + language + "\n" + cleanCode + "\n```\n";
121 | } else {
122 | return "\n```\n" + cleanCode + "\n```\n";
123 | }
124 | });
125 |
126 | result = result.replace(/]*>([\s\S]*?)<\/code>/g, (match, code) => {
127 | const cleanCode = code
128 | .replace(/</g, '<')
129 | .replace(/>/g, '>')
130 | .replace(/&/g, '&')
131 | .replace(/"/g, '"')
132 | .replace(/ /g, ' ')
133 | .trim();
134 | console.log('Processing inline code:', cleanCode);
135 |
136 | const languageMatch = match.match(/class="[^"]*language-([^"\s]+)/);
137 | const language = languageMatch ? languageMatch[1] : '';
138 |
139 | if (!cleanCode.includes('\n')) {
140 | return "`" + cleanCode + "`";
141 | }
142 |
143 | if (language) {
144 | return "\n```" + language + "\n" + cleanCode + "\n```\n";
145 | } else {
146 | return "\n```\n" + cleanCode + "\n```\n";
147 | }
148 | });
149 |
150 | console.log('After code blocks:', result);
151 |
152 | // Remove UI elements and clean HTML
153 | result = result
154 | .replace(/]*>.*?<\/button>/g, "")
155 | .replace(/]*class="[^"]*copy[^"]*"[^>]*>.*?<\/div>/g, "")
156 | .replace(/
]*class="[^"]*edit[^"]*"[^>]*>.*?<\/div>/g, "")
157 | .replace(/Copy code/g, "")
158 | .replace(/Edit/g, "")
159 | .replace(/Copy/g, "")
160 | .replace(/
]*class="[^"]*copy[^"]*"[^>]*>.*?<\/span>/g, "")
161 | .replace(/]*class="[^"]*edit[^"]*"[^>]*>.*?<\/span>/g, "")
162 | .replace(/]*class="[^"]*language[^"]*"[^>]*>.*?<\/div>/g, "")
163 | .replace(/]*class="[^"]*language[^"]*"[^>]*>.*?<\/span>/g, "")
164 | .replace(/]*>(.*?)<\/span>/g, "$1")
165 | .replace(/<[a-zA-Z][^>]*>/g, "")
166 | .replace(/<\/[a-zA-Z][^>]*>/g, "")
167 | .replace(
168 | /This content may violate our content policy\. If you believe this to be in error, please submit your feedback — your input will aid our research in this area\./g,
169 | ""
170 | )
171 | .replace(/ /g, " ")
172 | .replace(/&/g, "&")
173 | .replace(/</g, "<")
174 | .replace(/>/g, ">")
175 | .replace(/"/g, '"')
176 | .replace(/\n\s*\n\s*\n/g, "\n\n")
177 | .replace(/^\s+|\s+$/g, "")
178 | .replace(/[a-zA-Z]+\*{4,}/g, "")
179 | .replace(/\n{3,}/g, "\n\n")
180 | .trim();
181 |
182 | console.log('Final result:', result);
183 | return result;
184 | }
185 |
186 | async function processMessageContent(contentElement, messageIndex) {
187 | let content = contentElement.innerHTML;
188 |
189 | console.log('Processing message content:', contentElement);
190 | console.log('Content HTML:', content);
191 |
192 | // Check for images and replace with markdown references
193 | const images = contentElement.querySelectorAll('img');
194 | console.log(`Found ${images.length} images`);
195 |
196 | for (let i = 0; i < images.length; i++) {
197 | const img = images[i];
198 | const src = img.src || img.getAttribute('src') || img.getAttribute('data-src');
199 |
200 | if (src) {
201 | console.log(`Image ${i}: ${src}`);
202 | const imgMarkdown = ``;
203 | content = content.replace(img.outerHTML, imgMarkdown);
204 | console.log(`Replaced image with: ${imgMarkdown}`);
205 | }
206 | }
207 |
208 | // Check for files and replace with markdown references
209 | const fileSelectors = [
210 | 'a[download]',
211 | 'a[href*="blob:"]',
212 | 'div[class*="text-token-text-primary"] a',
213 | 'div[class*="border-token-border"] a'
214 | ];
215 |
216 | let allFiles = [];
217 | fileSelectors.forEach(selector => {
218 | const files = contentElement.querySelectorAll(selector);
219 | allFiles = allFiles.concat(Array.from(files));
220 | });
221 |
222 | const uniqueFiles = [...new Set(allFiles)];
223 | console.log(`Found ${uniqueFiles.length} files`);
224 |
225 | for (let i = 0; i < uniqueFiles.length; i++) {
226 | const fileElement = uniqueFiles[i];
227 | const href = fileElement.href || fileElement.getAttribute('href');
228 |
229 | if (href) {
230 | let fileName = fileElement.download ||
231 | fileElement.getAttribute('download');
232 |
233 | if (!fileName) {
234 | const filenameElement = fileElement.querySelector('div[class*="font-semibold"], .font-semibold, [class*="truncate"]');
235 | if (filenameElement) {
236 | fileName = filenameElement.textContent.trim();
237 | } else {
238 | fileName = fileElement.textContent.trim() || `file_${i}`;
239 | }
240 | }
241 |
242 | console.log(`File ${i}: ${fileName} - ${href}`);
243 | const fileMarkdown = `[📎 ${fileName}](${href})`;
244 | content = content.replace(fileElement.outerHTML, fileMarkdown);
245 | console.log(`Replaced file with: ${fileMarkdown}`);
246 | }
247 | }
248 |
249 | return {
250 | content: h(content),
251 | hasMedia: false,
252 | mediaFiles: []
253 | };
254 | }
255 |
256 | (async () => {
257 | console.log('Starting ChatGPT message extraction...');
258 |
259 | // Try multiple selectors for different ChatGPT versions
260 | const selectorSets = [
261 | '[data-message-author-role]',
262 | 'article[data-testid*="conversation-turn"]',
263 | '.group.text-token-text-primary',
264 | '[class*="group"][class*="text-token"]',
265 | 'article',
266 | '[class*="group"]'
267 | ];
268 |
269 | let messageElements = [];
270 | for (const selector of selectorSets) {
271 | messageElements = document.querySelectorAll(selector);
272 | console.log(`Selector "${selector}" found ${messageElements.length} elements`);
273 | if (messageElements.length > 0) break;
274 | }
275 |
276 | if (messageElements.length === 0) {
277 | console.error('No message elements found!');
278 | alert('Unable to find conversation content. Please ensure you are on a ChatGPT conversation page.');
279 | return;
280 | }
281 |
282 | // Setup markdown file with title
283 | let t = `# ${
284 | document.querySelector("title")?.innerText || "Conversation with ChatGPT"
285 | }\n\n`;
286 |
287 | let messageIndex = 0;
288 |
289 | console.log(`Processing ${messageElements.length} message elements...`);
290 |
291 | for (const s of messageElements) {
292 | const contentSelectors = [
293 | '[class*="whitespace-pre-wrap"]',
294 | '[class*="prose"]',
295 | '[class*="markdown"]',
296 | '.markdown',
297 | '[class*="text-base"]',
298 | '[data-testid*="content"]',
299 | 'p',
300 | 'div'
301 | ];
302 |
303 | let contentElement = null;
304 | for (const selector of contentSelectors) {
305 | contentElement = s.querySelector(selector);
306 | if (contentElement && contentElement.textContent.trim()) {
307 | console.log(`Found content with selector: ${selector}`);
308 | break;
309 | }
310 | }
311 |
312 | if (contentElement && contentElement.textContent.trim()) {
313 | let isUser = false;
314 |
315 | const userIndicators = [
316 | s.querySelector('img[alt*="user"], img[alt*="User"]'),
317 | s.querySelector('[data-message-author-role="user"]'),
318 | s.querySelector('[data-testid*="user"]'),
319 | s.getAttribute('data-message-author-role') === 'user',
320 | s.getAttribute('data-turn') === 'user'
321 | ];
322 |
323 | isUser = userIndicators.some(indicator => indicator);
324 |
325 | const username = isUser ? "User" : "ChatGPT";
326 |
327 | console.log(`Processing ${username} message ${messageIndex}`);
328 |
329 | const processedContent = await processMessageContent(contentElement, messageIndex);
330 |
331 | t += messageIndex > 0 ? "\n---\n\n" : "";
332 | t += `**${username}**:\n\n${processedContent.content}\n\n`;
333 |
334 | messageIndex++;
335 | }
336 | }
337 |
338 | console.log('Downloading markdown file...');
339 |
340 | // Download markdown file
341 | const timestamp = new Date().toISOString().slice(0, 19).replace(/:/g, '-');
342 | const o = document.createElement("a");
343 | o.download = `ChatGPT_Conversation_${timestamp}.md`;
344 | o.href = URL.createObjectURL(new Blob([t], {type: 'text/markdown'}));
345 | o.style.display = "none";
346 | document.body.appendChild(o);
347 | o.click();
348 | document.body.removeChild(o);
349 | console.log('Markdown file downloaded successfully');
350 | })();
351 | }
352 | });
--------------------------------------------------------------------------------