decant-core 1.0.0
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/LICENSE +661 -0
- package/README.md +116 -0
- package/ai/base.js +26 -0
- package/ai/chatgpt.js +973 -0
- package/ai/chatgpt_helper.js +137 -0
- package/ai/chatgpt_scroll_collector.js +193 -0
- package/ai/claude.js +410 -0
- package/ai/claude_react_reader.js +59 -0
- package/ai/copilot.js +413 -0
- package/ai/deepseek.js +155 -0
- package/ai/gemini.js +856 -0
- package/ai/gemini_cloud_assist.js +91 -0
- package/ai/google_ai_studio.js +116 -0
- package/ai/google_search_ai.js +114 -0
- package/ai/index.js +34 -0
- package/ai/lumo.js +101 -0
- package/ai/meta.js +110 -0
- package/ai/mistral.js +57 -0
- package/ai/notebooklm.js +140 -0
- package/ai/perplexity.js +85 -0
- package/ai/qwen.js +101 -0
- package/ai/z_ai.js +101 -0
- package/detection/detect-platform.js +100 -0
- package/detection/domains.js +45 -0
- package/lib/turndown.js +803 -0
- package/package.json +56 -0
- package/utils/html-to-markdown.js +374 -0
package/ai/copilot.js
ADDED
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
import { ChatParser } from './base.js';
|
|
2
|
+
import { convertToMarkdown } from '../utils/html-to-markdown.js';
|
|
3
|
+
|
|
4
|
+
export class CopilotParser extends ChatParser {
|
|
5
|
+
name = 'Copilot';
|
|
6
|
+
isAvailable(url) {
|
|
7
|
+
return (
|
|
8
|
+
url.includes('copilot.microsoft.com') ||
|
|
9
|
+
url.includes('copilot.com') ||
|
|
10
|
+
url.includes('copilot.cloud.microsoft') ||
|
|
11
|
+
url.includes('m365.cloud.microsoft') ||
|
|
12
|
+
url.includes('m365.microsoft.com') ||
|
|
13
|
+
url.includes('bing.com/chat') ||
|
|
14
|
+
url.includes('bing.com/copilot') ||
|
|
15
|
+
url.includes('bing.com/copilotsearch') ||
|
|
16
|
+
url.includes('edgeservices.bing.com')
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async parse() {
|
|
21
|
+
let title = 'Copilot Conversation';
|
|
22
|
+
if (document.title) {
|
|
23
|
+
const cleanTitle = document.title
|
|
24
|
+
.replace(/^Microsoft Copilot:\s*/i, '')
|
|
25
|
+
.replace(/\s*-\s*Microsoft Copilot$/i, '')
|
|
26
|
+
.replace(/^Copilot:\s*/i, '')
|
|
27
|
+
.replace(/\s*-\s*Copilot$/i, '')
|
|
28
|
+
.replace(/Your AI companion/i, '')
|
|
29
|
+
.trim();
|
|
30
|
+
if (
|
|
31
|
+
cleanTitle &&
|
|
32
|
+
cleanTitle.toLowerCase() !== 'microsoft copilot' &&
|
|
33
|
+
cleanTitle.toLowerCase() !== 'copilot'
|
|
34
|
+
) {
|
|
35
|
+
title = cleanTitle;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const messages = [];
|
|
40
|
+
|
|
41
|
+
// Helper to process code blocks and links before HTML-to-markdown conversion
|
|
42
|
+
const processAiElement = (element) => {
|
|
43
|
+
const clone = element.cloneNode(true);
|
|
44
|
+
|
|
45
|
+
// Remove UI noise elements
|
|
46
|
+
const noiseSelectors = [
|
|
47
|
+
'[data-testid="message-item-reactions"]',
|
|
48
|
+
'[data-testid="user-message-reactions"]',
|
|
49
|
+
'[data-testid="copy-ai-message-button"]',
|
|
50
|
+
'[data-testid="copy-user-message-button"]',
|
|
51
|
+
'[data-testid="CopyButtonContainerTestId"]',
|
|
52
|
+
'[data-testid="CopyButtonTestId"]',
|
|
53
|
+
'[data-testid="FeedbackContainerTestId"]',
|
|
54
|
+
'[data-testid="feedback-button-testid"]',
|
|
55
|
+
'[data-testid="overflow-menu-button"]',
|
|
56
|
+
'[data-testid="share-message-button"]',
|
|
57
|
+
'[data-testid="message-thumbs-up-button"]',
|
|
58
|
+
'[data-testid="message-thumbs-down-button"]',
|
|
59
|
+
'[data-testid="message-read-aloud-button"]',
|
|
60
|
+
'[data-testid="regenerate-message-button-popover"]',
|
|
61
|
+
'[data-testid="chat-suggestion"]',
|
|
62
|
+
'[data-testid="loading-message"]',
|
|
63
|
+
'.fai-CopilotMessage__actions',
|
|
64
|
+
'.fai-SuggestionList',
|
|
65
|
+
'.fai-UserMessage__accessibleHeading',
|
|
66
|
+
'.fai-CopilotMessage__accessibleHeading',
|
|
67
|
+
'[class*="suggestedReplies"]',
|
|
68
|
+
'[class*="workingCard"]',
|
|
69
|
+
'[class*="WorkingCard"]',
|
|
70
|
+
'[class*="disclaimerText"]',
|
|
71
|
+
'cib-action-bar',
|
|
72
|
+
'cib-feedback-buttons',
|
|
73
|
+
'cib-message-actions',
|
|
74
|
+
'.sr-only',
|
|
75
|
+
];
|
|
76
|
+
noiseSelectors.forEach((sel) => {
|
|
77
|
+
clone.querySelectorAll(sel).forEach((el) => el.remove());
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
// Transform data-url span buttons into standard anchor tags
|
|
81
|
+
clone.querySelectorAll('span[data-url]').forEach((span) => {
|
|
82
|
+
const url = span.getAttribute('data-url');
|
|
83
|
+
if (url && !url.startsWith('ca://')) {
|
|
84
|
+
const a = document.createElement('a');
|
|
85
|
+
a.href = url;
|
|
86
|
+
a.textContent = span.textContent;
|
|
87
|
+
span.replaceWith(a);
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// Standardize code blocks with language labels
|
|
92
|
+
clone.querySelectorAll('div.rounded-xl, div[class*="code-block"]').forEach((block) => {
|
|
93
|
+
const langEl = block.querySelector('span.capitalize, [class*="language-"]');
|
|
94
|
+
const codeEl = block.querySelector('code, pre');
|
|
95
|
+
if (codeEl) {
|
|
96
|
+
const lang = langEl ? langEl.innerText.trim().toLowerCase() : '';
|
|
97
|
+
const codeText = codeEl.innerText || codeEl.textContent;
|
|
98
|
+
const newPre = document.createElement('pre');
|
|
99
|
+
const newCode = document.createElement('code');
|
|
100
|
+
if (lang) {
|
|
101
|
+
newCode.className = `language-${lang}`;
|
|
102
|
+
}
|
|
103
|
+
newCode.textContent = codeText;
|
|
104
|
+
newPre.appendChild(newCode);
|
|
105
|
+
block.replaceWith(newPre);
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
return clone.innerHTML;
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
// Multi-tier DOM extraction strategy
|
|
113
|
+
|
|
114
|
+
// Tier 1: Modern M365 Copilot / Bebop layout & data-content markers
|
|
115
|
+
let turnElements = Array.from(
|
|
116
|
+
document.querySelectorAll(
|
|
117
|
+
'[data-testid="chatQuestion"], [data-testid="copilot-message-div"], [data-content="user-message"], [data-content="ai-message"], [data-content="assistant-message"], [data-message-author-role="user"], [data-message-author-role="assistant"], [data-testid="chat-turn-user"], [data-testid="chat-turn-bot"], [data-testid="chat-turn-assistant"], [data-testid="user-turn"], [data-testid="copilot-turn"], .fai-UserMessage, .fai-CopilotMessage',
|
|
118
|
+
),
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
// Filter out nested matches
|
|
122
|
+
if (turnElements.length) {
|
|
123
|
+
turnElements = turnElements.filter(
|
|
124
|
+
(el) => !turnElements.some((other) => other !== el && other.contains(el)),
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Tier 2: Tailwind group classes if direct markers are absent
|
|
129
|
+
if (!turnElements.length) {
|
|
130
|
+
turnElements = Array.from(
|
|
131
|
+
document.querySelectorAll(
|
|
132
|
+
'[class*="group/user-message"], [class*="group/ai-message"], [class*="group/assistant-message"]',
|
|
133
|
+
),
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Tier 3: testid attributes
|
|
138
|
+
if (!turnElements.length) {
|
|
139
|
+
turnElements = Array.from(
|
|
140
|
+
document.querySelectorAll(
|
|
141
|
+
'[data-testid="user-message"], [data-testid="ai-message"], [data-testid="assistant-message"]',
|
|
142
|
+
),
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (turnElements.length) {
|
|
147
|
+
turnElements.forEach((node) => {
|
|
148
|
+
const dataContent = node.getAttribute('data-content') || '';
|
|
149
|
+
const dataAuthor = (node.getAttribute('data-message-author-role') || '').toLowerCase();
|
|
150
|
+
const className = typeof node.className === 'string' ? node.className : '';
|
|
151
|
+
const testId = node.getAttribute('data-testid') || '';
|
|
152
|
+
|
|
153
|
+
const isUser =
|
|
154
|
+
dataAuthor === 'user' ||
|
|
155
|
+
testId === 'chatQuestion' ||
|
|
156
|
+
testId === 'chat-turn-user' ||
|
|
157
|
+
testId === 'user-turn' ||
|
|
158
|
+
className.includes('UserMessage') ||
|
|
159
|
+
dataContent === 'user-message' ||
|
|
160
|
+
className.includes('user-message') ||
|
|
161
|
+
testId === 'user-message';
|
|
162
|
+
|
|
163
|
+
if (isUser) {
|
|
164
|
+
const targetNode =
|
|
165
|
+
node.querySelector(
|
|
166
|
+
'[data-testid="chatOutput"], [data-testid="user-message-content"], .fai-UserMessage__message, [data-content="user-message"]',
|
|
167
|
+
) || node;
|
|
168
|
+
const clone = targetNode.cloneNode(true);
|
|
169
|
+
clone
|
|
170
|
+
.querySelectorAll('.fai-UserMessage__accessibleHeading, .sr-only, button')
|
|
171
|
+
.forEach((el) => el.remove());
|
|
172
|
+
const text = clone.innerText || clone.textContent;
|
|
173
|
+
if (text && text.trim()) {
|
|
174
|
+
messages.push({ role: 'User', content: text.trim() });
|
|
175
|
+
}
|
|
176
|
+
} else {
|
|
177
|
+
const targetNode =
|
|
178
|
+
node.querySelector('[data-testid="markdown-reply"]') ||
|
|
179
|
+
node.querySelector('[data-testid="ai-message-body"]') ||
|
|
180
|
+
node.querySelector('[data-testid="copilot-message-content"]') ||
|
|
181
|
+
node.querySelector('.fai-CopilotMessage__content') ||
|
|
182
|
+
node.querySelector('[class*="group/ai-message-item"]') ||
|
|
183
|
+
node;
|
|
184
|
+
const html = processAiElement(targetNode);
|
|
185
|
+
const markdown = convertToMarkdown(html);
|
|
186
|
+
if (markdown && markdown.trim()) {
|
|
187
|
+
messages.push({ role: 'Copilot', content: markdown });
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Helper to extract messages from Shadow DOM cib-serp components (Classic Bing Chat / Copilot)
|
|
194
|
+
const extractFromCibSerp = (rootDoc = document) => {
|
|
195
|
+
try {
|
|
196
|
+
const cibSerp = rootDoc.querySelector('cib-serp');
|
|
197
|
+
if (!cibSerp || !cibSerp.shadowRoot) return [];
|
|
198
|
+
const cibConversation = cibSerp.shadowRoot.querySelector('cib-conversation');
|
|
199
|
+
if (!cibConversation || !cibConversation.shadowRoot) return [];
|
|
200
|
+
const cibTurns = cibConversation.shadowRoot.querySelectorAll('cib-chat-turn');
|
|
201
|
+
const results = [];
|
|
202
|
+
|
|
203
|
+
cibTurns.forEach((turn) => {
|
|
204
|
+
const turnRoot = turn.shadowRoot || turn;
|
|
205
|
+
const msgGroups = turnRoot.querySelectorAll('cib-message-group');
|
|
206
|
+
msgGroups.forEach((group) => {
|
|
207
|
+
const source = (group.getAttribute('source') || '').toLowerCase();
|
|
208
|
+
const role = source === 'user' ? 'User' : 'Copilot';
|
|
209
|
+
const groupRoot = group.shadowRoot || group;
|
|
210
|
+
const cibMessages = groupRoot.querySelectorAll('cib-message');
|
|
211
|
+
|
|
212
|
+
cibMessages.forEach((msg) => {
|
|
213
|
+
const msgRoot = msg.shadowRoot || msg;
|
|
214
|
+
if (role === 'User') {
|
|
215
|
+
const text = msgRoot.textContent?.trim();
|
|
216
|
+
if (text) results.push({ role: 'User', content: text });
|
|
217
|
+
} else {
|
|
218
|
+
const shared = msgRoot.querySelector('cib-shared') || msgRoot;
|
|
219
|
+
const html = processAiElement(shared);
|
|
220
|
+
const md = convertToMarkdown(html);
|
|
221
|
+
if (md && md.trim()) results.push({ role: 'Copilot', content: md.trim() });
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
return results;
|
|
227
|
+
} catch (err) {
|
|
228
|
+
console.warn('[AI Exporter] Shadow DOM cib-serp extraction error:', err);
|
|
229
|
+
return [];
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
// Tier 4: Shadow DOM cib-serp components
|
|
234
|
+
if (!messages.length) {
|
|
235
|
+
const cibMessages = extractFromCibSerp(document);
|
|
236
|
+
if (cibMessages.length) {
|
|
237
|
+
messages.push(...cibMessages);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Tier 5: Web components in light DOM (cib-chat-turn / cib-message-group)
|
|
242
|
+
if (!messages.length) {
|
|
243
|
+
const cibTurns = document.querySelectorAll('cib-chat-turn');
|
|
244
|
+
if (cibTurns.length) {
|
|
245
|
+
cibTurns.forEach((turn) => {
|
|
246
|
+
const source = (turn.getAttribute('source') || '').toLowerCase();
|
|
247
|
+
const role = source === 'user' ? 'User' : 'Copilot';
|
|
248
|
+
if (role === 'User') {
|
|
249
|
+
const text = turn.innerText || turn.textContent;
|
|
250
|
+
if (text && text.trim()) {
|
|
251
|
+
messages.push({ role: 'User', content: text.trim() });
|
|
252
|
+
}
|
|
253
|
+
} else {
|
|
254
|
+
const html = processAiElement(turn);
|
|
255
|
+
const markdown = convertToMarkdown(html);
|
|
256
|
+
if (markdown && markdown.trim()) {
|
|
257
|
+
messages.push({ role: 'Copilot', content: markdown });
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Tier 6: React [data-turn-id] layout
|
|
265
|
+
if (!messages.length) {
|
|
266
|
+
const turnNodes = document.querySelectorAll('[data-turn-id]');
|
|
267
|
+
turnNodes.forEach((node) => {
|
|
268
|
+
const roleAttr = (
|
|
269
|
+
node.getAttribute('data-turn-role') ||
|
|
270
|
+
node.getAttribute('data-author') ||
|
|
271
|
+
''
|
|
272
|
+
).toLowerCase();
|
|
273
|
+
const role = roleAttr.includes('user') ? 'User' : 'Copilot';
|
|
274
|
+
|
|
275
|
+
if (role === 'User') {
|
|
276
|
+
const text = node.innerText || node.textContent;
|
|
277
|
+
if (text && text.trim()) {
|
|
278
|
+
messages.push({ role: 'User', content: text.trim() });
|
|
279
|
+
}
|
|
280
|
+
} else {
|
|
281
|
+
const html = processAiElement(node);
|
|
282
|
+
const markdown = convertToMarkdown(html);
|
|
283
|
+
if (markdown && markdown.trim()) {
|
|
284
|
+
messages.push({ role: 'Copilot', content: markdown });
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Tier 7: Universal role="article" scanner for Copilot
|
|
291
|
+
if (!messages.length) {
|
|
292
|
+
const articles = Array.from(document.querySelectorAll('[role="article"]'));
|
|
293
|
+
articles.forEach((art) => {
|
|
294
|
+
const headingText = (
|
|
295
|
+
art.querySelector('h1, h2, h3, h4, h5, h6')?.textContent || ''
|
|
296
|
+
).toLowerCase();
|
|
297
|
+
const ariaLabel = (
|
|
298
|
+
art.getAttribute('aria-label') ||
|
|
299
|
+
art.getAttribute('aria-labelledby') ||
|
|
300
|
+
''
|
|
301
|
+
).toLowerCase();
|
|
302
|
+
const className = typeof art.className === 'string' ? art.className : '';
|
|
303
|
+
|
|
304
|
+
const isUser =
|
|
305
|
+
headingText.includes('you said') ||
|
|
306
|
+
ariaLabel.includes('user-message') ||
|
|
307
|
+
ariaLabel.includes('you said') ||
|
|
308
|
+
className.includes('UserMessage') ||
|
|
309
|
+
art.matches('[data-testid="chatQuestion"], [data-testid="user-message"]');
|
|
310
|
+
|
|
311
|
+
const isCopilot =
|
|
312
|
+
headingText.includes('copilot said') ||
|
|
313
|
+
ariaLabel.includes('copilot-message') ||
|
|
314
|
+
ariaLabel.includes('copilot said') ||
|
|
315
|
+
className.includes('CopilotMessage') ||
|
|
316
|
+
art.matches('[data-testid="copilot-message-div"], [data-testid="ai-message"]');
|
|
317
|
+
|
|
318
|
+
if (isUser) {
|
|
319
|
+
const clone = art.cloneNode(true);
|
|
320
|
+
clone
|
|
321
|
+
.querySelectorAll('h1, h2, h3, h4, h5, h6, .sr-only, button')
|
|
322
|
+
.forEach((el) => el.remove());
|
|
323
|
+
const text = clone.innerText || clone.textContent;
|
|
324
|
+
if (text && text.trim()) {
|
|
325
|
+
messages.push({ role: 'User', content: text.trim() });
|
|
326
|
+
}
|
|
327
|
+
} else if (isCopilot) {
|
|
328
|
+
const targetNode =
|
|
329
|
+
art.querySelector('[data-testid="markdown-reply"]') ||
|
|
330
|
+
art.querySelector('.fai-CopilotMessage__content') ||
|
|
331
|
+
art.querySelector('[data-testid="ai-message-body"]') ||
|
|
332
|
+
art;
|
|
333
|
+
const html = processAiElement(targetNode);
|
|
334
|
+
const markdown = convertToMarkdown(html);
|
|
335
|
+
if (markdown && markdown.trim()) {
|
|
336
|
+
messages.push({ role: 'Copilot', content: markdown });
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// Tier 8: Check embedded iframe documents (e.g. Edge Sidebar panel iframe)
|
|
343
|
+
if (!messages.length) {
|
|
344
|
+
const iframes = Array.from(document.querySelectorAll('iframe'));
|
|
345
|
+
for (const iframe of iframes) {
|
|
346
|
+
try {
|
|
347
|
+
const iframeDoc = iframe.contentDocument || iframe.contentWindow?.document;
|
|
348
|
+
if (iframeDoc) {
|
|
349
|
+
const iframeCib = extractFromCibSerp(iframeDoc);
|
|
350
|
+
if (iframeCib.length) {
|
|
351
|
+
messages.push(...iframeCib);
|
|
352
|
+
break;
|
|
353
|
+
}
|
|
354
|
+
const iframeTurns = Array.from(
|
|
355
|
+
iframeDoc.querySelectorAll(
|
|
356
|
+
'[data-testid="chatQuestion"], [data-testid="copilot-message-div"], [data-content="user-message"], [data-content="ai-message"]',
|
|
357
|
+
),
|
|
358
|
+
);
|
|
359
|
+
if (iframeTurns.length) {
|
|
360
|
+
iframeTurns.forEach((node) => {
|
|
361
|
+
const dataContent = node.getAttribute('data-content') || '';
|
|
362
|
+
const testId = node.getAttribute('data-testid') || '';
|
|
363
|
+
const isUser =
|
|
364
|
+
testId === 'chatQuestion' ||
|
|
365
|
+
dataContent === 'user-message' ||
|
|
366
|
+
testId === 'user-message';
|
|
367
|
+
if (isUser) {
|
|
368
|
+
const targetNode =
|
|
369
|
+
node.querySelector(
|
|
370
|
+
'[data-testid="chatOutput"], .fai-UserMessage__message, [data-content="user-message"]',
|
|
371
|
+
) || node;
|
|
372
|
+
const text = targetNode.innerText || targetNode.textContent;
|
|
373
|
+
if (text && text.trim()) messages.push({ role: 'User', content: text.trim() });
|
|
374
|
+
} else {
|
|
375
|
+
const targetNode =
|
|
376
|
+
node.querySelector('[data-testid="markdown-reply"]') ||
|
|
377
|
+
node.querySelector('[data-testid="ai-message-body"]') ||
|
|
378
|
+
node;
|
|
379
|
+
const html = processAiElement(targetNode);
|
|
380
|
+
const markdown = convertToMarkdown(html);
|
|
381
|
+
if (markdown && markdown.trim()) {
|
|
382
|
+
messages.push({ role: 'Copilot', content: markdown });
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
});
|
|
386
|
+
if (messages.length) break;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
} catch {
|
|
390
|
+
// Ignore cross-origin iframe security restrictions
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// Fallback title generation if default title is generic
|
|
396
|
+
if (title === 'Copilot Conversation' && messages.length > 0 && messages[0].role === 'User') {
|
|
397
|
+
const firstPrompt = messages[0].content.split('\n')[0].trim();
|
|
398
|
+
if (firstPrompt) {
|
|
399
|
+
title = firstPrompt.length > 40 ? `${firstPrompt.slice(0, 40)}...` : firstPrompt;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
const currentUrl =
|
|
404
|
+
typeof window !== 'undefined' && window.location ? window.location.href || '' : '';
|
|
405
|
+
const metadata = {
|
|
406
|
+
Source: 'Copilot',
|
|
407
|
+
Date: new Date().toLocaleString(),
|
|
408
|
+
Link: currentUrl,
|
|
409
|
+
};
|
|
410
|
+
|
|
411
|
+
return { title, messages, url: currentUrl, metadata };
|
|
412
|
+
}
|
|
413
|
+
}
|
package/ai/deepseek.js
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { ChatParser } from './base.js';
|
|
2
|
+
import { convertToMarkdown } from '../utils/html-to-markdown.js';
|
|
3
|
+
|
|
4
|
+
function getUserToken() {
|
|
5
|
+
try {
|
|
6
|
+
if (typeof localStorage === 'undefined') return null;
|
|
7
|
+
const raw = localStorage.getItem('userToken');
|
|
8
|
+
if (!raw) return null;
|
|
9
|
+
try {
|
|
10
|
+
const parsed = JSON.parse(raw);
|
|
11
|
+
return parsed?.value || parsed || null;
|
|
12
|
+
} catch {
|
|
13
|
+
return raw;
|
|
14
|
+
}
|
|
15
|
+
} catch {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function getConversationId() {
|
|
21
|
+
try {
|
|
22
|
+
if (typeof window === 'undefined' || !window.location) return null;
|
|
23
|
+
const path = window.location.pathname || window.location.href || '';
|
|
24
|
+
return (
|
|
25
|
+
path.match(/\/chat\/s\/([a-f0-9-]+)/)?.[1] ??
|
|
26
|
+
path.match(/\/a\/chat\/s\/([a-f0-9-]+)/)?.[1] ??
|
|
27
|
+
null
|
|
28
|
+
);
|
|
29
|
+
} catch {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function fetchDeepSeekConversation(sessionId, token) {
|
|
35
|
+
const url = `https://chat.deepseek.com/api/v0/chat/history_messages?chat_session_id=${sessionId}&cache_version=0`;
|
|
36
|
+
const response = await fetch(url, {
|
|
37
|
+
method: 'GET',
|
|
38
|
+
credentials: 'include',
|
|
39
|
+
headers: {
|
|
40
|
+
'Content-Type': 'application/json',
|
|
41
|
+
Authorization: `Bearer ${token}`,
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
if (!response.ok) {
|
|
46
|
+
throw new Error(`DeepSeek API request failed: ${response.status}`);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const json = await response.json();
|
|
50
|
+
const bizData = json?.data?.biz_data;
|
|
51
|
+
const chatMessages = bizData?.chat_messages || [];
|
|
52
|
+
const currentMsgId = bizData?.chat_session?.current_message_id;
|
|
53
|
+
|
|
54
|
+
if (!chatMessages.length || currentMsgId == null) {
|
|
55
|
+
return [];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const messageMap = new Map();
|
|
59
|
+
chatMessages.forEach((msg) => {
|
|
60
|
+
if (msg && msg.message_id != null) {
|
|
61
|
+
messageMap.set(msg.message_id, msg);
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
const branch = [];
|
|
66
|
+
let currentId = currentMsgId;
|
|
67
|
+
while (currentId != null && messageMap.has(currentId)) {
|
|
68
|
+
const msgNode = messageMap.get(currentId);
|
|
69
|
+
branch.push(msgNode);
|
|
70
|
+
currentId = msgNode.parent_id ?? null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
branch.reverse();
|
|
74
|
+
|
|
75
|
+
return branch
|
|
76
|
+
.map((msgNode) => {
|
|
77
|
+
const isUser = msgNode.role === 'USER' || msgNode.role === 'user';
|
|
78
|
+
const role = isUser ? 'User' : 'DeepSeek';
|
|
79
|
+
const content = msgNode.content || msgNode.text || '';
|
|
80
|
+
return { role, content: content.trim() };
|
|
81
|
+
})
|
|
82
|
+
.filter((msg) => msg.content.length > 0);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export class DeepSeekParser extends ChatParser {
|
|
86
|
+
name = 'DeepSeek';
|
|
87
|
+
isAvailable(url) {
|
|
88
|
+
return url.includes('chat.deepseek.com');
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async parse() {
|
|
92
|
+
const title = document.title || 'DeepSeek Chat';
|
|
93
|
+
|
|
94
|
+
const currentUrl =
|
|
95
|
+
typeof window !== 'undefined' && window.location ? window.location.href || '' : '';
|
|
96
|
+
const metadata = {
|
|
97
|
+
Source: 'DeepSeek',
|
|
98
|
+
Date: new Date().toLocaleString(),
|
|
99
|
+
Link: currentUrl,
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
// Primary: API Extraction
|
|
103
|
+
try {
|
|
104
|
+
const token = getUserToken();
|
|
105
|
+
const sessionId = getConversationId();
|
|
106
|
+
if (token && sessionId) {
|
|
107
|
+
const apiMessages = await fetchDeepSeekConversation(sessionId, token);
|
|
108
|
+
if (apiMessages && apiMessages.length > 0) {
|
|
109
|
+
return { title, messages: apiMessages, url: currentUrl, metadata };
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
} catch (e) {
|
|
113
|
+
console.warn('[AI Exporter] DeepSeek API fetch failed, falling back to DOM:', e);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Secondary: DOM Fallback
|
|
117
|
+
const messages = [];
|
|
118
|
+
|
|
119
|
+
// Selectors from research
|
|
120
|
+
const userSelector = '.fbb737a4';
|
|
121
|
+
const assistantSelector = '.ds-markdown';
|
|
122
|
+
|
|
123
|
+
// We'll traverse the DOM to find these in order
|
|
124
|
+
const allElements = document.querySelectorAll(`${userSelector}, ${assistantSelector}`);
|
|
125
|
+
|
|
126
|
+
allElements.forEach((el) => {
|
|
127
|
+
let role = 'Unknown';
|
|
128
|
+
if (el.matches(userSelector)) {
|
|
129
|
+
role = 'User';
|
|
130
|
+
} else if (el.matches(assistantSelector)) {
|
|
131
|
+
role = 'DeepSeek';
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const text = convertToMarkdown(el);
|
|
135
|
+
if (text.trim()) {
|
|
136
|
+
messages.push({ role, content: text.trim() });
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
// Fallback if the specific classes fail (e.g. class name rotation)
|
|
141
|
+
if (messages.length === 0) {
|
|
142
|
+
const messageRows = document.querySelectorAll('.ds-message-row, .message-row');
|
|
143
|
+
messageRows.forEach((row) => {
|
|
144
|
+
const isUser = row.classList.contains('ds-user-message');
|
|
145
|
+
const role = isUser ? 'User' : 'DeepSeek';
|
|
146
|
+
const text = convertToMarkdown(row);
|
|
147
|
+
if (text.trim()) {
|
|
148
|
+
messages.push({ role, content: text.trim() });
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return { title, messages, url: currentUrl, metadata };
|
|
154
|
+
}
|
|
155
|
+
}
|