decant-core 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -20
- package/ai/base.js +4 -4
- package/ai/chatgpt.js +356 -216
- package/ai/chatgpt_helper.js +46 -28
- package/ai/chatgpt_scroll_collector.js +36 -33
- package/ai/chub.js +90 -0
- package/ai/claude.js +119 -92
- package/ai/claude_react_reader.js +8 -6
- package/ai/copilot.js +158 -124
- package/ai/deepseek.js +36 -27
- package/ai/gemini.js +384 -230
- package/ai/gemini_cloud_assist.js +31 -22
- package/ai/google_ai_studio.js +45 -27
- package/ai/google_search_ai.js +32 -23
- package/ai/index.js +25 -19
- package/ai/joyland.js +85 -0
- package/ai/lumo.js +39 -28
- package/ai/meta.js +30 -24
- package/ai/mistral.js +21 -16
- package/ai/notebooklm.js +50 -34
- package/ai/perplexity.js +22 -19
- package/ai/qwen.js +29 -25
- package/ai/z_ai.js +34 -28
- package/detection/detect-platform.js +27 -19
- package/detection/domains.js +32 -24
- package/lib/turndown.js +352 -183
- package/package.json +13 -4
- package/utils/html-to-markdown.js +130 -109
package/ai/chatgpt.js
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
|
-
import { ChatParser } from
|
|
2
|
-
import { convertToMarkdown } from
|
|
1
|
+
import { ChatParser } from "./base.js";
|
|
2
|
+
import { convertToMarkdown } from "../utils/html-to-markdown.js";
|
|
3
3
|
import {
|
|
4
4
|
collectMountedTurnMessages,
|
|
5
5
|
findChatGPTScrollRoot,
|
|
6
6
|
getConversationTurns,
|
|
7
|
-
} from
|
|
7
|
+
} from "./chatgpt_scroll_collector.js";
|
|
8
8
|
|
|
9
9
|
function getAccessToken() {
|
|
10
10
|
try {
|
|
11
|
-
const el =
|
|
11
|
+
const el =
|
|
12
|
+
typeof document !== "undefined" &&
|
|
13
|
+
document.getElementById("client-bootstrap");
|
|
12
14
|
if (!el) return null;
|
|
13
15
|
return JSON.parse(el.textContent).session.accessToken;
|
|
14
16
|
} catch {
|
|
@@ -18,8 +20,8 @@ function getAccessToken() {
|
|
|
18
20
|
|
|
19
21
|
function getConversationId() {
|
|
20
22
|
try {
|
|
21
|
-
if (typeof window ===
|
|
22
|
-
const path = window.location.pathname || window.location.href ||
|
|
23
|
+
if (typeof window === "undefined" || !window.location) return null;
|
|
24
|
+
const path = window.location.pathname || window.location.href || "";
|
|
23
25
|
const match = path.match(/\/(?:c|share|g\/[^/]+\/c)\/([^/?#]+)/);
|
|
24
26
|
return match ? match[1] : null;
|
|
25
27
|
} catch {
|
|
@@ -30,14 +32,14 @@ function getConversationId() {
|
|
|
30
32
|
function fetchConversation(convId, token, includeImages) {
|
|
31
33
|
return new Promise((resolve, reject) => {
|
|
32
34
|
const requestId =
|
|
33
|
-
typeof crypto !==
|
|
35
|
+
typeof crypto !== "undefined" && crypto.randomUUID
|
|
34
36
|
? crypto.randomUUID()
|
|
35
37
|
: Math.random().toString(36).substring(2) + Date.now().toString(36);
|
|
36
38
|
|
|
37
39
|
const handler = (event) => {
|
|
38
|
-
if (event.data?.source !==
|
|
40
|
+
if (event.data?.source !== "chatgpt-exporter-page") return;
|
|
39
41
|
if (event.data?.requestId !== requestId) return;
|
|
40
|
-
window.removeEventListener(
|
|
42
|
+
window.removeEventListener("message", handler);
|
|
41
43
|
clearTimeout(timer);
|
|
42
44
|
if (event.data.error) {
|
|
43
45
|
reject(new Error(event.data.error));
|
|
@@ -47,21 +49,21 @@ function fetchConversation(convId, token, includeImages) {
|
|
|
47
49
|
};
|
|
48
50
|
|
|
49
51
|
const timer = setTimeout(() => {
|
|
50
|
-
window.removeEventListener(
|
|
51
|
-
reject(new Error(
|
|
52
|
+
window.removeEventListener("message", handler);
|
|
53
|
+
reject(new Error("Request timed out"));
|
|
52
54
|
}, 45000);
|
|
53
55
|
|
|
54
|
-
window.addEventListener(
|
|
56
|
+
window.addEventListener("message", handler);
|
|
55
57
|
window.postMessage(
|
|
56
58
|
{
|
|
57
|
-
source:
|
|
58
|
-
type:
|
|
59
|
+
source: "chatgpt-exporter-ext",
|
|
60
|
+
type: "fetch_conversation",
|
|
59
61
|
convId,
|
|
60
62
|
token,
|
|
61
63
|
requestId,
|
|
62
64
|
includeImages,
|
|
63
65
|
},
|
|
64
|
-
|
|
66
|
+
"https://chatgpt.com",
|
|
65
67
|
);
|
|
66
68
|
});
|
|
67
69
|
}
|
|
@@ -84,16 +86,18 @@ function findLeafFromNode(mapping, startNodeId) {
|
|
|
84
86
|
|
|
85
87
|
function resolveActiveLeafNode(mapping, currentNodeId) {
|
|
86
88
|
// 1. Try DOM elements first (captures branch if user switched turns in UI)
|
|
87
|
-
if (typeof document !==
|
|
89
|
+
if (typeof document !== "undefined" && document.querySelectorAll) {
|
|
88
90
|
const msgEls = Array.from(
|
|
89
91
|
document.querySelectorAll(
|
|
90
|
-
|
|
92
|
+
"div[data-message-id], [data-message-author-role][data-message-id], section[data-turn-id], article[data-turn-id]",
|
|
91
93
|
),
|
|
92
94
|
);
|
|
93
95
|
for (let i = msgEls.length - 1; i >= 0; i--) {
|
|
94
96
|
const el = msgEls[i];
|
|
95
97
|
const id =
|
|
96
|
-
el.dataset?.messageId ||
|
|
98
|
+
el.dataset?.messageId ||
|
|
99
|
+
el.dataset?.turnId ||
|
|
100
|
+
el.getAttribute?.("data-message-id");
|
|
97
101
|
if (id && mapping[id]) {
|
|
98
102
|
const leaf = findLeafFromNode(mapping, id);
|
|
99
103
|
if (leaf) return leaf;
|
|
@@ -110,21 +114,25 @@ function resolveActiveLeafNode(mapping, currentNodeId) {
|
|
|
110
114
|
}
|
|
111
115
|
|
|
112
116
|
export function extractSharedConversationFromDom(
|
|
113
|
-
doc = typeof document !==
|
|
117
|
+
doc = typeof document !== "undefined" ? document : null,
|
|
114
118
|
) {
|
|
115
|
-
if (!doc || typeof doc.querySelectorAll !==
|
|
119
|
+
if (!doc || typeof doc.querySelectorAll !== "function") return null;
|
|
116
120
|
|
|
117
121
|
function findMappingInObj(obj, seen = new Set()) {
|
|
118
|
-
if (!obj || typeof obj !==
|
|
122
|
+
if (!obj || typeof obj !== "object" || seen.has(obj)) return null;
|
|
119
123
|
seen.add(obj);
|
|
120
124
|
|
|
121
|
-
if (
|
|
125
|
+
if (
|
|
126
|
+
obj.mapping &&
|
|
127
|
+
typeof obj.mapping === "object" &&
|
|
128
|
+
Object.keys(obj.mapping).length > 0
|
|
129
|
+
) {
|
|
122
130
|
return obj;
|
|
123
131
|
}
|
|
124
132
|
if (
|
|
125
133
|
obj.data &&
|
|
126
134
|
obj.data.mapping &&
|
|
127
|
-
typeof obj.data.mapping ===
|
|
135
|
+
typeof obj.data.mapping === "object" &&
|
|
128
136
|
Object.keys(obj.data.mapping).length > 0
|
|
129
137
|
) {
|
|
130
138
|
return obj.data;
|
|
@@ -144,10 +152,14 @@ export function extractSharedConversationFromDom(
|
|
|
144
152
|
return null;
|
|
145
153
|
}
|
|
146
154
|
|
|
147
|
-
const scripts = Array.from(doc.querySelectorAll(
|
|
155
|
+
const scripts = Array.from(doc.querySelectorAll("script"));
|
|
148
156
|
for (const script of scripts) {
|
|
149
|
-
const text = script.textContent ||
|
|
150
|
-
if (
|
|
157
|
+
const text = script.textContent || "";
|
|
158
|
+
if (
|
|
159
|
+
!text ||
|
|
160
|
+
(!text.includes('"mapping"') && !text.includes("current_node"))
|
|
161
|
+
)
|
|
162
|
+
continue;
|
|
151
163
|
|
|
152
164
|
try {
|
|
153
165
|
const parsed = JSON.parse(text);
|
|
@@ -169,7 +181,7 @@ export function extractSharedConversationFromDom(
|
|
|
169
181
|
}
|
|
170
182
|
}
|
|
171
183
|
|
|
172
|
-
const bootstrapEl = doc.getElementById?.(
|
|
184
|
+
const bootstrapEl = doc.getElementById?.("client-bootstrap");
|
|
173
185
|
if (bootstrapEl) {
|
|
174
186
|
try {
|
|
175
187
|
const parsed = JSON.parse(bootstrapEl.textContent);
|
|
@@ -197,7 +209,9 @@ export function linearize(mapping, includeImages, currentNodeId) {
|
|
|
197
209
|
}
|
|
198
210
|
path.reverse();
|
|
199
211
|
} else {
|
|
200
|
-
const root = Object.values(mapping).find(
|
|
212
|
+
const root = Object.values(mapping).find(
|
|
213
|
+
(n) => !n.parent || !mapping[n.parent],
|
|
214
|
+
);
|
|
201
215
|
if (!root) return [];
|
|
202
216
|
|
|
203
217
|
const subtreeSize = {};
|
|
@@ -206,7 +220,8 @@ export function linearize(mapping, includeImages, currentNodeId) {
|
|
|
206
220
|
const node = mapping[id];
|
|
207
221
|
if (!node) return (subtreeSize[id] = 0);
|
|
208
222
|
const childSizes = (node.children ?? []).map((cid) => size(cid));
|
|
209
|
-
return (subtreeSize[id] =
|
|
223
|
+
return (subtreeSize[id] =
|
|
224
|
+
1 + (childSizes.length ? Math.max(...childSizes) : 0));
|
|
210
225
|
}
|
|
211
226
|
for (const id of Object.keys(mapping)) size(id);
|
|
212
227
|
|
|
@@ -215,10 +230,14 @@ export function linearize(mapping, includeImages, currentNodeId) {
|
|
|
215
230
|
while (node && !visited.has(node.id)) {
|
|
216
231
|
visited.add(node.id);
|
|
217
232
|
path.push(node);
|
|
218
|
-
const validChildren = (node.children ?? []).filter(
|
|
233
|
+
const validChildren = (node.children ?? []).filter(
|
|
234
|
+
(cid) => cid in mapping,
|
|
235
|
+
);
|
|
219
236
|
node = validChildren.length
|
|
220
237
|
? mapping[
|
|
221
|
-
validChildren.reduce((best, cid) =>
|
|
238
|
+
validChildren.reduce((best, cid) =>
|
|
239
|
+
subtreeSize[cid] > subtreeSize[best] ? cid : best,
|
|
240
|
+
)
|
|
222
241
|
]
|
|
223
242
|
: null;
|
|
224
243
|
}
|
|
@@ -234,76 +253,105 @@ export function linearize(mapping, includeImages, currentNodeId) {
|
|
|
234
253
|
const role = msg?.author?.role;
|
|
235
254
|
const authorName = msg?.author?.name;
|
|
236
255
|
const isThoughtMsg =
|
|
237
|
-
authorName ===
|
|
238
|
-
msg?.recipient ===
|
|
239
|
-
msg?.content?.content_type ===
|
|
240
|
-
msg?.content?.content_type ===
|
|
241
|
-
msg?.metadata?.reasoning_status ===
|
|
256
|
+
authorName === "thought" ||
|
|
257
|
+
msg?.recipient === "thought" ||
|
|
258
|
+
msg?.content?.content_type === "thought" ||
|
|
259
|
+
msg?.content?.content_type === "thoughts" ||
|
|
260
|
+
msg?.metadata?.reasoning_status === "is_reasoning";
|
|
242
261
|
|
|
243
|
-
if (
|
|
262
|
+
if (
|
|
263
|
+
role === "user" ||
|
|
264
|
+
role === "assistant" ||
|
|
265
|
+
role === "tool" ||
|
|
266
|
+
isThoughtMsg
|
|
267
|
+
) {
|
|
244
268
|
const segments = [];
|
|
245
269
|
const parts = msg?.content?.parts ?? [];
|
|
246
270
|
|
|
247
271
|
for (const part of parts) {
|
|
248
|
-
let partText =
|
|
272
|
+
let partText = "";
|
|
249
273
|
let isThoughtPart = isThoughtMsg;
|
|
250
274
|
|
|
251
|
-
if (typeof part ===
|
|
275
|
+
if (typeof part === "string") {
|
|
252
276
|
partText = part;
|
|
253
|
-
} else if (part && typeof part ===
|
|
254
|
-
if (part.content_type ===
|
|
277
|
+
} else if (part && typeof part === "object") {
|
|
278
|
+
if (part.content_type === "text" && typeof part.text === "string") {
|
|
255
279
|
partText = part.text;
|
|
256
|
-
} else if (
|
|
280
|
+
} else if (
|
|
281
|
+
part.content_type === "thought" &&
|
|
282
|
+
typeof part.text === "string"
|
|
283
|
+
) {
|
|
257
284
|
partText = part.text;
|
|
258
285
|
isThoughtPart = true;
|
|
259
|
-
} else if (
|
|
286
|
+
} else if (
|
|
287
|
+
part.content_type === "audio_transcription" &&
|
|
288
|
+
typeof part.text === "string"
|
|
289
|
+
) {
|
|
260
290
|
partText = part.text;
|
|
261
291
|
}
|
|
262
292
|
}
|
|
263
293
|
|
|
264
|
-
if (partText && role !==
|
|
294
|
+
if (partText && role !== "tool") {
|
|
265
295
|
const text = partText
|
|
266
|
-
.replace(/\u{E0000}[\u{E0000}-\u{E007F}]*/gu,
|
|
267
|
-
.replace(/citeturn\d+\w*/g,
|
|
296
|
+
.replace(/\u{E0000}[\u{E0000}-\u{E007F}]*/gu, "")
|
|
297
|
+
.replace(/citeturn\d+\w*/g, "")
|
|
268
298
|
.trim();
|
|
269
299
|
if (text) {
|
|
270
300
|
if (isThoughtPart) {
|
|
271
|
-
segments.push({ type:
|
|
301
|
+
segments.push({ type: "thought", content: text });
|
|
272
302
|
} else {
|
|
273
|
-
segments.push({ type:
|
|
303
|
+
segments.push({ type: "text", content: text });
|
|
274
304
|
}
|
|
275
305
|
}
|
|
276
306
|
} else if (
|
|
277
307
|
includeImages &&
|
|
278
|
-
part?.content_type ===
|
|
308
|
+
part?.content_type === "image_asset_pointer" &&
|
|
279
309
|
part?.asset_pointer
|
|
280
310
|
) {
|
|
281
|
-
segments.push({
|
|
311
|
+
segments.push({
|
|
312
|
+
type: "image",
|
|
313
|
+
fileId: part.asset_pointer.split("://")[1],
|
|
314
|
+
});
|
|
282
315
|
}
|
|
283
316
|
}
|
|
284
317
|
|
|
285
318
|
// Handle standalone content.text (e.g. execution_output or plain text)
|
|
286
|
-
if (
|
|
287
|
-
|
|
319
|
+
if (
|
|
320
|
+
typeof msg.content?.text === "string" &&
|
|
321
|
+
msg.content.text.trim() &&
|
|
322
|
+
parts.length === 0
|
|
323
|
+
) {
|
|
324
|
+
segments.push({ type: "text", content: msg.content.text.trim() });
|
|
288
325
|
}
|
|
289
326
|
|
|
290
327
|
// Handle o1/o3/o4 reasoning thoughts array: content.thoughts = [{ summary, content }]
|
|
291
|
-
if (
|
|
328
|
+
if (
|
|
329
|
+
Array.isArray(msg.content?.thoughts) &&
|
|
330
|
+
msg.content.thoughts.length > 0
|
|
331
|
+
) {
|
|
292
332
|
const thoughtParts = msg.content.thoughts
|
|
293
|
-
.map((t) =>
|
|
333
|
+
.map((t) =>
|
|
334
|
+
t.summary
|
|
335
|
+
? `**${t.summary}**\n${t.content || ""}`
|
|
336
|
+
: t.content || "",
|
|
337
|
+
)
|
|
294
338
|
.filter(Boolean);
|
|
295
339
|
if (thoughtParts.length > 0) {
|
|
296
|
-
segments.push({
|
|
340
|
+
segments.push({
|
|
341
|
+
type: "thought",
|
|
342
|
+
content: thoughtParts.join("\n\n"),
|
|
343
|
+
});
|
|
297
344
|
}
|
|
298
345
|
}
|
|
299
346
|
|
|
300
347
|
// Handle reasoning recap
|
|
301
348
|
if (
|
|
302
|
-
(msg.content?.content_type ===
|
|
303
|
-
|
|
349
|
+
(msg.content?.content_type === "reasoning_recap" ||
|
|
350
|
+
msg.content?.content) &&
|
|
351
|
+
typeof msg.content.content === "string" &&
|
|
304
352
|
msg.content.content.trim()
|
|
305
353
|
) {
|
|
306
|
-
segments.push({ type:
|
|
354
|
+
segments.push({ type: "thought", content: msg.content.content.trim() });
|
|
307
355
|
}
|
|
308
356
|
|
|
309
357
|
// Handle Deep Research reports (widget_state)
|
|
@@ -312,14 +360,16 @@ export function linearize(mapping, includeImages, currentNodeId) {
|
|
|
312
360
|
msg.metadata?.tool_response_metadata?.venus_widget_state;
|
|
313
361
|
if (widgetRaw) {
|
|
314
362
|
try {
|
|
315
|
-
const widget =
|
|
316
|
-
|
|
363
|
+
const widget =
|
|
364
|
+
typeof widgetRaw === "string" ? JSON.parse(widgetRaw) : widgetRaw;
|
|
365
|
+
const reportText =
|
|
366
|
+
widget.report_message?.content?.parts?.[0] || widget.markdown;
|
|
317
367
|
const steering = widget.steering_acknowledgement;
|
|
318
|
-
let researchContent =
|
|
368
|
+
let researchContent = "";
|
|
319
369
|
if (steering) researchContent += `${steering}\n\n`;
|
|
320
370
|
if (reportText) researchContent += reportText;
|
|
321
371
|
if (researchContent.trim()) {
|
|
322
|
-
segments.push({ type:
|
|
372
|
+
segments.push({ type: "text", content: researchContent.trim() });
|
|
323
373
|
}
|
|
324
374
|
} catch {
|
|
325
375
|
// Ignore widget state JSON parse errors
|
|
@@ -327,20 +377,33 @@ export function linearize(mapping, includeImages, currentNodeId) {
|
|
|
327
377
|
}
|
|
328
378
|
|
|
329
379
|
// Handle attachments
|
|
330
|
-
if (
|
|
331
|
-
|
|
380
|
+
if (
|
|
381
|
+
Array.isArray(msg.metadata?.attachments) &&
|
|
382
|
+
msg.metadata.attachments.length > 0
|
|
383
|
+
) {
|
|
384
|
+
const fileNames = msg.metadata.attachments
|
|
385
|
+
.map((att) => att.name)
|
|
386
|
+
.filter(Boolean);
|
|
332
387
|
if (fileNames.length > 0) {
|
|
333
|
-
segments.push({
|
|
388
|
+
segments.push({
|
|
389
|
+
type: "text",
|
|
390
|
+
content: `[Attached: ${fileNames.join(", ")}]`,
|
|
391
|
+
});
|
|
334
392
|
}
|
|
335
393
|
}
|
|
336
394
|
|
|
337
395
|
// Handle Canvas documents
|
|
338
396
|
if (msg.metadata?.canvas?.title) {
|
|
339
|
-
segments.push({
|
|
397
|
+
segments.push({
|
|
398
|
+
type: "text",
|
|
399
|
+
content: `[Canvas: ${msg.metadata.canvas.title}]`,
|
|
400
|
+
});
|
|
340
401
|
}
|
|
341
402
|
|
|
342
|
-
const displayRole = role ===
|
|
343
|
-
const timestamp = msg?.create_time
|
|
403
|
+
const displayRole = role === "user" ? "User" : "ChatGPT";
|
|
404
|
+
const timestamp = msg?.create_time
|
|
405
|
+
? new Date(msg.create_time * 1000).toLocaleString()
|
|
406
|
+
: null;
|
|
344
407
|
|
|
345
408
|
if (segments.length) {
|
|
346
409
|
const citeMap = {};
|
|
@@ -348,7 +411,10 @@ export function linearize(mapping, includeImages, currentNodeId) {
|
|
|
348
411
|
for (const ref of msg?.metadata?.content_references ?? []) {
|
|
349
412
|
if (ref.matched_text) {
|
|
350
413
|
if (ref.items?.length) citeMap[ref.matched_text] = ref.items;
|
|
351
|
-
if (
|
|
414
|
+
if (
|
|
415
|
+
ref.type === "image_group" ||
|
|
416
|
+
ref.matched_text.includes("image_group")
|
|
417
|
+
) {
|
|
352
418
|
imageGroupMap[ref.matched_text] = ref;
|
|
353
419
|
}
|
|
354
420
|
}
|
|
@@ -356,9 +422,9 @@ export function linearize(mapping, includeImages, currentNodeId) {
|
|
|
356
422
|
|
|
357
423
|
// If previous message is also ChatGPT, merge segments (thoughts in front, content in back)
|
|
358
424
|
if (
|
|
359
|
-
displayRole ===
|
|
425
|
+
displayRole === "ChatGPT" &&
|
|
360
426
|
messages.length > 0 &&
|
|
361
|
-
messages[messages.length - 1].role ===
|
|
427
|
+
messages[messages.length - 1].role === "ChatGPT"
|
|
362
428
|
) {
|
|
363
429
|
const prevMsg = messages[messages.length - 1];
|
|
364
430
|
if (isThoughtMsg) {
|
|
@@ -372,7 +438,13 @@ export function linearize(mapping, includeImages, currentNodeId) {
|
|
|
372
438
|
prevMsg.timestamp = timestamp;
|
|
373
439
|
}
|
|
374
440
|
} else {
|
|
375
|
-
messages.push({
|
|
441
|
+
messages.push({
|
|
442
|
+
role: displayRole,
|
|
443
|
+
segments,
|
|
444
|
+
citeMap,
|
|
445
|
+
imageGroupMap,
|
|
446
|
+
timestamp,
|
|
447
|
+
});
|
|
376
448
|
}
|
|
377
449
|
}
|
|
378
450
|
}
|
|
@@ -382,12 +454,12 @@ export function linearize(mapping, includeImages, currentNodeId) {
|
|
|
382
454
|
}
|
|
383
455
|
|
|
384
456
|
function cleanMarkdownFromApi(text, citeMap, imageGroupMap) {
|
|
385
|
-
if (!text) return
|
|
457
|
+
if (!text) return "";
|
|
386
458
|
|
|
387
459
|
// 1. Remove specific character ranges (like some PUA ranges)
|
|
388
460
|
text = text
|
|
389
|
-
.replace(/\u{E0000}[\u{E0000}-\u{E007F}]*/gu,
|
|
390
|
-
.replace(/citeturn\d+\w*/g,
|
|
461
|
+
.replace(/\u{E0000}[\u{E0000}-\u{E007F}]*/gu, "")
|
|
462
|
+
.replace(/citeturn\d+\w*/g, "")
|
|
391
463
|
.trim();
|
|
392
464
|
|
|
393
465
|
// 2. Replace ChatGPT PUA URL annotations: url{label}{href}
|
|
@@ -415,87 +487,101 @@ function cleanMarkdownFromApi(text, citeMap, imageGroupMap) {
|
|
|
415
487
|
const markdownImgs = ref.images
|
|
416
488
|
.map((imgObj) => {
|
|
417
489
|
const res = imgObj.image_result || {};
|
|
418
|
-
const title = res.title || imgObj.image_search_query ||
|
|
419
|
-
const src =
|
|
490
|
+
const title = res.title || imgObj.image_search_query || "Image";
|
|
491
|
+
const src =
|
|
492
|
+
res.content_url || res.thumbnail_url || res.original_content_url;
|
|
420
493
|
if (src) {
|
|
421
494
|
return ``;
|
|
422
495
|
}
|
|
423
|
-
return
|
|
496
|
+
return "";
|
|
424
497
|
})
|
|
425
498
|
.filter(Boolean);
|
|
426
499
|
if (markdownImgs.length > 0) {
|
|
427
|
-
return
|
|
500
|
+
return "\n\n" + markdownImgs.join("\n\n") + "\n\n";
|
|
428
501
|
}
|
|
429
502
|
}
|
|
430
|
-
if (
|
|
503
|
+
if (
|
|
504
|
+
ref.safe_urls &&
|
|
505
|
+
Array.isArray(ref.safe_urls) &&
|
|
506
|
+
ref.safe_urls.length > 0
|
|
507
|
+
) {
|
|
431
508
|
return (
|
|
432
|
-
|
|
509
|
+
"\n\n" +
|
|
510
|
+
ref.safe_urls
|
|
511
|
+
.map((url, i) => ``)
|
|
512
|
+
.join("\n\n") +
|
|
513
|
+
"\n\n"
|
|
433
514
|
);
|
|
434
515
|
}
|
|
435
516
|
if (ref.alt) {
|
|
436
|
-
return
|
|
517
|
+
return "\n\n" + ref.alt + "\n\n";
|
|
437
518
|
}
|
|
438
519
|
}
|
|
439
|
-
return
|
|
520
|
+
return "";
|
|
440
521
|
});
|
|
441
522
|
|
|
442
523
|
// 4. Replace ChatGPT PUA cite annotations
|
|
443
|
-
text = text.replace(
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
524
|
+
text = text.replace(
|
|
525
|
+
/\uE200cite(?:\uE202[^\uE202\uE201]+)+\uE201/g,
|
|
526
|
+
(match) => {
|
|
527
|
+
const items = citeMap?.[match] ?? [];
|
|
528
|
+
if (!items.length) return "";
|
|
529
|
+
const formatted = items.map((item) => {
|
|
530
|
+
const label = item.attribution || item.title || "Source";
|
|
531
|
+
return `[${label}](${item.url})`;
|
|
532
|
+
});
|
|
533
|
+
return ` (${formatted.join(", ")})`;
|
|
534
|
+
},
|
|
535
|
+
);
|
|
452
536
|
|
|
453
537
|
return text;
|
|
454
538
|
}
|
|
455
539
|
|
|
456
540
|
export class ChatGPTParser extends ChatParser {
|
|
457
|
-
name =
|
|
541
|
+
name = "ChatGPT";
|
|
458
542
|
constructor() {
|
|
459
543
|
super();
|
|
460
544
|
this.lastFetch = null;
|
|
461
545
|
}
|
|
462
546
|
|
|
463
547
|
isAvailable(url) {
|
|
464
|
-
return url.includes(
|
|
548
|
+
return url.includes("chatgpt.com");
|
|
465
549
|
}
|
|
466
550
|
|
|
467
551
|
getRoleElement(container) {
|
|
468
|
-
if (container.matches?.(
|
|
469
|
-
return container.querySelector?.(
|
|
552
|
+
if (container.matches?.("[data-message-author-role]")) return container;
|
|
553
|
+
return container.querySelector?.("[data-message-author-role]") || null;
|
|
470
554
|
}
|
|
471
555
|
|
|
472
556
|
getRoleElements(container) {
|
|
473
|
-
if (container.matches?.(
|
|
474
|
-
return Array.from(
|
|
557
|
+
if (container.matches?.("[data-message-author-role]")) return [container];
|
|
558
|
+
return Array.from(
|
|
559
|
+
container.querySelectorAll?.("[data-message-author-role]") || [],
|
|
560
|
+
);
|
|
475
561
|
}
|
|
476
562
|
|
|
477
563
|
getMessageRole(container, roleElement) {
|
|
478
|
-
const roleAttr = roleElement?.getAttribute(
|
|
479
|
-
if (roleAttr) return roleAttr ===
|
|
564
|
+
const roleAttr = roleElement?.getAttribute("data-message-author-role");
|
|
565
|
+
if (roleAttr) return roleAttr === "user" ? "User" : "ChatGPT";
|
|
480
566
|
|
|
481
|
-
const text = container.innerText ||
|
|
482
|
-
if (text.startsWith(
|
|
567
|
+
const text = container.innerText || "";
|
|
568
|
+
if (text.startsWith("You\n") || text.includes("\nYou\n")) return "User";
|
|
483
569
|
|
|
484
|
-
return
|
|
570
|
+
return "ChatGPT";
|
|
485
571
|
}
|
|
486
572
|
|
|
487
573
|
getContentElement(container, roleElement) {
|
|
488
|
-
if (roleElement?.getAttribute(
|
|
574
|
+
if (roleElement?.getAttribute("data-message-author-role") === "user") {
|
|
489
575
|
return roleElement;
|
|
490
576
|
}
|
|
491
577
|
|
|
492
|
-
const selectors = [
|
|
578
|
+
const selectors = [".markdown", ".prose", ".whitespace-pre-wrap"];
|
|
493
579
|
for (const selector of selectors) {
|
|
494
580
|
const contentElement = container.querySelector?.(selector);
|
|
495
581
|
if (contentElement) return contentElement;
|
|
496
582
|
}
|
|
497
583
|
|
|
498
|
-
return roleElement || (container.matches?.(
|
|
584
|
+
return roleElement || (container.matches?.("article") ? container : null);
|
|
499
585
|
}
|
|
500
586
|
|
|
501
587
|
getContentElements(container, roleElements) {
|
|
@@ -514,26 +600,27 @@ export class ChatGPTParser extends ChatParser {
|
|
|
514
600
|
|
|
515
601
|
cleanContent(content) {
|
|
516
602
|
return content
|
|
517
|
-
.replace(/^Show moreShow less$/gm,
|
|
518
|
-
.replace(/\n{3,}/g,
|
|
603
|
+
.replace(/^Show moreShow less$/gm, "")
|
|
604
|
+
.replace(/\n{3,}/g, "\n\n")
|
|
519
605
|
.trim();
|
|
520
606
|
}
|
|
521
607
|
|
|
522
608
|
getMessageKey(container, roleElement, role, content) {
|
|
523
609
|
const idElement =
|
|
524
|
-
roleElement?.closest?.(
|
|
525
|
-
|
|
610
|
+
roleElement?.closest?.("[data-message-id]") ||
|
|
611
|
+
container.querySelector?.("[data-message-id]");
|
|
612
|
+
const messageId = idElement?.getAttribute("data-message-id");
|
|
526
613
|
if (messageId) return messageId;
|
|
527
614
|
|
|
528
|
-
const turnId = container.getAttribute?.(
|
|
615
|
+
const turnId = container.getAttribute?.("data-testid");
|
|
529
616
|
if (turnId) return `${turnId}:${role}`;
|
|
530
617
|
|
|
531
|
-
return `${role}:${content.replace(/\s+/g,
|
|
618
|
+
return `${role}:${content.replace(/\s+/g, " ").trim()}`;
|
|
532
619
|
}
|
|
533
620
|
|
|
534
621
|
extractAttachments(container) {
|
|
535
622
|
const attachments = [];
|
|
536
|
-
const rawContent = container.textContent || container.innerText ||
|
|
623
|
+
const rawContent = container.textContent || container.innerText || "";
|
|
537
624
|
const filePatterns = [
|
|
538
625
|
/([a-zA-Z0-9_-]+\.tex)/g,
|
|
539
626
|
/([a-zA-Z0-9_-]+\.txt)/g,
|
|
@@ -549,16 +636,18 @@ export class ChatGPTParser extends ChatParser {
|
|
|
549
636
|
});
|
|
550
637
|
|
|
551
638
|
foundFiles.forEach((fileName) => {
|
|
552
|
-
const fileExt = fileName
|
|
639
|
+
const fileExt = fileName
|
|
640
|
+
.substring(fileName.lastIndexOf(".") + 1)
|
|
641
|
+
.toLowerCase();
|
|
553
642
|
const typeMap = {
|
|
554
|
-
tex:
|
|
555
|
-
txt:
|
|
556
|
-
md:
|
|
557
|
-
pdf:
|
|
558
|
-
doc:
|
|
559
|
-
docx:
|
|
643
|
+
tex: "LaTeX",
|
|
644
|
+
txt: "Text",
|
|
645
|
+
md: "Markdown",
|
|
646
|
+
pdf: "PDF",
|
|
647
|
+
doc: "Document",
|
|
648
|
+
docx: "Document",
|
|
560
649
|
};
|
|
561
|
-
attachments.push({ name: fileName, type: typeMap[fileExt] ||
|
|
650
|
+
attachments.push({ name: fileName, type: typeMap[fileExt] || "File" });
|
|
562
651
|
});
|
|
563
652
|
|
|
564
653
|
return attachments;
|
|
@@ -568,15 +657,15 @@ export class ChatGPTParser extends ChatParser {
|
|
|
568
657
|
const seenSrcs = new Set();
|
|
569
658
|
const capturedImages = [];
|
|
570
659
|
|
|
571
|
-
container.querySelectorAll?.(
|
|
572
|
-
const src = img.getAttribute(
|
|
573
|
-
const alt = img.getAttribute(
|
|
660
|
+
container.querySelectorAll?.("img").forEach((img) => {
|
|
661
|
+
const src = img.getAttribute("src");
|
|
662
|
+
const alt = img.getAttribute("alt") || "Image";
|
|
574
663
|
const isContentImage =
|
|
575
|
-
src?.includes(
|
|
576
|
-
src?.includes(
|
|
577
|
-
src?.startsWith(
|
|
578
|
-
alt.includes(
|
|
579
|
-
alt.includes(
|
|
664
|
+
src?.includes("backend-api") ||
|
|
665
|
+
src?.includes("files") ||
|
|
666
|
+
src?.startsWith("blob:") ||
|
|
667
|
+
alt.includes("Uploaded") ||
|
|
668
|
+
alt.includes("Generated");
|
|
580
669
|
|
|
581
670
|
if (src && !seenSrcs.has(src) && isContentImage) {
|
|
582
671
|
seenSrcs.add(src);
|
|
@@ -599,17 +688,17 @@ export class ChatGPTParser extends ChatParser {
|
|
|
599
688
|
Object.entries(groupedAttachments).forEach(([type, files]) => {
|
|
600
689
|
attachmentLines.push(`**${type} Files:**`);
|
|
601
690
|
files.forEach((file) => attachmentLines.push(`- ${file}`));
|
|
602
|
-
attachmentLines.push(
|
|
691
|
+
attachmentLines.push("");
|
|
603
692
|
});
|
|
604
693
|
|
|
605
694
|
if (capturedImages.length > 0) {
|
|
606
|
-
if (attachmentLines.length > 0) attachmentLines.push(
|
|
607
|
-
attachmentLines.push(
|
|
695
|
+
if (attachmentLines.length > 0) attachmentLines.push("");
|
|
696
|
+
attachmentLines.push("**Images:**");
|
|
608
697
|
capturedImages.forEach((image) => attachmentLines.push(`- ${image}`));
|
|
609
698
|
}
|
|
610
699
|
|
|
611
700
|
if (attachmentLines.length === 0) return content;
|
|
612
|
-
return `${content}\n\n**Attachments & Images:**\n${attachmentLines.join(
|
|
701
|
+
return `${content}\n\n**Attachments & Images:**\n${attachmentLines.join("\n")}`;
|
|
613
702
|
}
|
|
614
703
|
|
|
615
704
|
convertContentElement(contentElement) {
|
|
@@ -623,30 +712,41 @@ export class ChatGPTParser extends ChatParser {
|
|
|
623
712
|
if (contentElements.length === 0) return null;
|
|
624
713
|
|
|
625
714
|
const role = this.getMessageRole(container, roleElement);
|
|
626
|
-
const noiseSelectors = [
|
|
715
|
+
const noiseSelectors = [
|
|
716
|
+
".flex.gap-2",
|
|
717
|
+
"button",
|
|
718
|
+
".sr-only",
|
|
719
|
+
'[role="button"]',
|
|
720
|
+
];
|
|
627
721
|
const contentParts = contentElements
|
|
628
722
|
.map((contentElement) => {
|
|
629
723
|
const clone = contentElement.cloneNode(true);
|
|
630
|
-
clone.querySelectorAll(
|
|
631
|
-
const img = button.querySelector(
|
|
724
|
+
clone.querySelectorAll("button").forEach((button) => {
|
|
725
|
+
const img = button.querySelector("img");
|
|
632
726
|
if (!img) return;
|
|
633
727
|
|
|
634
|
-
let caption =
|
|
635
|
-
const ariaLabel = button.getAttribute(
|
|
636
|
-
if (ariaLabel.toLowerCase().includes(
|
|
637
|
-
caption = ariaLabel
|
|
638
|
-
|
|
639
|
-
|
|
728
|
+
let caption = "Image";
|
|
729
|
+
const ariaLabel = button.getAttribute("aria-label") || "";
|
|
730
|
+
if (ariaLabel.toLowerCase().includes("open image details for")) {
|
|
731
|
+
caption = ariaLabel
|
|
732
|
+
.replace(/^Open image details for\s*/i, "")
|
|
733
|
+
.trim();
|
|
734
|
+
} else if (
|
|
735
|
+
img.getAttribute("alt") &&
|
|
736
|
+
!img.getAttribute("alt").startsWith("http")
|
|
737
|
+
) {
|
|
738
|
+
caption = img.getAttribute("alt").trim();
|
|
640
739
|
}
|
|
641
740
|
|
|
642
|
-
const alt = img.getAttribute(
|
|
643
|
-
const src = img.getAttribute(
|
|
644
|
-
const imageUrl =
|
|
741
|
+
const alt = img.getAttribute("alt") || "";
|
|
742
|
+
const src = img.getAttribute("src") || "";
|
|
743
|
+
const imageUrl =
|
|
744
|
+
alt.startsWith("http://") || alt.startsWith("https://") ? alt : src;
|
|
645
745
|
|
|
646
746
|
if (imageUrl) {
|
|
647
|
-
const newImg = clone.ownerDocument.createElement(
|
|
648
|
-
newImg.setAttribute(
|
|
649
|
-
newImg.setAttribute(
|
|
747
|
+
const newImg = clone.ownerDocument.createElement("img");
|
|
748
|
+
newImg.setAttribute("src", imageUrl);
|
|
749
|
+
newImg.setAttribute("alt", caption);
|
|
650
750
|
button.parentNode.replaceChild(newImg, button);
|
|
651
751
|
}
|
|
652
752
|
});
|
|
@@ -658,7 +758,7 @@ export class ChatGPTParser extends ChatParser {
|
|
|
658
758
|
})
|
|
659
759
|
.filter(Boolean);
|
|
660
760
|
|
|
661
|
-
let content = contentParts.join(
|
|
761
|
+
let content = contentParts.join("\n\n");
|
|
662
762
|
content = this.appendAttachments(
|
|
663
763
|
content,
|
|
664
764
|
this.extractAttachments(container),
|
|
@@ -675,11 +775,11 @@ export class ChatGPTParser extends ChatParser {
|
|
|
675
775
|
}
|
|
676
776
|
|
|
677
777
|
extractMountedMessages() {
|
|
678
|
-
const articles = Array.from(document.querySelectorAll(
|
|
778
|
+
const articles = Array.from(document.querySelectorAll("article"));
|
|
679
779
|
const containers =
|
|
680
780
|
articles.length > 0
|
|
681
781
|
? articles
|
|
682
|
-
: Array.from(document.querySelectorAll(
|
|
782
|
+
: Array.from(document.querySelectorAll("[data-message-author-role]"));
|
|
683
783
|
|
|
684
784
|
return containers
|
|
685
785
|
.map((container) => this.extractMessage(container))
|
|
@@ -701,16 +801,22 @@ export class ChatGPTParser extends ChatParser {
|
|
|
701
801
|
formatApiResult(convoData, apiMessages, fallbackTitle, images = {}) {
|
|
702
802
|
const messages = [];
|
|
703
803
|
for (const msg of apiMessages) {
|
|
704
|
-
let content =
|
|
804
|
+
let content = "";
|
|
705
805
|
for (const seg of msg.segments) {
|
|
706
|
-
if (seg.type ===
|
|
707
|
-
content +=
|
|
708
|
-
|
|
709
|
-
|
|
806
|
+
if (seg.type === "text") {
|
|
807
|
+
content +=
|
|
808
|
+
cleanMarkdownFromApi(seg.content, msg.citeMap, msg.imageGroupMap) +
|
|
809
|
+
"\n\n";
|
|
810
|
+
} else if (seg.type === "thought") {
|
|
811
|
+
const thoughtText = cleanMarkdownFromApi(
|
|
812
|
+
seg.content,
|
|
813
|
+
msg.citeMap,
|
|
814
|
+
msg.imageGroupMap,
|
|
815
|
+
);
|
|
710
816
|
if (thoughtText) {
|
|
711
817
|
content += `<details><summary>Thought Process</summary>\n\n${thoughtText}\n\n</details>\n\n`;
|
|
712
818
|
}
|
|
713
|
-
} else if (seg.type ===
|
|
819
|
+
} else if (seg.type === "image") {
|
|
714
820
|
const src = images[seg.fileId];
|
|
715
821
|
if (src) {
|
|
716
822
|
content += `\n\n`;
|
|
@@ -731,48 +837,55 @@ export class ChatGPTParser extends ChatParser {
|
|
|
731
837
|
}
|
|
732
838
|
|
|
733
839
|
const currentUrl =
|
|
734
|
-
typeof window !==
|
|
840
|
+
typeof window !== "undefined" && window.location
|
|
841
|
+
? window.location.href || ""
|
|
842
|
+
: "";
|
|
735
843
|
const convTitle = convoData?.title || fallbackTitle;
|
|
736
844
|
const metadata = {
|
|
737
|
-
Source:
|
|
845
|
+
Source: "ChatGPT",
|
|
738
846
|
Date: new Date().toLocaleString(),
|
|
739
847
|
Link: currentUrl,
|
|
740
848
|
Model:
|
|
741
849
|
convoData?.model_slug ||
|
|
742
|
-
document.querySelector('[data-testid="model-selector-dropdown"]')
|
|
743
|
-
|
|
850
|
+
document.querySelector('[data-testid="model-selector-dropdown"]')
|
|
851
|
+
?.innerText ||
|
|
852
|
+
"ChatGPT",
|
|
744
853
|
};
|
|
745
854
|
|
|
746
855
|
return { title: convTitle, messages, url: currentUrl, metadata };
|
|
747
856
|
}
|
|
748
857
|
|
|
749
858
|
async parse(options = {}) {
|
|
750
|
-
const title = document.title ||
|
|
859
|
+
const title = document.title || "ChatGPT Session";
|
|
751
860
|
const messages = [];
|
|
752
861
|
|
|
753
862
|
const token = getAccessToken();
|
|
754
863
|
const convId = getConversationId();
|
|
755
|
-
const parserMode = options.parserMode ||
|
|
864
|
+
const parserMode = options.parserMode || "auto";
|
|
756
865
|
const includeImages = options.includeImages !== false;
|
|
757
866
|
|
|
758
867
|
// 1. If on shared chat URL (/share/...) or SSR conversation data exists in DOM, try SSR data first
|
|
759
868
|
const isShareUrl =
|
|
760
|
-
typeof window !==
|
|
869
|
+
typeof window !== "undefined" &&
|
|
761
870
|
window.location &&
|
|
762
|
-
(window.location.pathname ||
|
|
871
|
+
(window.location.pathname || "").startsWith("/share/");
|
|
763
872
|
const sharedData = extractSharedConversationFromDom(
|
|
764
|
-
typeof document !==
|
|
873
|
+
typeof document !== "undefined" ? document : null,
|
|
765
874
|
);
|
|
766
875
|
|
|
767
|
-
if (isShareUrl && sharedData?.mapping && parserMode !==
|
|
768
|
-
const apiMessages = linearize(
|
|
876
|
+
if (isShareUrl && sharedData?.mapping && parserMode !== "prefer_dom") {
|
|
877
|
+
const apiMessages = linearize(
|
|
878
|
+
sharedData.mapping,
|
|
879
|
+
includeImages,
|
|
880
|
+
sharedData.current_node,
|
|
881
|
+
);
|
|
769
882
|
if (apiMessages.length > 0) {
|
|
770
883
|
return this.formatApiResult(sharedData, apiMessages, title);
|
|
771
884
|
}
|
|
772
885
|
}
|
|
773
886
|
|
|
774
887
|
// 2. Try fetching from ChatGPT backend API
|
|
775
|
-
if (token && convId && parserMode !==
|
|
888
|
+
if (token && convId && parserMode !== "prefer_dom") {
|
|
776
889
|
try {
|
|
777
890
|
const now = Date.now();
|
|
778
891
|
let result;
|
|
@@ -785,10 +898,10 @@ export class ChatGPTParser extends ChatParser {
|
|
|
785
898
|
) {
|
|
786
899
|
result = this.lastFetch.result;
|
|
787
900
|
} else {
|
|
788
|
-
if (!document.getElementById(
|
|
789
|
-
const script = document.createElement(
|
|
790
|
-
script.src = chrome.runtime.getURL(
|
|
791
|
-
script.id =
|
|
901
|
+
if (!document.getElementById("ai-export-chatgpt-helper")) {
|
|
902
|
+
const script = document.createElement("script");
|
|
903
|
+
script.src = chrome.runtime.getURL("content/chatgpt_helper.js");
|
|
904
|
+
script.id = "ai-export-chatgpt-helper";
|
|
792
905
|
script.onload = function () {
|
|
793
906
|
this.remove();
|
|
794
907
|
};
|
|
@@ -805,49 +918,69 @@ export class ChatGPTParser extends ChatParser {
|
|
|
805
918
|
};
|
|
806
919
|
}
|
|
807
920
|
|
|
808
|
-
const apiMessages = linearize(
|
|
921
|
+
const apiMessages = linearize(
|
|
922
|
+
result.data.mapping,
|
|
923
|
+
includeImages,
|
|
924
|
+
result.data.current_node,
|
|
925
|
+
);
|
|
809
926
|
if (apiMessages.length > 0) {
|
|
810
|
-
return this.formatApiResult(
|
|
927
|
+
return this.formatApiResult(
|
|
928
|
+
result.data,
|
|
929
|
+
apiMessages,
|
|
930
|
+
title,
|
|
931
|
+
result.images,
|
|
932
|
+
);
|
|
811
933
|
}
|
|
812
934
|
} catch (e) {
|
|
813
|
-
console.error(
|
|
935
|
+
console.error(
|
|
936
|
+
"[AI Exporter] API parse failed, falling back to SSR/DOM:",
|
|
937
|
+
e,
|
|
938
|
+
);
|
|
814
939
|
}
|
|
815
940
|
}
|
|
816
941
|
|
|
817
942
|
// 3. If API failed or was not available, check if SSR shared/embedded conversation data exists
|
|
818
|
-
if (sharedData?.mapping && parserMode !==
|
|
819
|
-
const apiMessages = linearize(
|
|
943
|
+
if (sharedData?.mapping && parserMode !== "prefer_dom") {
|
|
944
|
+
const apiMessages = linearize(
|
|
945
|
+
sharedData.mapping,
|
|
946
|
+
includeImages,
|
|
947
|
+
sharedData.current_node,
|
|
948
|
+
);
|
|
820
949
|
if (apiMessages.length > 0) {
|
|
821
950
|
return this.formatApiResult(sharedData, apiMessages, title);
|
|
822
951
|
}
|
|
823
952
|
}
|
|
824
953
|
|
|
825
954
|
// Check if we have iframe-based content (deep research feature)
|
|
826
|
-
const iframes = document.querySelectorAll(
|
|
955
|
+
const iframes = document.querySelectorAll(
|
|
956
|
+
'iframe[src*="oaiusercontent.com"]',
|
|
957
|
+
);
|
|
827
958
|
if (iframes.length > 0) {
|
|
828
|
-
console.log(
|
|
959
|
+
console.log("Detected iframe-based content, attempting extraction...");
|
|
829
960
|
|
|
830
961
|
// Try multiple strategies to extract content
|
|
831
|
-
let extractedContent =
|
|
962
|
+
let extractedContent = "";
|
|
832
963
|
|
|
833
964
|
// Strategy 1: Look for data in script tags or window objects
|
|
834
965
|
try {
|
|
835
966
|
// Check if any conversation data is exposed globally
|
|
836
967
|
if (window.conversationData || window.chatData) {
|
|
837
|
-
extractedContent = JSON.stringify(
|
|
968
|
+
extractedContent = JSON.stringify(
|
|
969
|
+
window.conversationData || window.chatData,
|
|
970
|
+
);
|
|
838
971
|
}
|
|
839
972
|
} catch (e) {
|
|
840
|
-
console.log(
|
|
973
|
+
console.log("Global data access failed:", e);
|
|
841
974
|
}
|
|
842
975
|
|
|
843
976
|
// Strategy 2: Look for preloaded content in hidden elements
|
|
844
977
|
if (!extractedContent) {
|
|
845
978
|
const hiddenSelectors = [
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
979
|
+
"[data-conversation]",
|
|
980
|
+
"[data-messages]",
|
|
981
|
+
".conversation-data",
|
|
982
|
+
".chat-transcript",
|
|
983
|
+
"pre[data-conversation]",
|
|
851
984
|
];
|
|
852
985
|
|
|
853
986
|
for (const selector of hiddenSelectors) {
|
|
@@ -862,34 +995,34 @@ export class ChatGPTParser extends ChatParser {
|
|
|
862
995
|
// Strategy 3: Enhanced text extraction from main content
|
|
863
996
|
if (!extractedContent) {
|
|
864
997
|
const mainContent =
|
|
865
|
-
document.querySelector(
|
|
998
|
+
document.querySelector("main") ||
|
|
866
999
|
document.querySelector('[role="main"]') ||
|
|
867
|
-
document.querySelector(
|
|
1000
|
+
document.querySelector(".conversation") ||
|
|
868
1001
|
document.body;
|
|
869
1002
|
|
|
870
1003
|
if (mainContent) {
|
|
871
1004
|
const textContent = mainContent.textContent || mainContent.innerText;
|
|
872
1005
|
if (textContent && textContent.trim()) {
|
|
873
|
-
const lines = textContent.split(
|
|
1006
|
+
const lines = textContent.split("\n").filter((line) => line.trim());
|
|
874
1007
|
|
|
875
1008
|
// Look for conversation patterns
|
|
876
1009
|
const conversationLines = lines.filter(
|
|
877
1010
|
(line) =>
|
|
878
1011
|
line.length > 20 && // Substantial content
|
|
879
|
-
!line.includes(
|
|
880
|
-
!line.includes(
|
|
881
|
-
!line.includes(
|
|
882
|
-
!line.includes(
|
|
883
|
-
!line.includes(
|
|
884
|
-
!line.includes(
|
|
885
|
-
!line.includes(
|
|
886
|
-
!line.includes(
|
|
887
|
-
!line.includes(
|
|
888
|
-
!line.includes(
|
|
1012
|
+
!line.includes("ChatGPT") &&
|
|
1013
|
+
!line.includes("Regenerate") &&
|
|
1014
|
+
!line.includes("Copy code") &&
|
|
1015
|
+
!line.includes("Continue") &&
|
|
1016
|
+
!line.includes("Share") &&
|
|
1017
|
+
!line.includes("Thumb") &&
|
|
1018
|
+
!line.includes("New chat") &&
|
|
1019
|
+
!line.includes("Menu") &&
|
|
1020
|
+
!line.includes("Settings") &&
|
|
1021
|
+
!line.includes("History"),
|
|
889
1022
|
);
|
|
890
1023
|
|
|
891
1024
|
if (conversationLines.length > 0) {
|
|
892
|
-
extractedContent = conversationLines.join(
|
|
1025
|
+
extractedContent = conversationLines.join("\n\n");
|
|
893
1026
|
}
|
|
894
1027
|
}
|
|
895
1028
|
}
|
|
@@ -906,24 +1039,26 @@ export class ChatGPTParser extends ChatParser {
|
|
|
906
1039
|
// If we found content, try to structure it
|
|
907
1040
|
if (extractedContent) {
|
|
908
1041
|
// Try to identify user vs assistant messages
|
|
909
|
-
const lines = extractedContent
|
|
1042
|
+
const lines = extractedContent
|
|
1043
|
+
.split("\n")
|
|
1044
|
+
.filter((line) => line.trim());
|
|
910
1045
|
|
|
911
1046
|
lines.forEach((line) => {
|
|
912
1047
|
if (line.length > 10) {
|
|
913
1048
|
// Simple heuristic: shorter lines are often user prompts
|
|
914
1049
|
if (
|
|
915
1050
|
line.length < 200 ||
|
|
916
|
-
line.includes(
|
|
917
|
-
line.includes(
|
|
918
|
-
line.includes(
|
|
1051
|
+
line.includes("?") ||
|
|
1052
|
+
line.includes("write") ||
|
|
1053
|
+
line.includes("tell")
|
|
919
1054
|
) {
|
|
920
1055
|
messages.push({
|
|
921
|
-
role:
|
|
1056
|
+
role: "User",
|
|
922
1057
|
content: line.trim(),
|
|
923
1058
|
});
|
|
924
1059
|
} else {
|
|
925
1060
|
messages.push({
|
|
926
|
-
role:
|
|
1061
|
+
role: "ChatGPT",
|
|
927
1062
|
content: line.trim(),
|
|
928
1063
|
});
|
|
929
1064
|
}
|
|
@@ -934,16 +1069,16 @@ export class ChatGPTParser extends ChatParser {
|
|
|
934
1069
|
// Add note about extraction method
|
|
935
1070
|
if (messages.length > 0) {
|
|
936
1071
|
messages.push({
|
|
937
|
-
role:
|
|
1072
|
+
role: "ChatGPT",
|
|
938
1073
|
content:
|
|
939
|
-
|
|
1074
|
+
"*Note: Content extracted from iframe-based ChatGPT interface. Some formatting may be lost.*",
|
|
940
1075
|
});
|
|
941
1076
|
} else {
|
|
942
1077
|
// Last resort - add a message explaining the limitation
|
|
943
1078
|
messages.push({
|
|
944
|
-
role:
|
|
1079
|
+
role: "ChatGPT",
|
|
945
1080
|
content:
|
|
946
|
-
|
|
1081
|
+
"*Note: ChatGPT is using iframe-based content that cannot be accessed by browser extensions. Please try exporting from a standard ChatGPT conversation.*",
|
|
947
1082
|
});
|
|
948
1083
|
}
|
|
949
1084
|
|
|
@@ -955,17 +1090,22 @@ export class ChatGPTParser extends ChatParser {
|
|
|
955
1090
|
? await this.extractAllConversationTurns()
|
|
956
1091
|
: this.extractMountedMessages();
|
|
957
1092
|
messages.push(
|
|
958
|
-
...(extractedMessages.length > 0
|
|
1093
|
+
...(extractedMessages.length > 0
|
|
1094
|
+
? extractedMessages
|
|
1095
|
+
: this.extractMountedMessages()),
|
|
959
1096
|
);
|
|
960
1097
|
|
|
961
1098
|
const currentUrl =
|
|
962
|
-
typeof window !==
|
|
1099
|
+
typeof window !== "undefined" && window.location
|
|
1100
|
+
? window.location.href || ""
|
|
1101
|
+
: "";
|
|
963
1102
|
const metadata = {
|
|
964
|
-
Source:
|
|
1103
|
+
Source: "ChatGPT",
|
|
965
1104
|
Date: new Date().toLocaleString(),
|
|
966
1105
|
Link: currentUrl,
|
|
967
1106
|
Model:
|
|
968
|
-
document.querySelector('[data-testid="model-selector-dropdown"]')
|
|
1107
|
+
document.querySelector('[data-testid="model-selector-dropdown"]')
|
|
1108
|
+
?.innerText || "ChatGPT",
|
|
969
1109
|
};
|
|
970
1110
|
|
|
971
1111
|
return { title, messages, url: currentUrl, metadata };
|