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
|
@@ -1,60 +1,67 @@
|
|
|
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 GeminiCloudAssistParser extends ChatParser {
|
|
5
|
-
name =
|
|
5
|
+
name = "Gemini Cloud Assist";
|
|
6
6
|
isAvailable(url) {
|
|
7
|
-
return url.includes(
|
|
7
|
+
return url.includes("console.cloud.google.com/gemini");
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
async parse() {
|
|
11
11
|
const activeItem = document.querySelector(
|
|
12
|
-
|
|
12
|
+
"mat-list-item.mdc-list-item--activated span.cfc-flex-grow-content",
|
|
13
13
|
);
|
|
14
|
-
let title = activeItem?.textContent?.trim() ||
|
|
14
|
+
let title = activeItem?.textContent?.trim() || "";
|
|
15
15
|
|
|
16
16
|
// Fallback if no active item
|
|
17
17
|
if (!title) {
|
|
18
|
-
const firstUserMsg = document
|
|
18
|
+
const firstUserMsg = document
|
|
19
|
+
.querySelector(".aic-user-message-text")
|
|
20
|
+
?.textContent?.trim();
|
|
19
21
|
if (firstUserMsg) {
|
|
20
|
-
title =
|
|
22
|
+
title =
|
|
23
|
+
firstUserMsg.length > 50
|
|
24
|
+
? firstUserMsg.substring(0, 50) + "..."
|
|
25
|
+
: firstUserMsg;
|
|
21
26
|
}
|
|
22
27
|
}
|
|
23
28
|
|
|
24
29
|
if (!title) {
|
|
25
|
-
title = document.title ||
|
|
30
|
+
title = document.title || "Gemini Cloud Assist";
|
|
26
31
|
}
|
|
27
32
|
|
|
28
33
|
// Clean up title whitespace
|
|
29
|
-
title = title.replace(/\s+/g,
|
|
34
|
+
title = title.replace(/\s+/g, " ").trim();
|
|
30
35
|
|
|
31
36
|
const messages = [];
|
|
32
37
|
|
|
33
38
|
// Find all turns inside aic-conversation or standard turn containers
|
|
34
|
-
const turns = document.querySelectorAll(
|
|
39
|
+
const turns = document.querySelectorAll(
|
|
40
|
+
"aic-conversation .aic-turn, .aic-turn",
|
|
41
|
+
);
|
|
35
42
|
|
|
36
43
|
turns.forEach((turn) => {
|
|
37
44
|
// 1. Process user message
|
|
38
|
-
const userEl = turn.querySelector(
|
|
45
|
+
const userEl = turn.querySelector(".aic-user-message");
|
|
39
46
|
if (userEl) {
|
|
40
|
-
const textEl = userEl.querySelector(
|
|
47
|
+
const textEl = userEl.querySelector(".aic-user-message-text") || userEl;
|
|
41
48
|
let text = convertToMarkdown(textEl).trim();
|
|
42
49
|
if (text) {
|
|
43
50
|
messages.push({
|
|
44
|
-
role:
|
|
51
|
+
role: "User",
|
|
45
52
|
content: text,
|
|
46
53
|
});
|
|
47
54
|
}
|
|
48
55
|
}
|
|
49
56
|
|
|
50
57
|
// 2. Process assistant message
|
|
51
|
-
const agentEl = turn.querySelector(
|
|
58
|
+
const agentEl = turn.querySelector("aic-agent-entry");
|
|
52
59
|
if (agentEl) {
|
|
53
60
|
// Prefer the actual markdown renderer content if present, to avoid feedback actions/buttons noise
|
|
54
61
|
const markdownEl =
|
|
55
|
-
agentEl.querySelector(
|
|
56
|
-
agentEl.querySelector(
|
|
57
|
-
agentEl.querySelector(
|
|
62
|
+
agentEl.querySelector(".ai-markdown-artifact-renderer") ||
|
|
63
|
+
agentEl.querySelector(".aic-markdown-renderer-container") ||
|
|
64
|
+
agentEl.querySelector(".aic-agent-content") ||
|
|
58
65
|
agentEl;
|
|
59
66
|
|
|
60
67
|
// Clone the element to safely strip unwanted components
|
|
@@ -62,7 +69,7 @@ export class GeminiCloudAssistParser extends ChatParser {
|
|
|
62
69
|
// Remove thumb up/down feedback buttons or loader/expander elements
|
|
63
70
|
clone
|
|
64
71
|
.querySelectorAll(
|
|
65
|
-
|
|
72
|
+
"button, aic-feedback-actions, .aic-agent-thoughts-container, aic-loading-indicator",
|
|
66
73
|
)
|
|
67
74
|
.forEach((el) => {
|
|
68
75
|
el.remove();
|
|
@@ -71,7 +78,7 @@ export class GeminiCloudAssistParser extends ChatParser {
|
|
|
71
78
|
let text = convertToMarkdown(clone).trim();
|
|
72
79
|
if (text) {
|
|
73
80
|
messages.push({
|
|
74
|
-
role:
|
|
81
|
+
role: "Model",
|
|
75
82
|
content: text,
|
|
76
83
|
});
|
|
77
84
|
}
|
|
@@ -79,9 +86,11 @@ export class GeminiCloudAssistParser extends ChatParser {
|
|
|
79
86
|
});
|
|
80
87
|
|
|
81
88
|
const currentUrl =
|
|
82
|
-
typeof window !==
|
|
89
|
+
typeof window !== "undefined" && window.location
|
|
90
|
+
? window.location.href || ""
|
|
91
|
+
: "";
|
|
83
92
|
const metadata = {
|
|
84
|
-
Source:
|
|
93
|
+
Source: "Gemini Cloud Assist",
|
|
85
94
|
Date: new Date().toLocaleString(),
|
|
86
95
|
Link: currentUrl,
|
|
87
96
|
};
|
package/ai/google_ai_studio.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
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 GoogleAIStudioParser extends ChatParser {
|
|
5
|
-
name =
|
|
5
|
+
name = "Google AI Studio";
|
|
6
6
|
isAvailable(url) {
|
|
7
|
-
return url.includes(
|
|
7
|
+
return url.includes("aistudio.google.com");
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
async parse() {
|
|
11
|
-
let title =
|
|
11
|
+
let title = "";
|
|
12
12
|
|
|
13
13
|
// Title extraction strategies
|
|
14
14
|
const titleInput = document.querySelector(
|
|
@@ -18,51 +18,59 @@ export class GoogleAIStudioParser extends ChatParser {
|
|
|
18
18
|
title = titleInput.value.trim();
|
|
19
19
|
}
|
|
20
20
|
if (!title) {
|
|
21
|
-
const headerTitle = document.querySelector(
|
|
21
|
+
const headerTitle = document.querySelector(
|
|
22
|
+
".title-text, h1, .prompt-name",
|
|
23
|
+
);
|
|
22
24
|
if (headerTitle) {
|
|
23
25
|
title = headerTitle.textContent.trim();
|
|
24
26
|
}
|
|
25
27
|
}
|
|
26
28
|
if (!title) {
|
|
27
|
-
title = document.title ||
|
|
29
|
+
title = document.title || "Google AI Studio Chat";
|
|
28
30
|
}
|
|
29
|
-
title = title.replace(/\s+/g,
|
|
31
|
+
title = title.replace(/\s+/g, " ").trim();
|
|
30
32
|
|
|
31
33
|
const messages = [];
|
|
32
34
|
|
|
33
35
|
// Selectors for chat turns
|
|
34
36
|
const allTurnElements = Array.from(
|
|
35
|
-
document.querySelectorAll(
|
|
37
|
+
document.querySelectorAll(
|
|
38
|
+
"ms-chat-turn, .chat-turn-container, ms-prompt-editor",
|
|
39
|
+
),
|
|
36
40
|
);
|
|
37
41
|
|
|
38
42
|
// Filter to top-level turn elements only (avoiding child elements nested in parent turn containers)
|
|
39
43
|
const turnElements = allTurnElements.filter(
|
|
40
|
-
(el) =>
|
|
44
|
+
(el) =>
|
|
45
|
+
!allTurnElements.some((parent) => parent !== el && parent.contains(el)),
|
|
41
46
|
);
|
|
42
47
|
|
|
43
48
|
if (turnElements.length > 0) {
|
|
44
49
|
turnElements.forEach((turn) => {
|
|
45
50
|
// User prompt element
|
|
46
51
|
const userEl =
|
|
47
|
-
turn.querySelector(
|
|
48
|
-
|
|
52
|
+
turn.querySelector(
|
|
53
|
+
".user-prompt, .user-prompt-container, .user-message",
|
|
54
|
+
) || (turn.tagName === "MS-PROMPT-EDITOR" ? turn : null);
|
|
49
55
|
|
|
50
56
|
// Model output element
|
|
51
57
|
const aiEl = turn.querySelector(
|
|
52
|
-
|
|
58
|
+
".model-response-text, .model-output, ms-model-output, .model-prompt-container",
|
|
53
59
|
);
|
|
54
60
|
|
|
55
61
|
if (userEl) {
|
|
56
|
-
const contentEl =
|
|
62
|
+
const contentEl =
|
|
63
|
+
userEl.querySelector(".turn-content, .prompt-text, textarea") ||
|
|
64
|
+
userEl;
|
|
57
65
|
let text;
|
|
58
|
-
if (contentEl.tagName ===
|
|
59
|
-
text = contentEl.value || contentEl.textContent ||
|
|
66
|
+
if (contentEl.tagName === "TEXTAREA") {
|
|
67
|
+
text = contentEl.value || contentEl.textContent || "";
|
|
60
68
|
} else {
|
|
61
69
|
text = convertToMarkdown(contentEl);
|
|
62
70
|
}
|
|
63
71
|
text = text.trim();
|
|
64
72
|
if (text) {
|
|
65
|
-
messages.push({ role:
|
|
73
|
+
messages.push({ role: "User", content: text });
|
|
66
74
|
}
|
|
67
75
|
}
|
|
68
76
|
|
|
@@ -71,13 +79,17 @@ export class GoogleAIStudioParser extends ChatParser {
|
|
|
71
79
|
// Remove noise elements like copy buttons or action menus
|
|
72
80
|
clone
|
|
73
81
|
.querySelectorAll(
|
|
74
|
-
|
|
82
|
+
"button, .mat-expansion-panel-header, .author-label, .timestamp, ms-prompt-options-menu, .navigator-container",
|
|
75
83
|
)
|
|
76
84
|
.forEach((el) => el.remove());
|
|
77
85
|
|
|
78
86
|
let text = convertToMarkdown(clone).trim();
|
|
79
|
-
if (
|
|
80
|
-
|
|
87
|
+
if (
|
|
88
|
+
text &&
|
|
89
|
+
text !== "Thinking..." &&
|
|
90
|
+
!text.includes("model_thought_output")
|
|
91
|
+
) {
|
|
92
|
+
messages.push({ role: "Google AI Studio", content: text });
|
|
81
93
|
}
|
|
82
94
|
}
|
|
83
95
|
});
|
|
@@ -85,28 +97,34 @@ export class GoogleAIStudioParser extends ChatParser {
|
|
|
85
97
|
|
|
86
98
|
// Fallback if turnElements linear loop yielded no messages
|
|
87
99
|
if (messages.length === 0) {
|
|
88
|
-
const userPrompts = document.querySelectorAll(
|
|
100
|
+
const userPrompts = document.querySelectorAll(
|
|
101
|
+
".user-prompt, .user-prompt-container",
|
|
102
|
+
);
|
|
89
103
|
const modelOutputs = document.querySelectorAll(
|
|
90
|
-
|
|
104
|
+
".model-response-text, ms-model-output, .model-output",
|
|
91
105
|
);
|
|
92
106
|
|
|
93
107
|
const maxLen = Math.max(userPrompts.length, modelOutputs.length);
|
|
94
108
|
for (let i = 0; i < maxLen; i++) {
|
|
95
109
|
if (userPrompts[i]) {
|
|
96
|
-
const text = (
|
|
97
|
-
|
|
110
|
+
const text = (
|
|
111
|
+
userPrompts[i].value || convertToMarkdown(userPrompts[i])
|
|
112
|
+
).trim();
|
|
113
|
+
if (text) messages.push({ role: "User", content: text });
|
|
98
114
|
}
|
|
99
115
|
if (modelOutputs[i]) {
|
|
100
116
|
const text = convertToMarkdown(modelOutputs[i]).trim();
|
|
101
|
-
if (text) messages.push({ role:
|
|
117
|
+
if (text) messages.push({ role: "Google AI Studio", content: text });
|
|
102
118
|
}
|
|
103
119
|
}
|
|
104
120
|
}
|
|
105
121
|
|
|
106
122
|
const currentUrl =
|
|
107
|
-
typeof window !==
|
|
123
|
+
typeof window !== "undefined" && window.location
|
|
124
|
+
? window.location.href || ""
|
|
125
|
+
: "";
|
|
108
126
|
const metadata = {
|
|
109
|
-
Source:
|
|
127
|
+
Source: "Google AI Studio",
|
|
110
128
|
Date: new Date().toLocaleString(),
|
|
111
129
|
Link: currentUrl,
|
|
112
130
|
};
|
package/ai/google_search_ai.js
CHANGED
|
@@ -1,29 +1,29 @@
|
|
|
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 GoogleSearchAIParser extends ChatParser {
|
|
5
|
-
name =
|
|
5
|
+
name = "Google Search AI";
|
|
6
6
|
isAvailable(url) {
|
|
7
7
|
return /google\.[a-z.]+\/search/.test(url);
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
async parse() {
|
|
11
|
-
const title = document.title ||
|
|
11
|
+
const title = document.title || "Google Search AI Overview";
|
|
12
12
|
const messages = [];
|
|
13
13
|
|
|
14
14
|
// 1. Extract the user's queries
|
|
15
15
|
const queries = [];
|
|
16
|
-
const queryElements = document.querySelectorAll(
|
|
16
|
+
const queryElements = document.querySelectorAll(".sUKAcb");
|
|
17
17
|
queryElements.forEach((el) => {
|
|
18
18
|
// Clone the element to avoid mutating the live DOM
|
|
19
19
|
const clone = el.cloneNode(true);
|
|
20
|
-
const prefixEl = clone.querySelector(
|
|
20
|
+
const prefixEl = clone.querySelector(".iMqumd");
|
|
21
21
|
if (prefixEl) {
|
|
22
22
|
prefixEl.remove();
|
|
23
23
|
}
|
|
24
|
-
let text = clone.textContent ||
|
|
24
|
+
let text = clone.textContent || "";
|
|
25
25
|
// Fallback regex cleanup if needed
|
|
26
|
-
text = text.replace(/^\s*You\s+said:\s*/i,
|
|
26
|
+
text = text.replace(/^\s*You\s+said:\s*/i, "").trim();
|
|
27
27
|
if (text) {
|
|
28
28
|
queries.push(text);
|
|
29
29
|
}
|
|
@@ -31,29 +31,29 @@ export class GoogleSearchAIParser extends ChatParser {
|
|
|
31
31
|
|
|
32
32
|
// Fallback to single-turn query extraction if no turn query elements are found
|
|
33
33
|
if (queries.length === 0) {
|
|
34
|
-
let userQuery =
|
|
35
|
-
const uqEl = document.querySelector(
|
|
34
|
+
let userQuery = "";
|
|
35
|
+
const uqEl = document.querySelector("[data-uq]");
|
|
36
36
|
if (uqEl) {
|
|
37
|
-
userQuery = uqEl.getAttribute(
|
|
37
|
+
userQuery = uqEl.getAttribute("data-uq");
|
|
38
38
|
}
|
|
39
39
|
if (!userQuery) {
|
|
40
|
-
const qEl = document.querySelector(
|
|
40
|
+
const qEl = document.querySelector("[data-q]");
|
|
41
41
|
if (qEl) {
|
|
42
|
-
userQuery = qEl.getAttribute(
|
|
42
|
+
userQuery = qEl.getAttribute("data-q");
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
45
|
if (!userQuery) {
|
|
46
46
|
try {
|
|
47
47
|
const url = new URL(window.location.href);
|
|
48
|
-
userQuery = url.searchParams.get(
|
|
48
|
+
userQuery = url.searchParams.get("q");
|
|
49
49
|
} catch {
|
|
50
50
|
// Ignore invalid URLs
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
53
|
if (!userQuery) {
|
|
54
54
|
const textarea =
|
|
55
|
-
document.querySelector(
|
|
56
|
-
document.querySelector(
|
|
55
|
+
document.querySelector("textarea.gLFyf") ||
|
|
56
|
+
document.querySelector("textarea.ITIRGe") ||
|
|
57
57
|
document.querySelector('input[name="q"]');
|
|
58
58
|
if (textarea) {
|
|
59
59
|
userQuery = textarea.value || textarea.textContent;
|
|
@@ -71,7 +71,9 @@ export class GoogleSearchAIParser extends ChatParser {
|
|
|
71
71
|
turnElements.forEach((turnEl) => {
|
|
72
72
|
const resEl =
|
|
73
73
|
turnEl.querySelector('[data-container-id="main-col"]') ||
|
|
74
|
-
turnEl.querySelector(
|
|
74
|
+
turnEl.querySelector(
|
|
75
|
+
'[data-container-id="model-response-placeholder"]',
|
|
76
|
+
);
|
|
75
77
|
if (resEl) {
|
|
76
78
|
responseContainers.push(resEl);
|
|
77
79
|
}
|
|
@@ -80,7 +82,9 @@ export class GoogleSearchAIParser extends ChatParser {
|
|
|
80
82
|
// Fallback for pages without data-scope-id="turn"
|
|
81
83
|
const resEl =
|
|
82
84
|
document.querySelector('[data-container-id="main-col"]') ||
|
|
83
|
-
document.querySelector(
|
|
85
|
+
document.querySelector(
|
|
86
|
+
'[data-container-id="model-response-placeholder"]',
|
|
87
|
+
);
|
|
84
88
|
if (resEl) {
|
|
85
89
|
responseContainers.push(resEl);
|
|
86
90
|
}
|
|
@@ -89,22 +93,27 @@ export class GoogleSearchAIParser extends ChatParser {
|
|
|
89
93
|
// Pair queries and responses in order
|
|
90
94
|
const minLength = Math.min(queries.length, responseContainers.length);
|
|
91
95
|
for (let i = 0; i < minLength; i++) {
|
|
92
|
-
messages.push({ role:
|
|
96
|
+
messages.push({ role: "User", content: queries[i].trim() });
|
|
93
97
|
const text = convertToMarkdown(responseContainers[i]);
|
|
94
98
|
if (text.trim()) {
|
|
95
|
-
messages.push({ role:
|
|
99
|
+
messages.push({ role: "Model", content: text.trim() });
|
|
96
100
|
}
|
|
97
101
|
}
|
|
98
102
|
|
|
99
103
|
// If there is a trailing user query with no response container (yet)
|
|
100
104
|
if (queries.length > responseContainers.length) {
|
|
101
|
-
messages.push({
|
|
105
|
+
messages.push({
|
|
106
|
+
role: "User",
|
|
107
|
+
content: queries[queries.length - 1].trim(),
|
|
108
|
+
});
|
|
102
109
|
}
|
|
103
110
|
|
|
104
111
|
const currentUrl =
|
|
105
|
-
typeof window !==
|
|
112
|
+
typeof window !== "undefined" && window.location
|
|
113
|
+
? window.location.href || ""
|
|
114
|
+
: "";
|
|
106
115
|
const metadata = {
|
|
107
|
-
Source:
|
|
116
|
+
Source: "Google Search AI",
|
|
108
117
|
Date: new Date().toLocaleString(),
|
|
109
118
|
Link: currentUrl,
|
|
110
119
|
};
|
package/ai/index.js
CHANGED
|
@@ -7,28 +7,34 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
// Base class
|
|
10
|
-
export { ChatParser } from
|
|
10
|
+
export { ChatParser } from "./base.js";
|
|
11
11
|
|
|
12
12
|
// Individual parsers
|
|
13
|
-
export { ChatGPTParser } from
|
|
14
|
-
export { ClaudeParser } from
|
|
15
|
-
export { GeminiParser } from
|
|
16
|
-
export { CopilotParser } from
|
|
17
|
-
export { DeepSeekParser } from
|
|
18
|
-
export { MetaParser } from
|
|
19
|
-
export { MistralParser } from
|
|
20
|
-
export { PerplexityParser } from
|
|
21
|
-
export { QwenParser } from
|
|
22
|
-
export { LumoParser } from
|
|
23
|
-
export { ZAiParser } from
|
|
24
|
-
export { GoogleAIStudioParser } from
|
|
25
|
-
export { NotebookLMParser } from
|
|
26
|
-
export { GoogleSearchAIParser } from
|
|
27
|
-
export { GeminiCloudAssistParser } from
|
|
13
|
+
export { ChatGPTParser } from "./chatgpt.js";
|
|
14
|
+
export { ClaudeParser } from "./claude.js";
|
|
15
|
+
export { GeminiParser } from "./gemini.js";
|
|
16
|
+
export { CopilotParser } from "./copilot.js";
|
|
17
|
+
export { DeepSeekParser } from "./deepseek.js";
|
|
18
|
+
export { MetaParser } from "./meta.js";
|
|
19
|
+
export { MistralParser } from "./mistral.js";
|
|
20
|
+
export { PerplexityParser } from "./perplexity.js";
|
|
21
|
+
export { QwenParser } from "./qwen.js";
|
|
22
|
+
export { LumoParser } from "./lumo.js";
|
|
23
|
+
export { ZAiParser } from "./z_ai.js";
|
|
24
|
+
export { GoogleAIStudioParser } from "./google_ai_studio.js";
|
|
25
|
+
export { NotebookLMParser } from "./notebooklm.js";
|
|
26
|
+
export { GoogleSearchAIParser } from "./google_search_ai.js";
|
|
27
|
+
export { GeminiCloudAssistParser } from "./gemini_cloud_assist.js";
|
|
28
|
+
export { JoylandParser } from "./joyland.js";
|
|
29
|
+
export { ChubParser } from "./chub.js";
|
|
28
30
|
|
|
29
31
|
// Utilities
|
|
30
|
-
export { convertToMarkdown, cleanMarkdown } from
|
|
32
|
+
export { convertToMarkdown, cleanMarkdown } from "../utils/html-to-markdown.js";
|
|
31
33
|
|
|
32
34
|
// Detection
|
|
33
|
-
export {
|
|
34
|
-
|
|
35
|
+
export {
|
|
36
|
+
detectPlatform,
|
|
37
|
+
isAiChatUrl,
|
|
38
|
+
parsers,
|
|
39
|
+
} from "../detection/detect-platform.js";
|
|
40
|
+
export { AI_CHAT_DOMAINS } from "../detection/domains.js";
|
package/ai/joyland.js
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { ChatParser } from "./base.js";
|
|
2
|
+
import { convertToMarkdown } from "../utils/html-to-markdown.js";
|
|
3
|
+
|
|
4
|
+
export class JoylandParser extends ChatParser {
|
|
5
|
+
name = "Joyland";
|
|
6
|
+
|
|
7
|
+
isAvailable(url) {
|
|
8
|
+
return url.includes("joyland.ai");
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
async parse() {
|
|
12
|
+
// Extract Character Name & Creator
|
|
13
|
+
const botNameEl =
|
|
14
|
+
document.querySelector(".bot-name-text") ||
|
|
15
|
+
document.querySelector(".bot-name");
|
|
16
|
+
let botName = botNameEl
|
|
17
|
+
? botNameEl.textContent.trim().replace(/\s+/g, " ")
|
|
18
|
+
: "";
|
|
19
|
+
if (
|
|
20
|
+
botName.endsWith("1") &&
|
|
21
|
+
botNameEl?.parentElement?.querySelector(".bot-name-text")
|
|
22
|
+
) {
|
|
23
|
+
const cleanEl = botNameEl.parentElement.querySelector(".bot-name-text");
|
|
24
|
+
if (cleanEl) botName = cleanEl.textContent.trim();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const creatorEl = document.querySelector(".creator-name .name");
|
|
28
|
+
const creator = creatorEl
|
|
29
|
+
? creatorEl.textContent.trim().replace(/\s+/g, " ")
|
|
30
|
+
: "";
|
|
31
|
+
|
|
32
|
+
// Extract Title
|
|
33
|
+
let title = "";
|
|
34
|
+
if (botName) {
|
|
35
|
+
title = `${botName} - Joyland Chat`;
|
|
36
|
+
} else if (document.title) {
|
|
37
|
+
title = document.title.replace(/\s*-\s*Joyland.*$/i, "").trim();
|
|
38
|
+
}
|
|
39
|
+
title = title || "Joyland Conversation";
|
|
40
|
+
|
|
41
|
+
const messages = [];
|
|
42
|
+
const items = document.querySelectorAll(".chat-list-item");
|
|
43
|
+
|
|
44
|
+
for (const item of items) {
|
|
45
|
+
const isUser = !!item.querySelector(".user-text-box, .user-text");
|
|
46
|
+
const isRobot = !!item.querySelector(".robot-text-box, .robot-text");
|
|
47
|
+
if (!isUser && !isRobot) continue;
|
|
48
|
+
|
|
49
|
+
const rawContentEl =
|
|
50
|
+
item.querySelector(".markdown-body") ||
|
|
51
|
+
item.querySelector(".body-text") ||
|
|
52
|
+
item;
|
|
53
|
+
const contentElClone = rawContentEl.cloneNode(true);
|
|
54
|
+
|
|
55
|
+
// Remove UI noise elements (audio player, ratings, refresh buttons, etc.)
|
|
56
|
+
contentElClone
|
|
57
|
+
.querySelectorAll(
|
|
58
|
+
".audio-style-box, .audio-style, .rate-container, .rate, .refresh, button, .click-gif, svg",
|
|
59
|
+
)
|
|
60
|
+
.forEach((el) => el.remove());
|
|
61
|
+
|
|
62
|
+
const markdown = convertToMarkdown(contentElClone);
|
|
63
|
+
if (markdown.trim()) {
|
|
64
|
+
messages.push({
|
|
65
|
+
role: isUser ? "User" : "Joyland",
|
|
66
|
+
content: markdown.trim(),
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const currentUrl =
|
|
72
|
+
typeof window !== "undefined" && window.location
|
|
73
|
+
? window.location.href || ""
|
|
74
|
+
: "";
|
|
75
|
+
const metadata = {
|
|
76
|
+
Source: "Joyland",
|
|
77
|
+
Date: new Date().toLocaleString(),
|
|
78
|
+
Link: currentUrl,
|
|
79
|
+
};
|
|
80
|
+
if (botName) metadata.Character = botName;
|
|
81
|
+
if (creator) metadata.Creator = creator;
|
|
82
|
+
|
|
83
|
+
return { title, messages, url: currentUrl, metadata };
|
|
84
|
+
}
|
|
85
|
+
}
|