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/qwen.js
CHANGED
|
@@ -1,24 +1,24 @@
|
|
|
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
|
|
|
4
4
|
export class QwenParser extends ChatParser {
|
|
5
|
-
name =
|
|
5
|
+
name = "Qwen";
|
|
6
6
|
isAvailable(url) {
|
|
7
|
-
return url.includes(
|
|
7
|
+
return url.includes("qwen.ai");
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
async parse() {
|
|
11
11
|
// Try to get the actual chat title from multiple possible selectors
|
|
12
12
|
const titleSelectors = [
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
".chat-item-drag-link-content-tip-text",
|
|
14
|
+
".ant-tooltip-inner",
|
|
15
15
|
'input[placeholder*="title"]',
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
".chat-title",
|
|
17
|
+
"h1",
|
|
18
|
+
"title",
|
|
19
19
|
];
|
|
20
20
|
|
|
21
|
-
let title =
|
|
21
|
+
let title = "Qwen Chat";
|
|
22
22
|
for (const selector of titleSelectors) {
|
|
23
23
|
const element = document.querySelector(selector);
|
|
24
24
|
if (element) {
|
|
@@ -33,27 +33,29 @@ export class QwenParser extends ChatParser {
|
|
|
33
33
|
const messages = [];
|
|
34
34
|
|
|
35
35
|
// chat.qwen.ai uses specific class names
|
|
36
|
-
const chatMessages = document.querySelectorAll(
|
|
36
|
+
const chatMessages = document.querySelectorAll(".qwen-chat-message");
|
|
37
37
|
|
|
38
38
|
chatMessages.forEach((message) => {
|
|
39
|
-
const isUser = message.classList.contains(
|
|
40
|
-
const role = isUser ?
|
|
39
|
+
const isUser = message.classList.contains("qwen-chat-message-user");
|
|
40
|
+
const role = isUser ? "User" : "Qwen";
|
|
41
41
|
|
|
42
|
-
let content =
|
|
42
|
+
let content = "";
|
|
43
43
|
let attachments = [];
|
|
44
44
|
|
|
45
45
|
if (isUser) {
|
|
46
46
|
// Extract attachments first
|
|
47
|
-
const fileItems = message.querySelectorAll(
|
|
47
|
+
const fileItems = message.querySelectorAll(
|
|
48
|
+
".index-module__file-message-document___OjWnc",
|
|
49
|
+
);
|
|
48
50
|
fileItems.forEach((item) => {
|
|
49
|
-
const fileNameEl = item.querySelector(
|
|
50
|
-
const fileExtEl = item.querySelector(
|
|
51
|
-
const fileSizeEl = item.querySelector(
|
|
51
|
+
const fileNameEl = item.querySelector(".fileitem-file-name-text");
|
|
52
|
+
const fileExtEl = item.querySelector(".fileitem-file-name-ext");
|
|
53
|
+
const fileSizeEl = item.querySelector(".fileitem-file-size span");
|
|
52
54
|
|
|
53
55
|
if (fileNameEl && fileExtEl) {
|
|
54
56
|
const fileName = fileNameEl.textContent.trim();
|
|
55
57
|
const fileExt = fileExtEl.textContent.trim();
|
|
56
|
-
const fileSize = fileSizeEl ? fileSizeEl.textContent.trim() :
|
|
58
|
+
const fileSize = fileSizeEl ? fileSizeEl.textContent.trim() : "";
|
|
57
59
|
|
|
58
60
|
attachments.push({
|
|
59
61
|
name: fileName + fileExt,
|
|
@@ -63,13 +65,13 @@ export class QwenParser extends ChatParser {
|
|
|
63
65
|
});
|
|
64
66
|
|
|
65
67
|
// User messages are in .user-message-content
|
|
66
|
-
const userContent = message.querySelector(
|
|
68
|
+
const userContent = message.querySelector(".user-message-content");
|
|
67
69
|
if (userContent) {
|
|
68
70
|
content = convertToMarkdown(userContent);
|
|
69
71
|
}
|
|
70
72
|
} else {
|
|
71
73
|
// Assistant messages are in .qwen-markdown elements
|
|
72
|
-
const markdownContent = message.querySelector(
|
|
74
|
+
const markdownContent = message.querySelector(".qwen-markdown");
|
|
73
75
|
if (markdownContent) {
|
|
74
76
|
content = convertToMarkdown(markdownContent);
|
|
75
77
|
}
|
|
@@ -79,8 +81,8 @@ export class QwenParser extends ChatParser {
|
|
|
79
81
|
if (attachments.length > 0) {
|
|
80
82
|
const attachmentList = attachments
|
|
81
83
|
.map((att) => `- **${att.name}** (${att.size})`)
|
|
82
|
-
.join(
|
|
83
|
-
content = content +
|
|
84
|
+
.join("\n");
|
|
85
|
+
content = content + "\n\n**Attachments:**\n" + attachmentList;
|
|
84
86
|
}
|
|
85
87
|
|
|
86
88
|
if (content && content.trim()) {
|
|
@@ -89,9 +91,11 @@ export class QwenParser extends ChatParser {
|
|
|
89
91
|
});
|
|
90
92
|
|
|
91
93
|
const currentUrl =
|
|
92
|
-
typeof window !==
|
|
94
|
+
typeof window !== "undefined" && window.location
|
|
95
|
+
? window.location.href || ""
|
|
96
|
+
: "";
|
|
93
97
|
const metadata = {
|
|
94
|
-
Source:
|
|
98
|
+
Source: "Qwen",
|
|
95
99
|
Date: new Date().toLocaleString(),
|
|
96
100
|
Link: currentUrl,
|
|
97
101
|
};
|
package/ai/z_ai.js
CHANGED
|
@@ -1,69 +1,73 @@
|
|
|
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
|
|
|
4
4
|
export class ZAiParser extends ChatParser {
|
|
5
|
-
name =
|
|
5
|
+
name = "Z.ai";
|
|
6
6
|
isAvailable(url) {
|
|
7
|
-
return url.includes(
|
|
7
|
+
return url.includes("chat.z.ai");
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
async parse() {
|
|
11
|
-
let title =
|
|
12
|
-
const titleEl = document.querySelector(
|
|
11
|
+
let title = "";
|
|
12
|
+
const titleEl = document.querySelector("title");
|
|
13
13
|
if (titleEl) {
|
|
14
|
-
title = titleEl.textContent.trim().replace(/\s+/g,
|
|
14
|
+
title = titleEl.textContent.trim().replace(/\s+/g, " ");
|
|
15
15
|
}
|
|
16
16
|
if (!title && document.title) {
|
|
17
|
-
title = document.title.trim().replace(/\s+/g,
|
|
17
|
+
title = document.title.trim().replace(/\s+/g, " ");
|
|
18
18
|
}
|
|
19
|
-
title = title ||
|
|
19
|
+
title = title || "Z.ai Chat";
|
|
20
20
|
const messages = [];
|
|
21
21
|
|
|
22
22
|
// Selectors for z.ai messages
|
|
23
|
-
const userSelector =
|
|
24
|
-
const assistantSelector =
|
|
23
|
+
const userSelector = ".chat-user";
|
|
24
|
+
const assistantSelector = ".chat-assistant";
|
|
25
25
|
|
|
26
26
|
// We'll traverse the DOM to find these in order
|
|
27
|
-
const allElements = document.querySelectorAll(
|
|
27
|
+
const allElements = document.querySelectorAll(
|
|
28
|
+
`${userSelector}, ${assistantSelector}`,
|
|
29
|
+
);
|
|
28
30
|
|
|
29
31
|
allElements.forEach((el) => {
|
|
30
|
-
let role =
|
|
32
|
+
let role = "Unknown";
|
|
31
33
|
let contentEl = null;
|
|
32
34
|
|
|
33
35
|
if (el.matches(userSelector)) {
|
|
34
|
-
role =
|
|
36
|
+
role = "User";
|
|
35
37
|
// Select user message text block (excluding edit/copy buttons)
|
|
36
|
-
contentEl = el.querySelector(
|
|
38
|
+
contentEl = el.querySelector("div.relative.overflow-hidden") || el;
|
|
37
39
|
} else if (el.matches(assistantSelector)) {
|
|
38
|
-
role =
|
|
40
|
+
role = "Z.ai";
|
|
39
41
|
// Select assistant message content wrapper (excluding copy/regenerate buttons)
|
|
40
42
|
const rawContentEl =
|
|
41
|
-
el.querySelector(
|
|
42
|
-
el.querySelector(
|
|
43
|
+
el.querySelector("#response-content-container") ||
|
|
44
|
+
el.querySelector(".markdown-prose") ||
|
|
43
45
|
el;
|
|
44
46
|
const contentElClone = rawContentEl.cloneNode(true);
|
|
45
47
|
|
|
46
48
|
// Preprocess CodeMirror 6 code blocks into standard HTML <pre><code> structures
|
|
47
|
-
contentElClone.querySelectorAll(
|
|
49
|
+
contentElClone.querySelectorAll(".cm-editor").forEach((cmEditor) => {
|
|
48
50
|
// Detect language from class names of the parent language container
|
|
49
51
|
const languageWrapper = cmEditor.closest('[class*="language-"]');
|
|
50
|
-
let language =
|
|
52
|
+
let language = "";
|
|
51
53
|
if (languageWrapper) {
|
|
52
54
|
const classList = Array.from(languageWrapper.classList);
|
|
53
|
-
const langClass = classList.find((cls) =>
|
|
55
|
+
const langClass = classList.find((cls) =>
|
|
56
|
+
cls.startsWith("language-"),
|
|
57
|
+
);
|
|
54
58
|
if (langClass) {
|
|
55
|
-
language = langClass.replace(
|
|
59
|
+
language = langClass.replace("language-", "");
|
|
56
60
|
}
|
|
57
61
|
}
|
|
58
62
|
|
|
59
63
|
// Extract the lines from CodeMirror editor view
|
|
60
|
-
const lines = Array.from(cmEditor.querySelectorAll(
|
|
61
|
-
const codeText = lines.map((line) => line.textContent).join(
|
|
64
|
+
const lines = Array.from(cmEditor.querySelectorAll(".cm-line"));
|
|
65
|
+
const codeText = lines.map((line) => line.textContent).join("\n");
|
|
62
66
|
|
|
63
67
|
// Create new pre and code tags using the clone's owner document context
|
|
64
68
|
const ownerDoc = cmEditor.ownerDocument || document;
|
|
65
|
-
const pre = ownerDoc.createElement(
|
|
66
|
-
const code = ownerDoc.createElement(
|
|
69
|
+
const pre = ownerDoc.createElement("pre");
|
|
70
|
+
const code = ownerDoc.createElement("code");
|
|
67
71
|
if (language) {
|
|
68
72
|
code.className = `language-${language}`;
|
|
69
73
|
}
|
|
@@ -89,9 +93,11 @@ export class ZAiParser extends ChatParser {
|
|
|
89
93
|
});
|
|
90
94
|
|
|
91
95
|
const currentUrl =
|
|
92
|
-
typeof window !==
|
|
96
|
+
typeof window !== "undefined" && window.location
|
|
97
|
+
? window.location.href || ""
|
|
98
|
+
: "";
|
|
93
99
|
const metadata = {
|
|
94
|
-
Source:
|
|
100
|
+
Source: "Z.ai",
|
|
95
101
|
Date: new Date().toLocaleString(),
|
|
96
102
|
Link: currentUrl,
|
|
97
103
|
};
|
|
@@ -1,19 +1,21 @@
|
|
|
1
|
-
import { AI_CHAT_DOMAINS, URL_PATTERNS } from
|
|
2
|
-
import { ChatGPTParser } from
|
|
3
|
-
import { ClaudeParser } from
|
|
4
|
-
import { GeminiParser } from
|
|
5
|
-
import { CopilotParser } from
|
|
6
|
-
import { DeepSeekParser } from
|
|
7
|
-
import { MetaParser } from
|
|
8
|
-
import { MistralParser } from
|
|
9
|
-
import { PerplexityParser } from
|
|
10
|
-
import { QwenParser } from
|
|
11
|
-
import { LumoParser } from
|
|
12
|
-
import { ZAiParser } from
|
|
13
|
-
import { GoogleAIStudioParser } from
|
|
14
|
-
import { NotebookLMParser } from
|
|
15
|
-
import { GoogleSearchAIParser } from
|
|
16
|
-
import { GeminiCloudAssistParser } from
|
|
1
|
+
import { AI_CHAT_DOMAINS, URL_PATTERNS } from "./domains.js";
|
|
2
|
+
import { ChatGPTParser } from "../ai/chatgpt.js";
|
|
3
|
+
import { ClaudeParser } from "../ai/claude.js";
|
|
4
|
+
import { GeminiParser } from "../ai/gemini.js";
|
|
5
|
+
import { CopilotParser } from "../ai/copilot.js";
|
|
6
|
+
import { DeepSeekParser } from "../ai/deepseek.js";
|
|
7
|
+
import { MetaParser } from "../ai/meta.js";
|
|
8
|
+
import { MistralParser } from "../ai/mistral.js";
|
|
9
|
+
import { PerplexityParser } from "../ai/perplexity.js";
|
|
10
|
+
import { QwenParser } from "../ai/qwen.js";
|
|
11
|
+
import { LumoParser } from "../ai/lumo.js";
|
|
12
|
+
import { ZAiParser } from "../ai/z_ai.js";
|
|
13
|
+
import { GoogleAIStudioParser } from "../ai/google_ai_studio.js";
|
|
14
|
+
import { NotebookLMParser } from "../ai/notebooklm.js";
|
|
15
|
+
import { GoogleSearchAIParser } from "../ai/google_search_ai.js";
|
|
16
|
+
import { GeminiCloudAssistParser } from "../ai/gemini_cloud_assist.js";
|
|
17
|
+
import { JoylandParser } from "../ai/joyland.js";
|
|
18
|
+
import { ChubParser } from "../ai/chub.js";
|
|
17
19
|
|
|
18
20
|
/**
|
|
19
21
|
* Ordered list of parsers. First match wins.
|
|
@@ -35,6 +37,8 @@ export const parsers = [
|
|
|
35
37
|
new NotebookLMParser(),
|
|
36
38
|
new GoogleSearchAIParser(),
|
|
37
39
|
new GeminiCloudAssistParser(),
|
|
40
|
+
new JoylandParser(),
|
|
41
|
+
new ChubParser(),
|
|
38
42
|
];
|
|
39
43
|
|
|
40
44
|
/**
|
|
@@ -56,7 +60,7 @@ export function detectPlatform(url) {
|
|
|
56
60
|
if (domainMatch) {
|
|
57
61
|
const parser = parsers.find((p) => p.isAvailable(url));
|
|
58
62
|
if (parser) {
|
|
59
|
-
return { type:
|
|
63
|
+
return { type: "ai-chat", platform: parser.getPlatformName(), parser };
|
|
60
64
|
}
|
|
61
65
|
}
|
|
62
66
|
} catch {
|
|
@@ -68,7 +72,7 @@ export function detectPlatform(url) {
|
|
|
68
72
|
if (pattern.test(url)) {
|
|
69
73
|
const parser = parsers.find((p) => p.isAvailable(url));
|
|
70
74
|
if (parser) {
|
|
71
|
-
return { type:
|
|
75
|
+
return { type: "ai-chat", platform: parser.getPlatformName(), parser };
|
|
72
76
|
}
|
|
73
77
|
}
|
|
74
78
|
}
|
|
@@ -89,7 +93,11 @@ export function isAiChatUrl(url) {
|
|
|
89
93
|
try {
|
|
90
94
|
const parsed = new URL(url);
|
|
91
95
|
const hostname = parsed.hostname.toLowerCase();
|
|
92
|
-
if (
|
|
96
|
+
if (
|
|
97
|
+
AI_CHAT_DOMAINS.some(
|
|
98
|
+
(domain) => hostname === domain || hostname.endsWith(`.${domain}`),
|
|
99
|
+
)
|
|
100
|
+
) {
|
|
93
101
|
return true;
|
|
94
102
|
}
|
|
95
103
|
} catch {
|
package/detection/domains.js
CHANGED
|
@@ -4,21 +4,24 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
export const AI_CHAT_DOMAINS = [
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
7
|
+
"chatgpt.com",
|
|
8
|
+
"claude.ai",
|
|
9
|
+
"gemini.google.com",
|
|
10
|
+
"chat.deepseek.com",
|
|
11
|
+
"perplexity.ai",
|
|
12
|
+
"chat.qwen.ai",
|
|
13
|
+
"qwen.ai",
|
|
14
|
+
"chat.mistral.ai",
|
|
15
|
+
"copilot.microsoft.com",
|
|
16
|
+
"lumo.proton.me",
|
|
17
|
+
"meta.ai",
|
|
18
|
+
"aistudio.google.com",
|
|
19
|
+
"notebooklm.google.com",
|
|
20
|
+
"notebook.google.com",
|
|
21
|
+
"chat.z.ai",
|
|
22
|
+
"joyland.ai",
|
|
23
|
+
"chub.ai",
|
|
24
|
+
"characterhub.org",
|
|
22
25
|
];
|
|
23
26
|
|
|
24
27
|
/**
|
|
@@ -27,19 +30,24 @@ export const AI_CHAT_DOMAINS = [
|
|
|
27
30
|
*/
|
|
28
31
|
export const URL_PATTERNS = [
|
|
29
32
|
{
|
|
30
|
-
test: (url) =>
|
|
31
|
-
url.includes(
|
|
32
|
-
url.includes(
|
|
33
|
-
url.includes(
|
|
34
|
-
url.includes(
|
|
35
|
-
|
|
33
|
+
test: (url) =>
|
|
34
|
+
url.includes("copilot.microsoft.com") ||
|
|
35
|
+
url.includes("copilot.com") ||
|
|
36
|
+
url.includes("copilot.cloud.microsoft") ||
|
|
37
|
+
url.includes("m365.cloud.microsoft") ||
|
|
38
|
+
url.includes("m365.microsoft.com") ||
|
|
39
|
+
url.includes("bing.com/chat") ||
|
|
40
|
+
url.includes("bing.com/copilot") ||
|
|
41
|
+
url.includes("bing.com/copilotsearch") ||
|
|
42
|
+
url.includes("edgeservices.bing.com"),
|
|
43
|
+
platform: "copilot",
|
|
36
44
|
},
|
|
37
45
|
{
|
|
38
46
|
test: (url) => /google\.[a-z.]+\/search/.test(url),
|
|
39
|
-
platform:
|
|
47
|
+
platform: "google-search-ai",
|
|
40
48
|
},
|
|
41
49
|
{
|
|
42
|
-
test: (url) => url.includes(
|
|
43
|
-
platform:
|
|
50
|
+
test: (url) => url.includes("console.cloud.google.com/gemini"),
|
|
51
|
+
platform: "gemini-cloud-assist",
|
|
44
52
|
},
|
|
45
53
|
];
|