webforai 3.0.0 → 4.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/README.md +25 -5
- package/dist/bin.js +1277 -479
- package/dist/cjs/detect-client-shell.js +78 -0
- package/dist/cjs/extract-meta-refresh.js +71 -0
- package/dist/cjs/extractors/lib/sanitize.js +4 -0
- package/dist/cjs/html-to-mdast.js +2 -1
- package/dist/cjs/index.js +12 -0
- package/dist/cjs/link-replacer.js +19 -13
- package/dist/cjs/loaders/cf-puppeteer.js +14 -7
- package/dist/cjs/loaders/fetch.js +14 -2
- package/dist/cjs/loaders/playwright.js +32 -30
- package/dist/cjs/loaders/puppeteer.js +14 -7
- package/dist/cjs/md-splitter.js +24 -11
- package/dist/cjs/mdast-handlers/custom-code-handler.js +18 -7
- package/dist/cjs/mdast-handlers/custom-div-handler.js +12 -33
- package/dist/cjs/mdast-handlers/definition-list-handler.js +17 -6
- package/dist/cjs/mdast-to-markdown.js +2 -5
- package/dist/cjs/normalize/index.js +11 -2
- package/dist/cjs/platform/client.js +73 -16
- package/dist/cjs/platform/index.js +4 -0
- package/dist/cjs/platform/types.js +7 -1
- package/dist/detect-client-shell.js +53 -0
- package/dist/extract-meta-refresh.js +47 -0
- package/dist/extractors/lib/sanitize.js +4 -0
- package/dist/html-to-mdast.js +2 -1
- package/dist/index.js +14 -0
- package/dist/link-replacer.js +19 -13
- package/dist/loaders/cf-puppeteer.js +15 -8
- package/dist/loaders/fetch.js +14 -2
- package/dist/loaders/playwright.js +32 -30
- package/dist/loaders/puppeteer.js +15 -8
- package/dist/md-splitter.js +24 -11
- package/dist/mdast-handlers/custom-code-handler.js +17 -7
- package/dist/mdast-handlers/custom-div-handler.js +12 -33
- package/dist/mdast-handlers/definition-list-handler.js +17 -6
- package/dist/mdast-to-markdown.js +2 -5
- package/dist/normalize/index.js +12 -2
- package/dist/platform/client.js +73 -16
- package/dist/platform/index.js +4 -0
- package/dist/platform/types.js +5 -1
- package/dist/types/cli/bin.js.map +1 -1
- package/dist/types/cli/commands/jobs/files.d.ts +28 -0
- package/dist/types/cli/commands/jobs/files.js.map +1 -0
- package/dist/types/cli/commands/jobs/llms-txt.d.ts +33 -0
- package/dist/types/cli/commands/jobs/llms-txt.js.map +1 -0
- package/dist/types/cli/commands/jobs/options.d.ts +58 -0
- package/dist/types/cli/commands/jobs/options.js.map +1 -0
- package/dist/types/cli/commands/jobs/run.d.ts +44 -0
- package/dist/types/cli/commands/jobs/run.js.map +1 -0
- package/dist/types/cli/commands/skill/content.js.map +1 -1
- package/dist/types/cli/commands/webforai/interactive.d.ts +1 -1
- package/dist/types/cli/commands/webforai/interactive.js.map +1 -1
- package/dist/types/cli/commands/webforai/options.d.ts +9 -2
- package/dist/types/cli/commands/webforai/options.js.map +1 -1
- package/dist/types/cli/commands/webforai/run.js.map +1 -1
- package/dist/types/cli/utils.d.ts +8 -1
- package/dist/types/cli/utils.js.map +1 -1
- package/dist/types/detect-client-shell.d.ts +24 -0
- package/dist/types/detect-client-shell.js.map +1 -0
- package/dist/types/extract-meta-refresh.d.ts +29 -0
- package/dist/types/extract-meta-refresh.js.map +1 -0
- package/dist/types/extractors/lib/sanitize.js.map +1 -1
- package/dist/types/html-to-mdast.js.map +1 -1
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.js.map +1 -1
- package/dist/types/link-replacer.d.ts +3 -1
- package/dist/types/link-replacer.js.map +1 -1
- package/dist/types/loaders/cf-puppeteer.js.map +1 -1
- package/dist/types/loaders/fetch.d.ts +4 -0
- package/dist/types/loaders/fetch.js.map +1 -1
- package/dist/types/loaders/playwright.js.map +1 -1
- package/dist/types/loaders/puppeteer.js.map +1 -1
- package/dist/types/md-splitter.d.ts +1 -1
- package/dist/types/md-splitter.js.map +1 -1
- package/dist/types/mdast-handlers/custom-code-handler.d.ts +2 -0
- package/dist/types/mdast-handlers/custom-code-handler.js.map +1 -1
- package/dist/types/mdast-handlers/custom-div-handler.js.map +1 -1
- package/dist/types/mdast-handlers/definition-list-handler.d.ts +0 -11
- package/dist/types/mdast-handlers/definition-list-handler.js.map +1 -1
- package/dist/types/mdast-to-markdown.js.map +1 -1
- package/dist/types/normalize/index.js.map +1 -1
- package/dist/types/platform/client.d.ts +2 -1
- package/dist/types/platform/client.js.map +1 -1
- package/dist/types/platform/error.d.ts +5 -3
- package/dist/types/platform/error.js.map +1 -1
- package/dist/types/platform/fetch.d.ts +2 -0
- package/dist/types/platform/fetch.js.map +1 -1
- package/dist/types/platform/index.d.ts +1 -1
- package/dist/types/platform/index.js.map +1 -1
- package/dist/types/platform/types.d.ts +35 -6
- package/dist/types/platform/types.js.map +1 -1
- package/package.json +16 -5
|
@@ -98,14 +98,16 @@ const katexDuplicates = (node) => {
|
|
|
98
98
|
if (!(classes.includes("katex") || classes.includes("katex-display"))) {
|
|
99
99
|
return [];
|
|
100
100
|
}
|
|
101
|
-
const hasMathml = node.children.some(
|
|
101
|
+
const hasMathml = node.children.some(
|
|
102
|
+
(child) => (0, import_hast_fast.isElement)(child) && (0, import_hast_fast.classList)(child).includes("katex-mathml") && hasMathSource(child)
|
|
103
|
+
);
|
|
102
104
|
if (!hasMathml) {
|
|
103
105
|
return [];
|
|
104
106
|
}
|
|
105
107
|
return node.children.filter((child) => (0, import_hast_fast.isElement)(child) && (0, import_hast_fast.classList)(child).includes("katex-html"));
|
|
106
108
|
};
|
|
107
109
|
const mediawikiDuplicates = (node) => {
|
|
108
|
-
if (!(0, import_hast_fast.classList)(node).includes("mwe-math-element")) {
|
|
110
|
+
if (!(0, import_hast_fast.classList)(node).includes("mwe-math-element") || !hasMathSource(node)) {
|
|
109
111
|
return [];
|
|
110
112
|
}
|
|
111
113
|
return node.children.filter(
|
|
@@ -116,8 +118,15 @@ const mathjaxDuplicates = (node) => {
|
|
|
116
118
|
if (node.tagName !== "mjx-container") {
|
|
117
119
|
return [];
|
|
118
120
|
}
|
|
121
|
+
const hasMathml = node.children.some(
|
|
122
|
+
(child) => (0, import_hast_fast.isElement)(child) && child.tagName === "mjx-assistive-mml" && hasMathSource(child)
|
|
123
|
+
);
|
|
124
|
+
if (!hasMathml) {
|
|
125
|
+
return [];
|
|
126
|
+
}
|
|
119
127
|
return node.children.filter((child) => (0, import_hast_fast.isElement)(child) && child.tagName !== "mjx-assistive-mml");
|
|
120
128
|
};
|
|
129
|
+
const hasMathSource = (node) => Boolean((0, import_hast_fast.findElement)(node, (element) => element.tagName === "math" && element.children.length > 0));
|
|
121
130
|
const dedupeRenderedMath = (tree) => {
|
|
122
131
|
const doomed = /* @__PURE__ */ new Set();
|
|
123
132
|
(0, import_hast_fast.walk)(tree, (node) => {
|
|
@@ -50,14 +50,28 @@ const parseRetryAfter = (body, response) => {
|
|
|
50
50
|
const header = Number(response.headers.get("retry-after"));
|
|
51
51
|
return Number.isFinite(header) && header > 0 ? header : void 0;
|
|
52
52
|
};
|
|
53
|
-
const
|
|
53
|
+
const dashboardHint = (status, dashboardUrl) => {
|
|
54
|
+
if (status === 401) {
|
|
55
|
+
return `create or check API keys at ${dashboardUrl}`;
|
|
56
|
+
}
|
|
57
|
+
if (status === 402) {
|
|
58
|
+
return `manage billing, credits and the spend cap at ${dashboardUrl}`;
|
|
59
|
+
}
|
|
60
|
+
return void 0;
|
|
61
|
+
};
|
|
62
|
+
const toApiError = (response, body, dashboardUrl) => {
|
|
54
63
|
const envelope = body;
|
|
55
64
|
const code = typeof envelope?.error?.code === "string" ? envelope.error.code : "invalid_response";
|
|
56
|
-
|
|
65
|
+
let message = typeof envelope?.error?.message === "string" ? envelope.error.message : `unexpected response (HTTP ${response.status})`;
|
|
66
|
+
const hint = code === "invalid_response" ? void 0 : dashboardHint(response.status, dashboardUrl);
|
|
67
|
+
if (hint && !message.includes("/dashboard")) {
|
|
68
|
+
message = `${message} (${hint})`;
|
|
69
|
+
}
|
|
57
70
|
return new import_error.PlatformApiError(code, message, response.status, parseRetryAfter(body, response));
|
|
58
71
|
};
|
|
59
72
|
const createPlatformClient = (options = {}) => {
|
|
60
73
|
const baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
74
|
+
const dashboardUrl = `${baseUrl}/dashboard`;
|
|
61
75
|
const fetchImpl = (0, import_fetch.resolveFetch)(options.fetch);
|
|
62
76
|
const request = async (path, init) => {
|
|
63
77
|
const headers = { accept: "application/json" };
|
|
@@ -68,7 +82,7 @@ const createPlatformClient = (options = {}) => {
|
|
|
68
82
|
if (!options.apiKey) {
|
|
69
83
|
throw new import_error.PlatformApiError(
|
|
70
84
|
"missing_api_key",
|
|
71
|
-
|
|
85
|
+
`This endpoint needs an API key: pass \`apiKey\` to createPlatformClient (create one at ${dashboardUrl}; 1,000 free credits/month).`,
|
|
72
86
|
0
|
|
73
87
|
);
|
|
74
88
|
}
|
|
@@ -77,18 +91,19 @@ const createPlatformClient = (options = {}) => {
|
|
|
77
91
|
const response = await fetchImpl(`${baseUrl}${path}`, {
|
|
78
92
|
method: init.method,
|
|
79
93
|
headers,
|
|
80
|
-
body: init.body === void 0 ? void 0 : JSON.stringify(init.body)
|
|
94
|
+
body: init.body === void 0 ? void 0 : JSON.stringify(init.body),
|
|
95
|
+
...init.signal ? { signal: init.signal } : {}
|
|
81
96
|
});
|
|
82
97
|
const body = await response.json().catch(() => void 0);
|
|
83
98
|
if (!response.ok) {
|
|
84
|
-
throw toApiError(response, body);
|
|
99
|
+
throw toApiError(response, body, dashboardUrl);
|
|
85
100
|
}
|
|
86
101
|
if (body === void 0) {
|
|
87
102
|
throw new import_error.PlatformApiError("invalid_response", "response body is not JSON", response.status);
|
|
88
103
|
}
|
|
89
104
|
return body;
|
|
90
105
|
};
|
|
91
|
-
const getJob = (jobId) => request(`/v1/jobs/${encodeURIComponent(jobId)}`, { method: "GET", auth: true });
|
|
106
|
+
const getJob = (jobId, signal) => request(`/v1/jobs/${encodeURIComponent(jobId)}`, { method: "GET", auth: true, signal });
|
|
92
107
|
const getJobResults = (jobId, opts) => {
|
|
93
108
|
const query = opts?.cursor ? `?cursor=${encodeURIComponent(opts.cursor)}` : "";
|
|
94
109
|
return request(`/v1/jobs/${encodeURIComponent(jobId)}/results${query}`, { method: "GET", auth: true });
|
|
@@ -104,6 +119,17 @@ const createPlatformClient = (options = {}) => {
|
|
|
104
119
|
}
|
|
105
120
|
return await response.json();
|
|
106
121
|
};
|
|
122
|
+
const getJobOrBackOff = async (jobId, signal, interval) => {
|
|
123
|
+
try {
|
|
124
|
+
return await getJob(jobId, signal);
|
|
125
|
+
} catch (error) {
|
|
126
|
+
if (!(error instanceof import_error.PlatformApiError && error.status === 429)) {
|
|
127
|
+
throw error;
|
|
128
|
+
}
|
|
129
|
+
await sleep(error.retryAfter !== void 0 ? error.retryAfter * 1e3 : interval, signal);
|
|
130
|
+
return void 0;
|
|
131
|
+
}
|
|
132
|
+
};
|
|
107
133
|
return {
|
|
108
134
|
scrape: (opts) => request("/v1/scrape", { method: "POST", body: opts, auth: true }),
|
|
109
135
|
scrapeAsync: (opts) => request("/v1/scrape", { method: "POST", body: { ...opts, async: true }, auth: true }),
|
|
@@ -114,17 +140,48 @@ const createPlatformClient = (options = {}) => {
|
|
|
114
140
|
waitForJob: async (jobId, opts = {}) => {
|
|
115
141
|
const interval = opts.pollIntervalMs ?? 2e3;
|
|
116
142
|
const timeout = opts.timeoutMs ?? 10 * 60 * 1e3;
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
143
|
+
if (!Number.isFinite(interval) || interval < 0 || interval > 2147483647) {
|
|
144
|
+
throw new RangeError("pollIntervalMs must be between 0 and 2147483647");
|
|
145
|
+
}
|
|
146
|
+
if (!Number.isFinite(timeout) || timeout <= 0 || timeout > 2147483647) {
|
|
147
|
+
throw new RangeError("timeoutMs must be greater than 0 and at most 2147483647");
|
|
148
|
+
}
|
|
149
|
+
opts.signal?.throwIfAborted();
|
|
150
|
+
const controller = new AbortController();
|
|
151
|
+
const { signal } = controller;
|
|
152
|
+
const onAbort = () => controller.abort(opts.signal?.reason);
|
|
153
|
+
opts.signal?.addEventListener("abort", onAbort, { once: true });
|
|
154
|
+
const timer = setTimeout(
|
|
155
|
+
() => controller.abort(new import_error.PlatformApiError("poll_timeout", `job ${jobId} did not finish within ${timeout}ms`, 0)),
|
|
156
|
+
timeout
|
|
157
|
+
);
|
|
158
|
+
let rejectOnAbort = () => {
|
|
159
|
+
};
|
|
160
|
+
const aborted = new Promise((_resolve, reject) => {
|
|
161
|
+
rejectOnAbort = () => reject(signal.reason);
|
|
162
|
+
signal.addEventListener("abort", rejectOnAbort, { once: true });
|
|
163
|
+
});
|
|
164
|
+
const poll = async () => {
|
|
165
|
+
for (; ; ) {
|
|
166
|
+
signal.throwIfAborted();
|
|
167
|
+
const status = await getJobOrBackOff(jobId, signal, interval);
|
|
168
|
+
signal.throwIfAborted();
|
|
169
|
+
if (status === void 0) {
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
opts.onStatus?.(status);
|
|
173
|
+
if (TERMINAL_STATES.has(status.status)) {
|
|
174
|
+
return status;
|
|
175
|
+
}
|
|
176
|
+
await sleep(interval, signal);
|
|
126
177
|
}
|
|
127
|
-
|
|
178
|
+
};
|
|
179
|
+
try {
|
|
180
|
+
return await Promise.race([poll(), aborted]);
|
|
181
|
+
} finally {
|
|
182
|
+
clearTimeout(timer);
|
|
183
|
+
opts.signal?.removeEventListener("abort", onAbort);
|
|
184
|
+
signal.removeEventListener("abort", rejectOnAbort);
|
|
128
185
|
}
|
|
129
186
|
},
|
|
130
187
|
jobResults: async function* (jobId) {
|
|
@@ -18,10 +18,12 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
18
18
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
19
|
var platform_exports = {};
|
|
20
20
|
__export(platform_exports, {
|
|
21
|
+
CRAWL_SITEMAP_MODES: () => import_types.CRAWL_SITEMAP_MODES,
|
|
21
22
|
DEFAULT_BASE_URL: () => import_client.DEFAULT_BASE_URL,
|
|
22
23
|
ENGINES: () => import_types.ENGINES,
|
|
23
24
|
PlatformApiError: () => import_error.PlatformApiError,
|
|
24
25
|
REGIONS: () => import_types.REGIONS,
|
|
26
|
+
REQUESTED_ENGINES: () => import_types.REQUESTED_ENGINES,
|
|
25
27
|
createPlatformClient: () => import_client.createPlatformClient,
|
|
26
28
|
isStoredPageStub: () => import_types.isStoredPageStub
|
|
27
29
|
});
|
|
@@ -31,10 +33,12 @@ var import_error = require("./error");
|
|
|
31
33
|
var import_types = require("./types");
|
|
32
34
|
// Annotate the CommonJS export names for ESM import in node:
|
|
33
35
|
0 && (module.exports = {
|
|
36
|
+
CRAWL_SITEMAP_MODES,
|
|
34
37
|
DEFAULT_BASE_URL,
|
|
35
38
|
ENGINES,
|
|
36
39
|
PlatformApiError,
|
|
37
40
|
REGIONS,
|
|
41
|
+
REQUESTED_ENGINES,
|
|
38
42
|
createPlatformClient,
|
|
39
43
|
isStoredPageStub
|
|
40
44
|
});
|
|
@@ -18,17 +18,23 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
18
18
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
19
|
var types_exports = {};
|
|
20
20
|
__export(types_exports, {
|
|
21
|
+
CRAWL_SITEMAP_MODES: () => CRAWL_SITEMAP_MODES,
|
|
21
22
|
ENGINES: () => ENGINES,
|
|
22
23
|
REGIONS: () => REGIONS,
|
|
24
|
+
REQUESTED_ENGINES: () => REQUESTED_ENGINES,
|
|
23
25
|
isStoredPageStub: () => isStoredPageStub
|
|
24
26
|
});
|
|
25
27
|
module.exports = __toCommonJS(types_exports);
|
|
26
28
|
const ENGINES = ["fetch", "browser", "proxy-fetch", "proxy-browser"];
|
|
27
|
-
const
|
|
29
|
+
const REQUESTED_ENGINES = [...ENGINES, "auto"];
|
|
30
|
+
const REGIONS = ["auto", "jp"];
|
|
31
|
+
const CRAWL_SITEMAP_MODES = ["skip", "include", "only"];
|
|
28
32
|
const isStoredPageStub = (item) => item.status === "ok" && "resultUrl" in item;
|
|
29
33
|
// Annotate the CommonJS export names for ESM import in node:
|
|
30
34
|
0 && (module.exports = {
|
|
35
|
+
CRAWL_SITEMAP_MODES,
|
|
31
36
|
ENGINES,
|
|
32
37
|
REGIONS,
|
|
38
|
+
REQUESTED_ENGINES,
|
|
33
39
|
isStoredPageStub
|
|
34
40
|
});
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// src/detect-client-shell.ts
|
|
2
|
+
var SHELL_SUSPICION_THRESHOLD = 300;
|
|
3
|
+
var SHELL_EMPTY_THRESHOLD = 50;
|
|
4
|
+
var BODY_PATTERN = /<body[^>]*>([\s\S]*?)<\/body>/i;
|
|
5
|
+
var HEAD_PATTERN = /<head[^>]*>[\s\S]*?<\/head>/gi;
|
|
6
|
+
var NOSCRIPT_PATTERN = /<noscript[^>]*>([\s\S]*?)<\/noscript>/gi;
|
|
7
|
+
var INVISIBLE_ELEMENT_PATTERN = /<(script|style|template|svg|noscript)[^>]*>[\s\S]*?<\/\1>/gi;
|
|
8
|
+
var COMMENT_PATTERN = /<!--[\s\S]*?-->/g;
|
|
9
|
+
var TAG_PATTERN = /<[^>]+>/g;
|
|
10
|
+
var MOUNT_POINT_PATTERN = /<[a-z][^>]*\sid=["'](?:root|app|__next|___gatsby|__nuxt|q-app|svelte|react-root)["']|<app-root[\s>]|\sdata-reactroot[\s>=]/i;
|
|
11
|
+
var CHALLENGE_PATTERN = /cf-chl|challenge-platform|cf_chl_opt|just a moment|attention required|checking your browser|_incapsula_|px-captcha|datadome/i;
|
|
12
|
+
var ENTITIES = {
|
|
13
|
+
" ": " ",
|
|
14
|
+
"&": "&",
|
|
15
|
+
"<": "<",
|
|
16
|
+
">": ">",
|
|
17
|
+
""": '"',
|
|
18
|
+
"'": "'"
|
|
19
|
+
};
|
|
20
|
+
var visibleTextOf = (markup) => markup.replace(COMMENT_PATTERN, " ").replace(INVISIBLE_ELEMENT_PATTERN, " ").replace(TAG_PATTERN, " ").replace(/&(?:nbsp|amp|lt|gt|quot|#39);/g, (entity) => ENTITIES[entity] ?? " ").replace(/\s+/g, " ").trim();
|
|
21
|
+
var noscriptMentionsJavascript = (html) => {
|
|
22
|
+
for (const match of html.matchAll(NOSCRIPT_PATTERN)) {
|
|
23
|
+
if (/javascript/i.test(match[1] ?? "")) {
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return false;
|
|
28
|
+
};
|
|
29
|
+
var detectClientShell = (html) => {
|
|
30
|
+
const body = BODY_PATTERN.exec(html)?.[1] ?? html.replace(HEAD_PATTERN, " ");
|
|
31
|
+
const visibleTextLength = visibleTextOf(body).length;
|
|
32
|
+
if (visibleTextLength >= SHELL_SUSPICION_THRESHOLD) {
|
|
33
|
+
return { isShell: false, visibleTextLength };
|
|
34
|
+
}
|
|
35
|
+
if (CHALLENGE_PATTERN.test(html)) {
|
|
36
|
+
return { isShell: true, reason: "anti-bot-challenge", visibleTextLength };
|
|
37
|
+
}
|
|
38
|
+
if (MOUNT_POINT_PATTERN.test(body)) {
|
|
39
|
+
return { isShell: true, reason: "spa-shell", visibleTextLength };
|
|
40
|
+
}
|
|
41
|
+
if (noscriptMentionsJavascript(html)) {
|
|
42
|
+
return { isShell: true, reason: "noscript-only", visibleTextLength };
|
|
43
|
+
}
|
|
44
|
+
if (visibleTextLength < SHELL_EMPTY_THRESHOLD) {
|
|
45
|
+
return { isShell: true, reason: "empty-body", visibleTextLength };
|
|
46
|
+
}
|
|
47
|
+
return { isShell: false, visibleTextLength };
|
|
48
|
+
};
|
|
49
|
+
export {
|
|
50
|
+
SHELL_EMPTY_THRESHOLD,
|
|
51
|
+
SHELL_SUSPICION_THRESHOLD,
|
|
52
|
+
detectClientShell
|
|
53
|
+
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// src/extract-meta-refresh.ts
|
|
2
|
+
var MAX_META_REFRESH_DELAY_SECONDS = 10;
|
|
3
|
+
var META_TAG_PATTERN = /<meta\b[^>]*>/gi;
|
|
4
|
+
var HTTP_EQUIV_REFRESH_PATTERN = /\bhttp-equiv\s*=\s*("\s*refresh\s*"|'\s*refresh\s*'|refresh\b)/i;
|
|
5
|
+
var CONTENT_ATTR_PATTERN = /\bcontent\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s>]+))/i;
|
|
6
|
+
var CONTENT_VALUE_PATTERN = /^\s*(\d+(?:\.\d+)?)\s*[;,]\s*url\s*=\s*(?:"([^"]*)"|'([^']*)'|(.+?))\s*$/i;
|
|
7
|
+
var resolveTarget = (raw, baseUrl) => {
|
|
8
|
+
try {
|
|
9
|
+
return baseUrl === void 0 ? new URL(raw) : new URL(raw, baseUrl);
|
|
10
|
+
} catch {
|
|
11
|
+
return void 0;
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
var extractMetaRefresh = (html, baseUrl) => {
|
|
15
|
+
for (const [tag] of html.matchAll(META_TAG_PATTERN)) {
|
|
16
|
+
if (!HTTP_EQUIV_REFRESH_PATTERN.test(tag)) {
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
const contentMatch = CONTENT_ATTR_PATTERN.exec(tag);
|
|
20
|
+
const content = contentMatch?.[1] ?? contentMatch?.[2] ?? contentMatch?.[3];
|
|
21
|
+
if (!content) {
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
const value = CONTENT_VALUE_PATTERN.exec(content);
|
|
25
|
+
if (!value) {
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
const delaySeconds = Number.parseFloat(value[1] ?? "");
|
|
29
|
+
const raw = (value[2] ?? value[3] ?? value[4] ?? "").trim();
|
|
30
|
+
if (!raw || delaySeconds > MAX_META_REFRESH_DELAY_SECONDS) {
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
const target = resolveTarget(raw, baseUrl);
|
|
34
|
+
if (!target || target.protocol !== "http:" && target.protocol !== "https:") {
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
if (baseUrl !== void 0 && resolveTarget(baseUrl, void 0)?.href === target.href) {
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
return { url: target.href, delaySeconds };
|
|
41
|
+
}
|
|
42
|
+
return void 0;
|
|
43
|
+
};
|
|
44
|
+
export {
|
|
45
|
+
MAX_META_REFRESH_DELAY_SECONDS,
|
|
46
|
+
extractMetaRefresh
|
|
47
|
+
};
|
|
@@ -374,7 +374,11 @@ var countAnchors = (element) => {
|
|
|
374
374
|
}
|
|
375
375
|
return count;
|
|
376
376
|
};
|
|
377
|
+
var LITERAL_TEXT_TAGS = /* @__PURE__ */ new Set(["code", "kbd", "samp", "var", "pre"]);
|
|
377
378
|
var isMidArticleWidget = (element) => {
|
|
379
|
+
if (LITERAL_TEXT_TAGS.has(element.tagName)) {
|
|
380
|
+
return false;
|
|
381
|
+
}
|
|
378
382
|
if (element.tagName === "form" || element.tagName === "fieldset") {
|
|
379
383
|
return true;
|
|
380
384
|
}
|
package/dist/html-to-mdast.js
CHANGED
|
@@ -12,13 +12,14 @@ import { definitionListHandler } from "./mdast-handlers/definition-list-handler.
|
|
|
12
12
|
import { mathHandler } from "./mdast-handlers/math-handler.js";
|
|
13
13
|
import { supSubHandler } from "./mdast-handlers/sup-sub-handler.js";
|
|
14
14
|
import { normalizeHast } from "./normalize/index.js";
|
|
15
|
+
import { cloneHast } from "./utils/hast-fast.js";
|
|
15
16
|
import { getLangFromHast, getLangFromStr, getUrlFromHast } from "./utils/hast-utils.js";
|
|
16
17
|
var htmlToMdast = (htmlOrHast, options) => {
|
|
17
18
|
const { extractors, url: defaultUrl, lang: defaultLang } = options || {};
|
|
18
19
|
const [lang, hast] = typeof htmlOrHast === "string" ? [defaultLang || getLangFromStr(htmlOrHast), fromHtml(htmlOrHast, { fragment: true })] : [defaultLang || getLangFromHast(htmlOrHast), htmlOrHast];
|
|
19
20
|
const isOwned = typeof htmlOrHast === "string" || (options?.owned ?? false);
|
|
20
21
|
const url = defaultUrl || getUrlFromHast(hast);
|
|
21
|
-
const extractedHast = pipeExtractors({ hast, lang, url, owned:
|
|
22
|
+
const extractedHast = pipeExtractors({ hast: isOwned ? hast : cloneHast(hast), lang, url, owned: true }, extractors);
|
|
22
23
|
normalizeHast(extractedHast, options?.normalize);
|
|
23
24
|
const mdast = toMdast(extractedHast, {
|
|
24
25
|
handlers: {
|
package/dist/index.js
CHANGED
|
@@ -22,15 +22,29 @@ import {
|
|
|
22
22
|
runAdapters,
|
|
23
23
|
youtubeCaptionTracks
|
|
24
24
|
} from "./adapters/index.js";
|
|
25
|
+
import {
|
|
26
|
+
detectClientShell,
|
|
27
|
+
SHELL_EMPTY_THRESHOLD,
|
|
28
|
+
SHELL_SUSPICION_THRESHOLD
|
|
29
|
+
} from "./detect-client-shell.js";
|
|
30
|
+
import {
|
|
31
|
+
extractMetaRefresh,
|
|
32
|
+
MAX_META_REFRESH_DELAY_SECONDS
|
|
33
|
+
} from "./extract-meta-refresh.js";
|
|
25
34
|
import { extractMetadata, toFrontmatter } from "./metadata/index.js";
|
|
26
35
|
import { normalizeHast } from "./normalize/index.js";
|
|
27
36
|
export {
|
|
28
37
|
BUILTIN_ADAPTERS,
|
|
38
|
+
MAX_META_REFRESH_DELAY_SECONDS,
|
|
39
|
+
SHELL_EMPTY_THRESHOLD,
|
|
40
|
+
SHELL_SUSPICION_THRESHOLD,
|
|
29
41
|
adapterById,
|
|
30
42
|
autoExtractor,
|
|
31
43
|
buildPageSignature,
|
|
32
44
|
createAutoExtractor,
|
|
33
45
|
defineSelectorAdapter,
|
|
46
|
+
detectClientShell,
|
|
47
|
+
extractMetaRefresh,
|
|
34
48
|
extractMetadata,
|
|
35
49
|
htmlToMarkdown,
|
|
36
50
|
htmlToMarkdownWithMetadata,
|
package/dist/link-replacer.js
CHANGED
|
@@ -1,17 +1,23 @@
|
|
|
1
1
|
// src/link-replacer.ts
|
|
2
|
-
var
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
2
|
+
var absoluteUrl = (url, base) => {
|
|
3
|
+
if (/^(?:[a-z][a-z\d+.-]*:|#)/i.test(url)) {
|
|
4
|
+
return url;
|
|
5
|
+
}
|
|
6
|
+
try {
|
|
7
|
+
return new URL(url, base).href;
|
|
8
|
+
} catch {
|
|
9
|
+
return url;
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
var linkReplacer = (node, base) => {
|
|
13
|
+
let result = node;
|
|
14
|
+
if (node.type === "link" || node.type === "image" || node.type === "definition") {
|
|
15
|
+
result = { ...node, url: absoluteUrl(node.url, base) };
|
|
16
|
+
}
|
|
17
|
+
if ("children" in result) {
|
|
18
|
+
return { ...result, children: result.children.map((child) => linkReplacer(child, base)) };
|
|
19
|
+
}
|
|
20
|
+
return result;
|
|
15
21
|
};
|
|
16
22
|
export {
|
|
17
23
|
linkReplacer
|
|
@@ -1,14 +1,21 @@
|
|
|
1
1
|
// src/loaders/cf-puppeteer.ts
|
|
2
|
-
import puppeteer from "@cloudflare/puppeteer";
|
|
3
|
-
var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
2
|
+
import puppeteer, { TimeoutError } from "@cloudflare/puppeteer";
|
|
4
3
|
var loadHtml = async (url, ctx) => {
|
|
5
4
|
const browser = await puppeteer.launch(ctx);
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
5
|
+
try {
|
|
6
|
+
const page = await browser.newPage();
|
|
7
|
+
await page.goto(url);
|
|
8
|
+
try {
|
|
9
|
+
await page.waitForNetworkIdle({ timeout: 1e4 });
|
|
10
|
+
} catch (error) {
|
|
11
|
+
if (!(error instanceof TimeoutError)) {
|
|
12
|
+
throw error;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
return await page.content();
|
|
16
|
+
} finally {
|
|
17
|
+
await browser.close();
|
|
18
|
+
}
|
|
12
19
|
};
|
|
13
20
|
export {
|
|
14
21
|
loadHtml
|
package/dist/loaders/fetch.js
CHANGED
|
@@ -1,8 +1,20 @@
|
|
|
1
1
|
// src/loaders/fetch.ts
|
|
2
|
+
import { extractMetaRefresh } from "../extract-meta-refresh.js";
|
|
2
3
|
var USER_AGENT = "mozilla/5.0 (windows nt 10.0; win64; x64) applewebkit/537.36 (khtml, like gecko) chrome/125.0.0.0 safari/537.36";
|
|
4
|
+
var MAX_META_REFRESH_HOPS = 3;
|
|
3
5
|
var loadHtml = async (url, userAgent = USER_AGENT) => {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
+
let current = url;
|
|
7
|
+
let html = "";
|
|
8
|
+
for (let hop = 0; hop <= MAX_META_REFRESH_HOPS; hop++) {
|
|
9
|
+
const response = await fetch(current, { headers: { "User-Agent": userAgent } });
|
|
10
|
+
html = await response.text();
|
|
11
|
+
const refresh = extractMetaRefresh(html, response.url || current);
|
|
12
|
+
if (!refresh) {
|
|
13
|
+
break;
|
|
14
|
+
}
|
|
15
|
+
current = refresh.url;
|
|
16
|
+
}
|
|
17
|
+
return html;
|
|
6
18
|
};
|
|
7
19
|
export {
|
|
8
20
|
USER_AGENT,
|
|
@@ -13,40 +13,42 @@ var SUPER_BYPASS_DEVICE = {
|
|
|
13
13
|
var loadHtml = async (url, options) => {
|
|
14
14
|
const { browser, waitUntil, timeout, superBypassMode } = options ?? {};
|
|
15
15
|
const _browser = browser ?? await chromium.launch({ headless: true });
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
await
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
await
|
|
27
|
-
|
|
28
|
-
|
|
16
|
+
let context;
|
|
17
|
+
try {
|
|
18
|
+
context = await _browser.newContext(superBypassMode ? SUPER_BYPASS_DEVICE : devices["Desktop Chrome"]);
|
|
19
|
+
if (superBypassMode) {
|
|
20
|
+
await context.addInitScript(() => {
|
|
21
|
+
Object.defineProperty(navigator, "webdriver", { get: () => void 0 });
|
|
22
|
+
Object.defineProperty(navigator, "languages", { get: () => ["en-US", "en"] });
|
|
23
|
+
Object.defineProperty(navigator, "plugins", { get: () => [1, 2, 3, 4, 5] });
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
const page = await context.newPage();
|
|
27
|
+
if (superBypassMode) {
|
|
28
|
+
await page.route("**/*.js", (route) => {
|
|
29
|
+
if (route.request().url().includes("captcha-delivery")) {
|
|
30
|
+
return route.abort();
|
|
31
|
+
}
|
|
32
|
+
return route.continue();
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
await page.goto(url, { waitUntil: waitUntil ?? "load", timeout });
|
|
36
|
+
await page.evaluate(() => {
|
|
37
|
+
const elements = document.querySelectorAll("*");
|
|
38
|
+
for (const element of elements) {
|
|
39
|
+
const rect = element.getBoundingClientRect();
|
|
40
|
+
element.setAttribute("data-rwidth", rect.width.toString());
|
|
41
|
+
element.setAttribute("data-rheight", rect.height.toString());
|
|
29
42
|
}
|
|
30
|
-
return route.continue();
|
|
31
43
|
});
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
element.setAttribute("data-rwidth", rect.width.toString());
|
|
39
|
-
element.setAttribute("data-rheight", rect.height.toString());
|
|
44
|
+
return await page.content();
|
|
45
|
+
} finally {
|
|
46
|
+
if (browser) {
|
|
47
|
+
await context?.close();
|
|
48
|
+
} else {
|
|
49
|
+
await _browser.close();
|
|
40
50
|
}
|
|
41
|
-
});
|
|
42
|
-
const html = await page.content();
|
|
43
|
-
await page.close();
|
|
44
|
-
if (browser) {
|
|
45
|
-
await context.close();
|
|
46
|
-
} else {
|
|
47
|
-
await _browser.close();
|
|
48
51
|
}
|
|
49
|
-
return html;
|
|
50
52
|
};
|
|
51
53
|
export {
|
|
52
54
|
loadHtml
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
// src/loaders/puppeteer.ts
|
|
2
|
-
import puppeteer from "puppeteer";
|
|
3
|
-
var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
2
|
+
import puppeteer, { TimeoutError } from "puppeteer";
|
|
4
3
|
var loadHtml = async (url, ctx) => {
|
|
5
4
|
const browser = await puppeteer.launch(
|
|
6
5
|
ctx || {
|
|
@@ -8,12 +7,20 @@ var loadHtml = async (url, ctx) => {
|
|
|
8
7
|
args: ["--no-sandbox", "--disable-setuid-sandbox"]
|
|
9
8
|
}
|
|
10
9
|
);
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
10
|
+
try {
|
|
11
|
+
const page = await browser.newPage();
|
|
12
|
+
await page.goto(url);
|
|
13
|
+
try {
|
|
14
|
+
await page.waitForNetworkIdle({ timeout: 1e4 });
|
|
15
|
+
} catch (error) {
|
|
16
|
+
if (!(error instanceof TimeoutError)) {
|
|
17
|
+
throw error;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return await page.content();
|
|
21
|
+
} finally {
|
|
22
|
+
await browser.close();
|
|
23
|
+
}
|
|
17
24
|
};
|
|
18
25
|
export {
|
|
19
26
|
loadHtml
|
package/dist/md-splitter.js
CHANGED
|
@@ -3,15 +3,13 @@ import { mdastToMarkdown } from "./mdast-to-markdown.js";
|
|
|
3
3
|
import { chunk } from "./utils/common.js";
|
|
4
4
|
import { internalType, unwarpRoot, warpRoot } from "./utils/mdast-utils.js";
|
|
5
5
|
var PRIORITY_SPLITTERS = ["h1", "h2", "h3", "h4", "h5", "h6", "list", "table", "code"];
|
|
6
|
-
var
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
}
|
|
10
|
-
};
|
|
11
|
-
var _mdastSplitter = async (contents, checker, splitterGenerator) => {
|
|
12
|
-
const splitter = splitterGenerator.next().value;
|
|
6
|
+
var _mdastSplitter = async (contents, checker, priority, signal) => {
|
|
7
|
+
signal?.throwIfAborted();
|
|
8
|
+
const splitter = PRIORITY_SPLITTERS[priority];
|
|
13
9
|
const markdown = mdastToMarkdown(warpRoot(contents));
|
|
14
|
-
|
|
10
|
+
const accepted = await checker(markdown);
|
|
11
|
+
signal?.throwIfAborted();
|
|
12
|
+
if (accepted || contents.length === 1) {
|
|
15
13
|
return [contents];
|
|
16
14
|
}
|
|
17
15
|
const chunked = splitter ? contents.reduce((acc, content) => {
|
|
@@ -22,11 +20,26 @@ var _mdastSplitter = async (contents, checker, splitterGenerator) => {
|
|
|
22
20
|
acc[acc.length - 1].push(content);
|
|
23
21
|
return acc;
|
|
24
22
|
}, []) : chunk(contents, Math.ceil(contents.length / 2));
|
|
25
|
-
const splitting = chunked.map((chunk2) => _mdastSplitter(chunk2, checker,
|
|
23
|
+
const splitting = chunked.map((chunk2) => _mdastSplitter(chunk2, checker, priority + 1, signal));
|
|
26
24
|
return Promise.all(splitting).then((chunks) => chunks.flat());
|
|
27
25
|
};
|
|
28
|
-
var mdastSplitter = (mdast, checker,
|
|
29
|
-
|
|
26
|
+
var mdastSplitter = async (mdast, checker, options) => {
|
|
27
|
+
const signal = options?.signal;
|
|
28
|
+
if (!signal) {
|
|
29
|
+
return _mdastSplitter(unwarpRoot(mdast), checker, 0);
|
|
30
|
+
}
|
|
31
|
+
signal.throwIfAborted();
|
|
32
|
+
let onAbort = () => {
|
|
33
|
+
};
|
|
34
|
+
const aborted = new Promise((_, reject) => {
|
|
35
|
+
onAbort = () => reject(signal.reason);
|
|
36
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
37
|
+
});
|
|
38
|
+
try {
|
|
39
|
+
return await Promise.race([_mdastSplitter(unwarpRoot(mdast), checker, 0, signal), aborted]);
|
|
40
|
+
} finally {
|
|
41
|
+
signal.removeEventListener("abort", onAbort);
|
|
42
|
+
}
|
|
30
43
|
};
|
|
31
44
|
export {
|
|
32
45
|
mdastSplitter
|