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_helper.js
CHANGED
|
@@ -8,7 +8,9 @@ if (!window.__chatgptHelperInjected) {
|
|
|
8
8
|
|
|
9
9
|
function getCookieDeviceId() {
|
|
10
10
|
try {
|
|
11
|
-
const match =
|
|
11
|
+
const match =
|
|
12
|
+
typeof document !== "undefined" &&
|
|
13
|
+
document.cookie.match(/oai-did=([^;]+)/);
|
|
12
14
|
return match ? match[1] : null;
|
|
13
15
|
} catch {
|
|
14
16
|
return null;
|
|
@@ -16,19 +18,23 @@ if (!window.__chatgptHelperInjected) {
|
|
|
16
18
|
}
|
|
17
19
|
|
|
18
20
|
// Hook fetch to store Authorization and custom headers when page performs network calls
|
|
19
|
-
if (typeof window !==
|
|
21
|
+
if (typeof window !== "undefined" && window.fetch) {
|
|
20
22
|
const originalFetch = window.fetch;
|
|
21
23
|
window.fetch = function (...args) {
|
|
22
24
|
try {
|
|
23
|
-
const url = typeof args[0] ===
|
|
24
|
-
if (
|
|
25
|
+
const url = typeof args[0] === "string" ? args[0] : args[0]?.url;
|
|
26
|
+
if (
|
|
27
|
+
url &&
|
|
28
|
+
(url.includes("/backend-api/") || url.includes("/api/auth/"))
|
|
29
|
+
) {
|
|
25
30
|
const options = args[1];
|
|
26
31
|
if (options?.headers) {
|
|
27
32
|
let auth = null;
|
|
28
33
|
if (options.headers instanceof Headers) {
|
|
29
|
-
auth = options.headers.get(
|
|
30
|
-
} else if (typeof options.headers ===
|
|
31
|
-
auth =
|
|
34
|
+
auth = options.headers.get("Authorization");
|
|
35
|
+
} else if (typeof options.headers === "object") {
|
|
36
|
+
auth =
|
|
37
|
+
options.headers.Authorization || options.headers.authorization;
|
|
32
38
|
}
|
|
33
39
|
if (auth) {
|
|
34
40
|
window.capturedAuthStore.authorization = auth;
|
|
@@ -42,31 +48,36 @@ if (!window.__chatgptHelperInjected) {
|
|
|
42
48
|
};
|
|
43
49
|
}
|
|
44
50
|
|
|
45
|
-
window.addEventListener(
|
|
46
|
-
if (event.origin !==
|
|
47
|
-
if (event.data?.source !==
|
|
48
|
-
if (event.data?.type !==
|
|
51
|
+
window.addEventListener("message", async (event) => {
|
|
52
|
+
if (event.origin !== "https://chatgpt.com") return;
|
|
53
|
+
if (event.data?.source !== "chatgpt-exporter-ext") return;
|
|
54
|
+
if (event.data?.type !== "fetch_conversation") return;
|
|
49
55
|
|
|
50
56
|
const { convId, token, requestId, includeImages } = event.data;
|
|
51
57
|
|
|
52
58
|
try {
|
|
53
59
|
const headers = {
|
|
54
|
-
Accept:
|
|
60
|
+
Accept: "application/json",
|
|
55
61
|
};
|
|
56
62
|
|
|
57
|
-
const authToken = token
|
|
63
|
+
const authToken = token
|
|
64
|
+
? `Bearer ${token}`
|
|
65
|
+
: window.capturedAuthStore?.authorization;
|
|
58
66
|
if (authToken) {
|
|
59
67
|
headers.Authorization = authToken;
|
|
60
68
|
}
|
|
61
69
|
|
|
62
70
|
const deviceId = getCookieDeviceId();
|
|
63
71
|
if (deviceId) {
|
|
64
|
-
headers[
|
|
72
|
+
headers["oai-device-id"] = deviceId;
|
|
65
73
|
}
|
|
66
74
|
|
|
67
|
-
const res = await fetch(
|
|
68
|
-
|
|
69
|
-
|
|
75
|
+
const res = await fetch(
|
|
76
|
+
`https://chatgpt.com/backend-api/conversation/${convId}`,
|
|
77
|
+
{
|
|
78
|
+
headers,
|
|
79
|
+
},
|
|
80
|
+
);
|
|
70
81
|
if (!res.ok) throw new Error(`API returned ${res.status}`);
|
|
71
82
|
const data = await res.json();
|
|
72
83
|
|
|
@@ -77,8 +88,12 @@ if (!window.__chatgptHelperInjected) {
|
|
|
77
88
|
const msg = node.message;
|
|
78
89
|
if (msg && msg.content && Array.isArray(msg.content.parts)) {
|
|
79
90
|
for (const part of msg.content.parts) {
|
|
80
|
-
if (
|
|
81
|
-
|
|
91
|
+
if (
|
|
92
|
+
part &&
|
|
93
|
+
part.content_type === "image_asset_pointer" &&
|
|
94
|
+
part.asset_pointer
|
|
95
|
+
) {
|
|
96
|
+
fileIds.add(part.asset_pointer.split("://")[1]);
|
|
82
97
|
}
|
|
83
98
|
}
|
|
84
99
|
}
|
|
@@ -90,7 +105,7 @@ if (!window.__chatgptHelperInjected) {
|
|
|
90
105
|
const b64 = await fetchImageAsBase64(id, token);
|
|
91
106
|
return [id, b64];
|
|
92
107
|
} catch (e) {
|
|
93
|
-
console.error(
|
|
108
|
+
console.error("[AI Exporter] Error fetching image:", id, e);
|
|
94
109
|
return [id, null];
|
|
95
110
|
}
|
|
96
111
|
}),
|
|
@@ -99,22 +114,25 @@ if (!window.__chatgptHelperInjected) {
|
|
|
99
114
|
}
|
|
100
115
|
|
|
101
116
|
window.postMessage(
|
|
102
|
-
{ source:
|
|
103
|
-
|
|
117
|
+
{ source: "chatgpt-exporter-page", requestId, data, images },
|
|
118
|
+
"https://chatgpt.com",
|
|
104
119
|
);
|
|
105
120
|
} catch (err) {
|
|
106
121
|
window.postMessage(
|
|
107
|
-
{ source:
|
|
108
|
-
|
|
122
|
+
{ source: "chatgpt-exporter-page", requestId, error: err.message },
|
|
123
|
+
"https://chatgpt.com",
|
|
109
124
|
);
|
|
110
125
|
}
|
|
111
126
|
});
|
|
112
127
|
|
|
113
128
|
async function fetchImageAsBase64(fileId, token) {
|
|
114
129
|
try {
|
|
115
|
-
const dlRes = await fetch(
|
|
116
|
-
|
|
117
|
-
|
|
130
|
+
const dlRes = await fetch(
|
|
131
|
+
`https://chatgpt.com/backend-api/files/download/${fileId}`,
|
|
132
|
+
{
|
|
133
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
134
|
+
},
|
|
135
|
+
);
|
|
118
136
|
if (!dlRes.ok) return null;
|
|
119
137
|
const { download_url } = await dlRes.json();
|
|
120
138
|
if (!download_url) return null;
|
|
@@ -130,7 +148,7 @@ if (!window.__chatgptHelperInjected) {
|
|
|
130
148
|
reader.readAsDataURL(blob);
|
|
131
149
|
});
|
|
132
150
|
} catch (e) {
|
|
133
|
-
console.error(
|
|
151
|
+
console.error("[AI Exporter] fetchImageAsBase64 error:", e);
|
|
134
152
|
return null;
|
|
135
153
|
}
|
|
136
154
|
}
|
|
@@ -12,7 +12,7 @@ function isScrollable(element) {
|
|
|
12
12
|
|
|
13
13
|
function messageKey(message) {
|
|
14
14
|
if (message.key) return message.key;
|
|
15
|
-
return `${message.role}:${message.content.replace(/\s+/g,
|
|
15
|
+
return `${message.role}:${message.content.replace(/\s+/g, " ").trim()}`;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
function publicMessage(message) {
|
|
@@ -23,17 +23,18 @@ function publicMessage(message) {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
export function getConversationTurnIndex(turn) {
|
|
26
|
-
if (!turn || typeof turn.getAttribute !==
|
|
27
|
-
|
|
26
|
+
if (!turn || typeof turn.getAttribute !== "function")
|
|
27
|
+
return Number.POSITIVE_INFINITY;
|
|
28
|
+
const testId = turn.getAttribute("data-testid") || "";
|
|
28
29
|
const match = testId.match(/^conversation-turn-(\d+)$/);
|
|
29
30
|
if (match) return Number(match[1]);
|
|
30
|
-
const turnId = turn.getAttribute(
|
|
31
|
+
const turnId = turn.getAttribute("data-turn-id");
|
|
31
32
|
if (turnId && /^\d+$/.test(turnId)) return Number(turnId);
|
|
32
33
|
return Number.POSITIVE_INFINITY;
|
|
33
34
|
}
|
|
34
35
|
|
|
35
36
|
export function getConversationTurns(doc = document) {
|
|
36
|
-
if (!doc || typeof doc.querySelectorAll !==
|
|
37
|
+
if (!doc || typeof doc.querySelectorAll !== "function") return [];
|
|
37
38
|
const turns = Array.from(doc.querySelectorAll(TURN_SELECTOR));
|
|
38
39
|
// Filter out nested duplicates (e.g. [data-message-author-role] inside an article)
|
|
39
40
|
const filtered = turns.filter((el) => {
|
|
@@ -44,7 +45,7 @@ export function getConversationTurns(doc = document) {
|
|
|
44
45
|
const idxA = getConversationTurnIndex(a);
|
|
45
46
|
const idxB = getConversationTurnIndex(b);
|
|
46
47
|
if (idxA !== idxB) return idxA - idxB;
|
|
47
|
-
if (typeof a.compareDocumentPosition ===
|
|
48
|
+
if (typeof a.compareDocumentPosition === "function") {
|
|
48
49
|
return a.compareDocumentPosition(b) &
|
|
49
50
|
(doc.defaultView?.Node?.DOCUMENT_POSITION_FOLLOWING || 4)
|
|
50
51
|
? -1
|
|
@@ -63,35 +64,37 @@ export function findChatGPTScrollRoot(turns, doc = document) {
|
|
|
63
64
|
current = current.parentElement;
|
|
64
65
|
}
|
|
65
66
|
|
|
66
|
-
const main = doc.querySelector(
|
|
67
|
+
const main = doc.querySelector("main");
|
|
67
68
|
if (isScrollable(main)) return main;
|
|
68
69
|
|
|
69
70
|
return doc.scrollingElement || doc.documentElement || doc.body;
|
|
70
71
|
}
|
|
71
72
|
|
|
72
73
|
function createProgressOverlay(doc) {
|
|
73
|
-
if (!doc || typeof doc.createElement !==
|
|
74
|
+
if (!doc || typeof doc.createElement !== "function" || !doc.body) return null;
|
|
74
75
|
try {
|
|
75
|
-
const overlay = doc.createElement(
|
|
76
|
-
overlay.id =
|
|
76
|
+
const overlay = doc.createElement("div");
|
|
77
|
+
overlay.id = "ai-exporter-progress-overlay";
|
|
77
78
|
Object.assign(overlay.style, {
|
|
78
|
-
position:
|
|
79
|
-
top:
|
|
80
|
-
left:
|
|
81
|
-
transform:
|
|
82
|
-
zIndex:
|
|
83
|
-
padding:
|
|
84
|
-
background:
|
|
85
|
-
color:
|
|
86
|
-
borderRadius:
|
|
87
|
-
fontFamily:
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
79
|
+
position: "fixed",
|
|
80
|
+
top: "16px",
|
|
81
|
+
left: "50%",
|
|
82
|
+
transform: "translateX(-50%)",
|
|
83
|
+
zIndex: "999999",
|
|
84
|
+
padding: "10px 20px",
|
|
85
|
+
background: "rgba(15, 23, 42, 0.92)",
|
|
86
|
+
color: "#f8fafc",
|
|
87
|
+
borderRadius: "8px",
|
|
88
|
+
fontFamily:
|
|
89
|
+
'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
|
|
90
|
+
fontSize: "14px",
|
|
91
|
+
fontWeight: "500",
|
|
92
|
+
boxShadow:
|
|
93
|
+
"0 10px 25px -5px rgba(0, 0, 0, 0.3), 0 8px 10px -6px rgba(0, 0, 0, 0.3)",
|
|
94
|
+
pointerEvents: "none",
|
|
95
|
+
transition: "opacity 0.2s ease",
|
|
93
96
|
});
|
|
94
|
-
overlay.textContent =
|
|
97
|
+
overlay.textContent = "Preparing conversation export...";
|
|
95
98
|
doc.body.appendChild(overlay);
|
|
96
99
|
return overlay;
|
|
97
100
|
} catch {
|
|
@@ -108,7 +111,7 @@ function updateProgressOverlay(overlay, current, total) {
|
|
|
108
111
|
function removeProgressOverlay(overlay) {
|
|
109
112
|
if (!overlay) return;
|
|
110
113
|
try {
|
|
111
|
-
overlay.style.opacity =
|
|
114
|
+
overlay.style.opacity = "0";
|
|
112
115
|
setTimeout(() => overlay.remove(), 200);
|
|
113
116
|
} catch {
|
|
114
117
|
// Ignore cleanup errors
|
|
@@ -122,14 +125,14 @@ export async function collectMountedTurnMessages({
|
|
|
122
125
|
waitForRender = delay,
|
|
123
126
|
renderWaitMs = DEFAULT_RENDER_WAIT_MS,
|
|
124
127
|
renderAttempts = 4,
|
|
125
|
-
doc = typeof document !==
|
|
128
|
+
doc = typeof document !== "undefined" ? document : null,
|
|
126
129
|
}) {
|
|
127
130
|
const originalTop = scrollRoot?.scrollTop;
|
|
128
131
|
const originalBehavior = scrollRoot?.style?.scrollBehavior;
|
|
129
132
|
const overlay = createProgressOverlay(doc);
|
|
130
133
|
|
|
131
134
|
if (scrollRoot && scrollRoot.style) {
|
|
132
|
-
scrollRoot.style.scrollBehavior =
|
|
135
|
+
scrollRoot.style.scrollBehavior = "auto";
|
|
133
136
|
}
|
|
134
137
|
|
|
135
138
|
const seen = new Set();
|
|
@@ -137,7 +140,7 @@ export async function collectMountedTurnMessages({
|
|
|
137
140
|
|
|
138
141
|
try {
|
|
139
142
|
// Scroll to top first to trigger un-virtualization of earlier turns
|
|
140
|
-
if (scrollRoot && typeof scrollRoot.scrollTop ===
|
|
143
|
+
if (scrollRoot && typeof scrollRoot.scrollTop === "number") {
|
|
141
144
|
scrollRoot.scrollTop = 0;
|
|
142
145
|
await waitForRender(renderWaitMs);
|
|
143
146
|
}
|
|
@@ -158,8 +161,8 @@ export async function collectMountedTurnMessages({
|
|
|
158
161
|
const turn = orderedTurns[idx];
|
|
159
162
|
updateProgressOverlay(overlay, idx + 1, totalTurns);
|
|
160
163
|
|
|
161
|
-
if (typeof turn.scrollIntoView ===
|
|
162
|
-
turn.scrollIntoView({ block:
|
|
164
|
+
if (typeof turn.scrollIntoView === "function") {
|
|
165
|
+
turn.scrollIntoView({ block: "center" });
|
|
163
166
|
}
|
|
164
167
|
|
|
165
168
|
let message = null;
|
|
@@ -181,7 +184,7 @@ export async function collectMountedTurnMessages({
|
|
|
181
184
|
removeProgressOverlay(overlay);
|
|
182
185
|
|
|
183
186
|
if (scrollRoot && scrollRoot.style) {
|
|
184
|
-
scrollRoot.style.scrollBehavior = originalBehavior ||
|
|
187
|
+
scrollRoot.style.scrollBehavior = originalBehavior || "";
|
|
185
188
|
}
|
|
186
189
|
|
|
187
190
|
if (scrollRoot && Number.isFinite(originalTop)) {
|
package/ai/chub.js
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { ChatParser } from "./base.js";
|
|
2
|
+
import { convertToMarkdown } from "../utils/html-to-markdown.js";
|
|
3
|
+
|
|
4
|
+
export class ChubParser extends ChatParser {
|
|
5
|
+
name = "Chub";
|
|
6
|
+
|
|
7
|
+
isAvailable(url) {
|
|
8
|
+
return url.includes("chub.ai") || url.includes("characterhub.org");
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
async parse() {
|
|
12
|
+
// Extract Character Name
|
|
13
|
+
const charLink = document.querySelector('a[href*="/characters/"]');
|
|
14
|
+
const character = charLink
|
|
15
|
+
? charLink.textContent.trim().replace(/\s+/g, " ")
|
|
16
|
+
: "";
|
|
17
|
+
|
|
18
|
+
// Extract User Name
|
|
19
|
+
const userLink = document.querySelector('a[href*="/users/"]');
|
|
20
|
+
let user = "";
|
|
21
|
+
if (userLink) {
|
|
22
|
+
const href = userLink.getAttribute("href") || "";
|
|
23
|
+
const match = href.match(/\/users\/([^/?#]+)/);
|
|
24
|
+
user = match ? match[1] : userLink.textContent.trim();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Extract Title
|
|
28
|
+
let title = document.title
|
|
29
|
+
? document.title.trim().replace(/\s+/g, " ")
|
|
30
|
+
: "";
|
|
31
|
+
if (
|
|
32
|
+
!title ||
|
|
33
|
+
title.toLowerCase() === "chub" ||
|
|
34
|
+
title.toLowerCase() === "chub ai"
|
|
35
|
+
) {
|
|
36
|
+
title = character ? `Chat with ${character}` : "Chub AI Conversation";
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Select message items
|
|
40
|
+
let items = Array.from(
|
|
41
|
+
document.querySelectorAll("li.ant-list-item.message-full"),
|
|
42
|
+
);
|
|
43
|
+
if (items.length === 0) {
|
|
44
|
+
const allMsg = Array.from(document.querySelectorAll(".message-full"));
|
|
45
|
+
items = allMsg.filter(
|
|
46
|
+
(el) => !allMsg.some((parent) => parent !== el && parent.contains(el)),
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const messages = [];
|
|
51
|
+
for (const item of items) {
|
|
52
|
+
const isUser =
|
|
53
|
+
!!item.querySelector('a[href*="/users/"]') ||
|
|
54
|
+
!item.querySelector('a[href*="/characters/"]');
|
|
55
|
+
const role = isUser ? "User" : "Chub";
|
|
56
|
+
|
|
57
|
+
const contentEl =
|
|
58
|
+
item.querySelector(".msg-mkdn-container") ||
|
|
59
|
+
item.querySelector(".ant-list-item-meta-description") ||
|
|
60
|
+
item;
|
|
61
|
+
const clone = contentEl.cloneNode(true);
|
|
62
|
+
|
|
63
|
+
// Clean up UI controls and noise elements
|
|
64
|
+
clone
|
|
65
|
+
.querySelectorAll(
|
|
66
|
+
"button, .message-control-buttons, .message-title, .ant-image-mask, .anticon",
|
|
67
|
+
)
|
|
68
|
+
.forEach((el) => el.remove());
|
|
69
|
+
|
|
70
|
+
const markdown = convertToMarkdown(clone);
|
|
71
|
+
if (markdown.trim()) {
|
|
72
|
+
messages.push({ role, content: markdown.trim() });
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const currentUrl =
|
|
77
|
+
typeof window !== "undefined" && window.location
|
|
78
|
+
? window.location.href || ""
|
|
79
|
+
: "";
|
|
80
|
+
const metadata = {
|
|
81
|
+
Source: "Chub",
|
|
82
|
+
Date: new Date().toLocaleString(),
|
|
83
|
+
Link: currentUrl,
|
|
84
|
+
};
|
|
85
|
+
if (character) metadata.Character = character;
|
|
86
|
+
if (user) metadata.User = user;
|
|
87
|
+
|
|
88
|
+
return { title, messages, url: currentUrl, metadata };
|
|
89
|
+
}
|
|
90
|
+
}
|